summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-11-04 00:41:00 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-11-04 00:41:00 +0000
commit725bf3e2372b8ab29e521e864d28770fdf2c0443 (patch)
treed094f43841ac9dc9c54fd96ffd940b6f71f8ba39
parent190c25efdbd5a5bdbb41a2818cf39a05d0ea9be0 (diff)
parent7b99dc653ed133fef81d0423fee87d45299097e2 (diff)
downloadAdServices-android13-mainline-ipsec-release.tar.gz
Snap for 9254005 from 7b99dc653ed133fef81d0423fee87d45299097e2 to mainline-ipsec-releaseaml_ips_331910010aml_ips_331312000aml_ips_331310000android13-mainline-ipsec-release
Change-Id: Id77860bb313cd5104abdea4a778d197a1db28861
-rw-r--r--adservices/TEST_MAPPING25
-rw-r--r--adservices/apk/AndroidManifest.xml108
-rw-r--r--adservices/apk/assets/classifier/classifier_assets_metadata.json5
-rw-r--r--adservices/apk/assets/classifier/precomputed_app_list.csv5
-rw-r--r--adservices/apk/assets/classifier/stopwords.txt128
-rw-r--r--adservices/apk/build.gradle7
-rw-r--r--adservices/apk/java/com/android/adservices/adid/AdIdService.java3
-rw-r--r--adservices/apk/java/com/android/adservices/adselection/AdSelectionService.java16
-rw-r--r--adservices/apk/java/com/android/adservices/appsetid/AppSetIdService.java3
-rw-r--r--adservices/apk/java/com/android/adservices/customaudience/CustomAudienceService.java14
-rw-r--r--adservices/apk/java/com/android/adservices/measurement/MeasurementService.java10
-rw-r--r--adservices/apk/java/com/android/adservices/topics/TopicsService.java8
-rw-r--r--adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationConfirmationFragment.java11
-rw-r--r--adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationFragment.java162
-rw-r--r--adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationTrigger.java17
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/ActionDelegate.java479
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/AdServicesSettingsActivity.java93
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/DialogManager.java225
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/activities/AdServicesBaseActivity.java45
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/activities/AdServicesSettingsMainActivity.java65
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/activities/AppsActivity.java55
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/activities/BlockedAppsActivity.java59
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/activities/BlockedTopicsActivity.java58
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/activities/TopicsActivity.java53
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/delegates/AppsActionDelegate.java130
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/delegates/BaseActionDelegate.java110
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/delegates/BlockedAppsActionDelegate.java88
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/delegates/BlockedTopicsActionDelegate.java83
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/delegates/MainActionDelegate.java123
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/delegates/TopicsActionDelegate.java119
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsAppsFragment.java85
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedAppsFragment.java46
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedTopicsFragment.java46
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsMainFragment.java18
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsTopicsFragment.java76
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/AppsListViewAdapter.java58
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/TopicsListViewAdapter.java40
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/viewmodels/AppsViewModel.java56
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/viewmodels/BlockedAppsViewModel.java122
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/viewmodels/BlockedTopicsViewModel.java121
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/viewmodels/MainViewModel.java14
-rw-r--r--adservices/apk/java/com/android/adservices/ui/settings/viewmodels/TopicsViewModel.java54
-rw-r--r--adservices/apk/res/drawable-night/ic_main_view_image.xml105
-rw-r--r--adservices/apk/res/drawable/ic_placeholder_icon_for_empty_apps_list.xml26
-rw-r--r--adservices/apk/res/drawable/ic_placeholder_icon_for_empty_topics_list.xml (renamed from adservices/apk/res/drawable/ic_placeholder_icon.xml)0
-rw-r--r--adservices/apk/res/layout/adservices_settings_main_activity.xml3
-rw-r--r--adservices/apk/res/layout/apps_fragment.xml7
-rw-r--r--adservices/apk/res/layout/blocked_apps_fragment.xml2
-rw-r--r--adservices/apk/res/layout/blocked_topics_fragment.xml2
-rw-r--r--adservices/apk/res/layout/consent_notification_accept_confirmation_fragment.xml10
-rw-r--r--adservices/apk/res/layout/consent_notification_decline_confirmation_fragment.xml10
-rw-r--r--adservices/apk/res/layout/consent_notification_fragment.xml58
-rw-r--r--adservices/apk/res/layout/consent_notification_fragment_eu.xml59
-rw-r--r--adservices/apk/res/layout/main_fragment.xml1
-rw-r--r--adservices/apk/res/layout/topics_fragment.xml8
-rw-r--r--adservices/apk/res/values-af/strings.xml26
-rw-r--r--adservices/apk/res/values-am/strings.xml34
-rw-r--r--adservices/apk/res/values-ar/strings.xml26
-rw-r--r--adservices/apk/res/values-as/strings.xml26
-rw-r--r--adservices/apk/res/values-az/strings.xml26
-rw-r--r--adservices/apk/res/values-b+sr+Latn/strings.xml26
-rw-r--r--adservices/apk/res/values-be/strings.xml26
-rw-r--r--adservices/apk/res/values-bg/strings.xml26
-rw-r--r--adservices/apk/res/values-bn/strings.xml26
-rw-r--r--adservices/apk/res/values-bs/strings.xml26
-rw-r--r--adservices/apk/res/values-ca/strings.xml26
-rw-r--r--adservices/apk/res/values-cs/strings.xml26
-rw-r--r--adservices/apk/res/values-da/strings.xml26
-rw-r--r--adservices/apk/res/values-de/strings.xml26
-rw-r--r--adservices/apk/res/values-el/strings.xml26
-rw-r--r--adservices/apk/res/values-en-rAU/strings.xml26
-rw-r--r--adservices/apk/res/values-en-rCA/strings.xml26
-rw-r--r--adservices/apk/res/values-en-rGB/strings.xml26
-rw-r--r--adservices/apk/res/values-en-rIN/strings.xml26
-rw-r--r--adservices/apk/res/values-en-rXC/strings.xml26
-rw-r--r--adservices/apk/res/values-es-rUS/strings.xml26
-rw-r--r--adservices/apk/res/values-es/strings.xml40
-rw-r--r--adservices/apk/res/values-et/strings.xml26
-rw-r--r--adservices/apk/res/values-eu/strings.xml42
-rw-r--r--adservices/apk/res/values-fa/strings.xml26
-rw-r--r--adservices/apk/res/values-fi/strings.xml26
-rw-r--r--adservices/apk/res/values-fr-rCA/strings.xml28
-rw-r--r--adservices/apk/res/values-fr/strings.xml26
-rw-r--r--adservices/apk/res/values-gl/strings.xml26
-rw-r--r--adservices/apk/res/values-gu/strings.xml26
-rw-r--r--adservices/apk/res/values-hi/strings.xml26
-rw-r--r--adservices/apk/res/values-hr/strings.xml26
-rw-r--r--adservices/apk/res/values-hu/strings.xml26
-rw-r--r--adservices/apk/res/values-hy/strings.xml28
-rw-r--r--adservices/apk/res/values-in/strings.xml28
-rw-r--r--adservices/apk/res/values-is/strings.xml26
-rw-r--r--adservices/apk/res/values-it/strings.xml28
-rw-r--r--adservices/apk/res/values-iw/strings.xml26
-rw-r--r--adservices/apk/res/values-ja/strings.xml26
-rw-r--r--adservices/apk/res/values-ka/strings.xml26
-rw-r--r--adservices/apk/res/values-kk/strings.xml26
-rw-r--r--adservices/apk/res/values-km/strings.xml26
-rw-r--r--adservices/apk/res/values-kn/strings.xml26
-rw-r--r--adservices/apk/res/values-ko/strings.xml26
-rw-r--r--adservices/apk/res/values-ky/strings.xml28
-rw-r--r--adservices/apk/res/values-lo/strings.xml26
-rw-r--r--adservices/apk/res/values-lt/strings.xml26
-rw-r--r--adservices/apk/res/values-lv/strings.xml26
-rw-r--r--adservices/apk/res/values-mk/strings.xml26
-rw-r--r--adservices/apk/res/values-ml/strings.xml26
-rw-r--r--adservices/apk/res/values-mn/strings.xml26
-rw-r--r--adservices/apk/res/values-mr/strings.xml26
-rw-r--r--adservices/apk/res/values-ms/strings.xml26
-rw-r--r--adservices/apk/res/values-my/strings.xml30
-rw-r--r--adservices/apk/res/values-nb/strings.xml26
-rw-r--r--adservices/apk/res/values-ne/strings.xml26
-rw-r--r--adservices/apk/res/values-night/colors.xml5
-rw-r--r--adservices/apk/res/values-nl/strings.xml26
-rw-r--r--adservices/apk/res/values-or/strings.xml26
-rw-r--r--adservices/apk/res/values-pa/strings.xml26
-rw-r--r--adservices/apk/res/values-pl/strings.xml26
-rw-r--r--adservices/apk/res/values-pt-rBR/strings.xml26
-rw-r--r--adservices/apk/res/values-pt-rPT/strings.xml26
-rw-r--r--adservices/apk/res/values-pt/strings.xml26
-rw-r--r--adservices/apk/res/values-ro/strings.xml44
-rw-r--r--adservices/apk/res/values-ru/strings.xml26
-rw-r--r--adservices/apk/res/values-si/strings.xml26
-rw-r--r--adservices/apk/res/values-sk/strings.xml26
-rw-r--r--adservices/apk/res/values-sl/strings.xml26
-rw-r--r--adservices/apk/res/values-sq/strings.xml26
-rw-r--r--adservices/apk/res/values-sr/strings.xml26
-rw-r--r--adservices/apk/res/values-sv/strings.xml26
-rw-r--r--adservices/apk/res/values-sw/strings.xml26
-rw-r--r--adservices/apk/res/values-ta/strings.xml26
-rw-r--r--adservices/apk/res/values-te/strings.xml26
-rw-r--r--adservices/apk/res/values-th/strings.xml26
-rw-r--r--adservices/apk/res/values-tl/strings.xml26
-rw-r--r--adservices/apk/res/values-tr/strings.xml26
-rw-r--r--adservices/apk/res/values-uk/strings.xml26
-rw-r--r--adservices/apk/res/values-ur/strings.xml26
-rw-r--r--adservices/apk/res/values-uz/strings.xml26
-rw-r--r--adservices/apk/res/values-vi/strings.xml26
-rw-r--r--adservices/apk/res/values-zh-rCN/strings.xml26
-rw-r--r--adservices/apk/res/values-zh-rHK/strings.xml26
-rw-r--r--adservices/apk/res/values-zh-rTW/strings.xml26
-rw-r--r--adservices/apk/res/values-zu/strings.xml26
-rw-r--r--adservices/apk/res/values/colors.xml7
-rw-r--r--adservices/apk/res/values/dimens.xml4
-rw-r--r--adservices/apk/res/values/strings.xml63
-rw-r--r--adservices/apk/res/values/styles.xml7
-rw-r--r--adservices/apk/res/values/themes.xml7
-rw-r--r--adservices/apk/tests/Android.bp1
-rw-r--r--adservices/apk/tests/AndroidManifest.xml58
-rw-r--r--adservices/apk/tests/src/com/android/adservices/ui/notifications/ConsentNotificationTriggerTest.java73
-rw-r--r--adservices/apk/tests/src/com/android/adservices/ui/notifications/NotificationActivityTest.java112
-rw-r--r--adservices/apk/tests/src/com/android/adservices/ui/notifications/NotificationActivityUiAutomatorTest.java170
-rw-r--r--adservices/apk/tests/src/com/android/adservices/ui/settings/AdServicesSettingsActivityWrapper.java34
-rw-r--r--adservices/apk/tests/src/com/android/adservices/ui/settings/SettingsActivityTest.java423
-rw-r--r--adservices/apk/tests/src/com/android/adservices/ui/settings/SettingsActivityUiAutomatorTest.java471
-rw-r--r--adservices/apk/unittest/src/com/android/adservices/adselection/AdSelectionServiceTest.java68
-rw-r--r--adservices/apk/unittest/src/com/android/adservices/customaudience/CustomAudienceServiceTest.java63
-rw-r--r--adservices/apk/unittest/src/com/android/adservices/measurement/MeasurementServiceTest.java38
-rw-r--r--adservices/apk/unittest/src/com/android/adservices/topics/TopicsServiceTest.java26
-rw-r--r--adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/MainViewModelTest.java9
-rw-r--r--adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/TopicsViewModelTest.java8
-rw-r--r--adservices/clients/java/android/adservices/clients/adselection/AdSelectionClient.java7
-rw-r--r--adservices/clients/java/android/adservices/clients/topics/AdvertisingTopicsClient.java40
-rw-r--r--adservices/framework/api/current.txt10
-rw-r--r--adservices/framework/java/android/adservices/AdServicesVersion.java2
-rw-r--r--adservices/framework/java/android/adservices/adselection/AdSelectionManager.java5
-rw-r--r--adservices/framework/java/android/adservices/adselection/AdSelectionService.aidl5
-rw-r--r--adservices/framework/java/android/adservices/common/AdServicesStatusUtils.java11
-rw-r--r--adservices/framework/java/android/adservices/measurement/RegistrationRequest.java27
-rw-r--r--adservices/framework/java/android/adservices/topics/GetTopicsParam.java27
-rw-r--r--adservices/framework/java/android/adservices/topics/GetTopicsRequest.java86
-rw-r--r--adservices/framework/java/android/adservices/topics/TopicsManager.java7
-rw-r--r--adservices/framework/java/com/android/adservices/AndroidServiceBinder.java45
-rw-r--r--adservices/samples/topics/sampleapp1/java/com/example/adservices/samples/topics/sampleapp1/MainActivity.java144
-rw-r--r--adservices/samples/topics/sampleapp1/res/layout/activity_main.xml20
-rw-r--r--adservices/samples/topics/sampleapp1/res/values/strings.xml3
-rw-r--r--adservices/service-core/Android.bp17
-rw-r--r--adservices/service-core/EmptyManifest.xml20
-rw-r--r--adservices/service-core/java/com/android/adservices/concurrency/AdServicesExecutors.java62
-rw-r--r--adservices/service-core/java/com/android/adservices/data/DbHelper.java67
-rw-r--r--adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionDatabase.java4
-rw-r--r--adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionEntryDao.java14
-rw-r--r--adservices/service-core/java/com/android/adservices/data/consent/AppConsentDao.java23
-rw-r--r--adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceDao.java192
-rw-r--r--adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceStats.java124
-rw-r--r--adservices/service-core/java/com/android/adservices/data/enrollment/EnrollmentDao.java120
-rw-r--r--adservices/service-core/java/com/android/adservices/data/enrollment/IEnrollmentDao.java13
-rw-r--r--adservices/service-core/java/com/android/adservices/data/enrollment/SqliteObjectMapper.java35
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/DatastoreManager.java2
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/IMeasurementDao.java251
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/MeasurementDao.java1071
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/MeasurementTables.java202
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/SQLDatastoreManager.java18
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/SqliteObjectMapper.java122
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/deletion/MeasurementDataDeleter.java160
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/migration/MeasurementDbMigratorV3.java220
-rw-r--r--adservices/service-core/java/com/android/adservices/data/measurement/migration/MeasurementTablesDeprecated.java33
-rw-r--r--adservices/service-core/java/com/android/adservices/data/topics/TopicsDao.java237
-rw-r--r--adservices/service-core/java/com/android/adservices/data/topics/TopicsTables.java95
-rw-r--r--adservices/service-core/java/com/android/adservices/data/topics/migration/AbstractTopicsDbMigrator.java79
-rw-r--r--adservices/service-core/java/com/android/adservices/data/topics/migration/ITopicsDbMigrator.java36
-rw-r--r--adservices/service-core/java/com/android/adservices/data/topics/migration/TopicDbMigratorV3.java46
-rw-r--r--adservices/service-core/java/com/android/adservices/service/AdServicesConfig.java30
-rw-r--r--adservices/service-core/java/com/android/adservices/service/Flags.java177
-rw-r--r--adservices/service-core/java/com/android/adservices/service/FlagsFactory.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/MaintenanceJobService.java81
-rw-r--r--adservices/service-core/java/com/android/adservices/service/PhFlags.java271
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adid/AdIdServiceImpl.java27
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/AdBidGenerator.java5
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/AdBidGeneratorImpl.java91
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionRunner.java359
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionScriptEngine.java14
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionServiceImpl.java106
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/AdsScoreGeneratorImpl.java8
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/CustomAudienceReportingSignalsArgument.java49
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/ImpressionReporter.java86
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/JsFetcher.java96
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/OnDeviceAdSelectionRunner.java331
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/PerBuyerBiddingRunner.java139
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/ReportImpressionScriptEngine.java8
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/TrustedServerAdSelectionRunner.java405
-rw-r--r--adservices/service-core/java/com/android/adservices/service/appsetid/AppSetIdServiceImpl.java27
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/AdServicesCommonServiceImpl.java5
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/AllowLists.java31
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/AppImportanceFilter.java18
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java13
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/ConsentNotificationJobService.java3
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/FledgeAllowListsFilter.java5
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/FledgeAuthorizationFilter.java26
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java64
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/PackageChangedReceiver.java24
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/PermissionHelper.java11
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/Throttler.java8
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java81
-rw-r--r--adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchJobService.java4
-rw-r--r--adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchRunner.java47
-rw-r--r--adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchWorker.java11
-rw-r--r--adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java11
-rw-r--r--adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceServiceImpl.java27
-rw-r--r--adservices/service-core/java/com/android/adservices/service/devapi/AdSelectionOverrider.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/devapi/CustomAudienceOverrider.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/enrollment/EnrollmentData.java33
-rw-r--r--adservices/service-core/java/com/android/adservices/service/js/JSSandboxIsNotAvailableException.java33
-rw-r--r--adservices/service-core/java/com/android/adservices/service/js/JSScriptEngine.java59
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistration.java352
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueJobService.java127
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java687
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/Attribution.java36
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/DeleteExpiredJobService.java13
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/DeleteUninstalledJobService.java122
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java137
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/EventTrigger.java26
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/FilterData.java (renamed from adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateFilterData.java)32
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/MeasurementHttpClient.java3
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/MeasurementImpl.java528
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/PrivacyParams.java42
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/Source.java109
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/SystemHealthParams.java24
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/Trigger.java78
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolver.java13
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/access/UserConsentAccessResolver.java2
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatableAttributionSource.java18
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManager.java15
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatePayloadGenerator.java65
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateReport.java79
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateTriggerData.java14
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java130
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobService.java23
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncSourceFetcher.java (renamed from adservices/service-core/java/com/android/adservices/service/measurement/registration/SourceFetcher.java)401
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncTriggerFetcher.java (renamed from adservices/service-core/java/com/android/adservices/service/measurement/registration/TriggerFetcher.java)299
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/EnqueueAsyncRegistration.java231
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/FetcherUtil.java56
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/SourceRegistration.java327
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/TriggerRegistration.java200
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobService.java47
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportBody.java24
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportSender.java27
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandler.java51
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobService.java19
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobService.java136
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobService.java17
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportPayload.java53
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportSender.java24
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobHandler.java53
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobService.java18
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/util/AsyncFetchStatus.java49
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/util/AsyncRedirect.java59
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/util/Enrollment.java5
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/util/Filter.java23
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/util/UnsignedLong.java89
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/util/Web.java15
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/AdServicesLogger.java55
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/AdServicesLoggerImpl.java82
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/ApiServiceLatencyCalculator.java107
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/BackgroundFetchProcessReportedStats.java50
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/EpochComputationClassifierStats.java76
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/EpochComputationGetTopTopicsStats.java61
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/GetTopicsReportedStats.java53
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/RunAdBiddingPerCAProcessReportedStats.java104
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/RunAdBiddingProcessReportedStats.java88
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/RunAdScoringProcessReportedStats.java109
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/RunAdSelectionProcessReportedStats.java64
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/StatsdAdServicesLogger.java162
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/UpdateCustomAudienceProcessReportedStats.java54
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java464
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/CacheManager.java73
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/EpochJobService.java5
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/EpochManager.java104
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/TopicsServiceImpl.java19
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/TopicsWorker.java52
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierManager.java7
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/classifier/CommonClassifierHelper.java26
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/classifier/ModelManager.java45
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/classifier/OnDeviceClassifier.java67
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/classifier/PrecomputedClassifier.java43
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/classifier/Preprocessor.java21
-rw-r--r--adservices/service-core/proto/Android.bp94
-rw-r--r--adservices/service-core/proto/seller_frontend_service.proto245
-rw-r--r--adservices/service-core/schemas/com.android.adservices.data.adselection.AdSelectionDatabase/1.json180
-rw-r--r--adservices/service/java/com/android/server/adservices/AdServicesManagerService.java104
-rw-r--r--adservices/service/java/com/android/server/adservices/Flags.java35
-rw-r--r--adservices/service/java/com/android/server/adservices/FlagsFactory.java30
-rw-r--r--adservices/service/java/com/android/server/adservices/PhFlags.java50
-rw-r--r--adservices/tests/cts/Android.bp2
-rw-r--r--adservices/tests/cts/AndroidManifest.xml1
-rw-r--r--adservices/tests/cts/AndroidTest.xml26
-rw-r--r--adservices/tests/cts/AndroidTestDebuggable.xml23
-rw-r--r--adservices/tests/cts/adid/Android.bp1
-rw-r--r--adservices/tests/cts/adid/AndroidTest.xml10
-rw-r--r--adservices/tests/cts/adid/src/com/android/adservices/tests/adid/AdIdManagerTest.java25
-rw-r--r--adservices/tests/cts/appsetid/Android.bp1
-rw-r--r--adservices/tests/cts/appsetid/AndroidTest.xml10
-rw-r--r--adservices/tests/cts/appsetid/src/com/android/adservices/tests/appsetid/AppSetIdManagerTest.java26
-rw-r--r--adservices/tests/cts/endtoends/measurement/Android.bp1
-rw-r--r--adservices/tests/cts/endtoends/measurement/AndroidTest.xml10
-rw-r--r--adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerCtsTest.java31
-rw-r--r--adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerSandboxCtsTest.java39
-rw-r--r--adservices/tests/cts/endtoends/permissions/appoptout/AndroidTest.xml67
-rw-r--r--adservices/tests/cts/endtoends/permissions/appoptout/src/com/android/adservices/tests/permissions/PermissionsAppOptOutTest.java73
-rw-r--r--adservices/tests/cts/endtoends/permissions/noperm/AndroidTest.xml63
-rw-r--r--adservices/tests/cts/endtoends/permissions/noperm/src/com/android/adservices/tests/permissions/PermissionsNoPermTest.java27
-rw-r--r--adservices/tests/cts/endtoends/permissions/notallowed/AndroidTest.xml19
-rw-r--r--adservices/tests/cts/endtoends/permissions/notallowed/src/com/android/adservices/tests/permissions/NotInAllowListTest.java21
-rw-r--r--adservices/tests/cts/endtoends/permissions/valid/AndroidTest.xml68
-rw-r--r--adservices/tests/cts/endtoends/permissions/valid/src/com/android/adservices/tests/permissions/PermissionsValidTest.java168
-rw-r--r--adservices/tests/cts/endtoends/topics/Android.bp5
-rw-r--r--adservices/tests/cts/endtoends/topics/AndroidTest.xml73
-rw-r--r--adservices/tests/cts/endtoends/topics/appupdate/Android.bp51
-rw-r--r--adservices/tests/cts/endtoends/topics/appupdate/AndroidManifest.xml30
-rw-r--r--adservices/tests/cts/endtoends/topics/appupdate/AndroidTest.xml85
-rw-r--r--adservices/tests/cts/endtoends/topics/appupdate/res/xml/ad_services_config.xml21
-rw-r--r--adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java (renamed from adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/AppUpdateTest.java)62
-rw-r--r--adservices/tests/cts/endtoends/topics/connection/Android.bp47
-rw-r--r--adservices/tests/cts/endtoends/topics/connection/AndroidManifest.xml30
-rw-r--r--adservices/tests/cts/endtoends/topics/connection/AndroidTest.xml62
-rw-r--r--adservices/tests/cts/endtoends/topics/connection/res/xml/ad_services_config.xml21
-rw-r--r--adservices/tests/cts/endtoends/topics/connection/src/com/android/adservices/tests/cts/topics/connection/TopicsConnectionTest.java207
-rw-r--r--adservices/tests/cts/endtoends/topics/mdd/Android.bp44
-rw-r--r--adservices/tests/cts/endtoends/topics/mdd/AndroidManifest.xml30
-rw-r--r--adservices/tests/cts/endtoends/topics/mdd/AndroidTest.xml59
-rw-r--r--adservices/tests/cts/endtoends/topics/mdd/res/xml/ad_services_config.xml19
-rw-r--r--adservices/tests/cts/endtoends/topics/mdd/src/com/android/adservices/tests/cts/topics/mdd/TopicsManagerMddTest.java284
-rw-r--r--adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/PreviewApiTest.java198
-rw-r--r--adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/TopicsManagerTest.java123
-rw-r--r--adservices/tests/cts/hosttests/AndroidTest.xml8
-rw-r--r--adservices/tests/cts/hosttests/app/Android.bp1
-rw-r--r--adservices/tests/cts/hosttests/src/com/android/adservices/cts/TopicsApiLoggingHostTest.java15
-rw-r--r--adservices/tests/cts/sandbox/adid/Android.bp46
-rw-r--r--adservices/tests/cts/sandbox/adid/AndroidManifest.xml48
-rw-r--r--adservices/tests/cts/sandbox/adid/AndroidTest.xml50
-rw-r--r--adservices/tests/cts/sandbox/adid/providers/adidsdk/Android.bp37
-rw-r--r--adservices/tests/cts/sandbox/adid/providers/adidsdk/AndroidManifest.xml28
-rw-r--r--adservices/tests/cts/sandbox/adid/providers/adidsdk/src/com/android/tests/providers/adidsdk/AdIdSdk.java86
-rw-r--r--adservices/tests/cts/sandbox/adid/res/xml/ad_services_config.xml22
-rw-r--r--adservices/tests/cts/sandbox/adid/src/com/android/tests/sandbox/adid/SandboxedAdIdManagerTest.java155
-rw-r--r--adservices/tests/cts/sandbox/adid/src/com/android/tests/sandbox/adid/SimpleActivity.java166
-rw-r--r--adservices/tests/cts/sandbox/appsetid/Android.bp46
-rw-r--r--adservices/tests/cts/sandbox/appsetid/AndroidManifest.xml (renamed from sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_WebViewClient.xml)29
-rw-r--r--adservices/tests/cts/sandbox/appsetid/AndroidTest.xml50
-rw-r--r--adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/Android.bp37
-rw-r--r--adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/AndroidManifest.xml26
-rw-r--r--adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/src/com/android/tests/providers/appsetidsdk/AppSetIdSdk.java86
-rw-r--r--adservices/tests/cts/sandbox/appsetid/res/xml/ad_services_config.xml22
-rw-r--r--adservices/tests/cts/sandbox/appsetid/src/com/android/tests/sandbox/appsetid/SandboxedAppSetIdManagerTest.java159
-rw-r--r--adservices/tests/cts/sandbox/appsetid/src/com/android/tests/sandbox/appsetid/SimpleActivity.java166
-rw-r--r--adservices/tests/cts/sandbox/fledge/Android.bp2
-rw-r--r--adservices/tests/cts/sandbox/fledge/AndroidTest.xml19
-rw-r--r--adservices/tests/cts/sandbox/fledge/providers/sdkFledge/Android.bp2
-rw-r--r--adservices/tests/cts/sandbox/fledge/providers/sdkFledge/src/com/android/tests/providers/sdkfledge/SdkFledge.java21
-rw-r--r--adservices/tests/cts/sandbox/fledge/src/com/android/tests/sandbox/fledge/SandboxedFledgeManagerTest.java9
-rw-r--r--adservices/tests/cts/sandbox/measurement/Android.bp2
-rw-r--r--adservices/tests/cts/sandbox/measurement/AndroidTest.xml17
-rw-r--r--adservices/tests/cts/sandbox/measurement/src/com/android/tests/sandbox/measurement/SandboxedMeasurementManagerTest.java40
-rw-r--r--adservices/tests/cts/sandbox/topics/Android.bp2
-rw-r--r--adservices/tests/cts/sandbox/topics/AndroidTest.xml25
-rw-r--r--adservices/tests/cts/sandbox/topics/providers/sdk1/Android.bp2
-rw-r--r--adservices/tests/cts/sandbox/topics/src/com/android/tests/sandbox/topics/SandboxedTopicsManagerTest.java73
-rw-r--r--adservices/tests/cts/src/android/adservices/cts/CustomAudienceApiCtsTest.java17
-rw-r--r--adservices/tests/cts/src/android/adservices/cts/FledgeCtsTest.java277
-rw-r--r--adservices/tests/cts/src/android/adservices/cts/TestAdSelectionManagerTest.java8
-rw-r--r--adservices/tests/cts/src/android/adservices/cts/TrustedBiddingDataTest.java15
-rw-r--r--adservices/tests/cts/src/android/adservices/debuggablects/FledgeCtsDebuggableTest.java101
-rw-r--r--adservices/tests/cts/testapps/testapp1/Android.bp2
-rw-r--r--adservices/tests/cts/testapps/testapp1/java/com/android/adservices/tests/cts/topics/testapp1/MainActivity.java1
-rw-r--r--adservices/tests/endtoends/AndroidTest.xml23
-rw-r--r--adservices/tests/endtoends/src/com/android/adservices/measurement/MeasurementManagerTest.java269
-rw-r--r--adservices/tests/perf/Android.bp37
-rw-r--r--adservices/tests/perf/Android.mk50
-rw-r--r--adservices/tests/perf/src/android/adservices/test/scenario/adservices/MeasurementRegisterCalls.java184
-rw-r--r--adservices/tests/perf/src/android/adservices/test/scenario/adservices/fledge/LimitPerfTests.java609
-rw-r--r--adservices/tests/perf/src/android/adservices/test/scenario/adservices/fledge/SelectAdsLatency.java301
-rw-r--r--adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/CustomAudienceSetupRule.java172
-rw-r--r--adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerDispatcherFactory.java264
-rw-r--r--adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerRule.java289
-rw-r--r--adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerRuleFactory.java35
-rw-r--r--adservices/tests/perf/tests/Android.bp17
-rw-r--r--adservices/tests/perf/tests/Android.mk50
-rw-r--r--adservices/tests/perf/tests/AndroidManifest.xml52
-rw-r--r--adservices/tests/perf/tests/AndroidTest.xml45
-rw-r--r--adservices/tests/perf/tests/assets/adservices_test_server.crt20
-rw-r--r--adservices/tests/perf/tests/assets/adservices_test_server.p12bin0 -> 2421 bytes
-rw-r--r--adservices/tests/perf/tests/res/xml/ad_services_config.xml8
-rw-r--r--adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/MeasurementRegisterCallsMicrobenchmark.java33
-rw-r--r--adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/fledge/LimitPerfTestsMicroBenchmark.java24
-rw-r--r--adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/fledge/SelectAdsLatencyMicroBenchmark.java26
-rw-r--r--adservices/tests/unittest/fixtures/java/android/adservices/customaudience/TrustedBiddingDataFixture.java14
-rw-r--r--adservices/tests/unittest/fixtures/java/com/android/adservices/customaudience/DBTrustedBiddingDataFixture.java3
-rw-r--r--adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/AsyncRegistrationFixture.java62
-rw-r--r--adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/SourceFixture.java14
-rw-r--r--adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/TriggerFixture.java12
-rw-r--r--adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/aggregation/AggregateReportFixture.java15
-rw-r--r--adservices/tests/unittest/fixtures/java/com/android/adservices/stats/FledgeApiCallStatsMatcher.java7
-rw-r--r--adservices/tests/unittest/framework/Android.bp2
-rw-r--r--adservices/tests/unittest/framework/src/android/adservices/measurement/RegistrationRequestTest.java4
-rw-r--r--adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsParamTest.java18
-rw-r--r--adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsRequestTest.java40
-rw-r--r--adservices/tests/unittest/service-core/Android.bp3
-rw-r--r--adservices/tests/unittest/service-core/assets/attribution_service_test.json811
-rw-r--r--adservices/tests/unittest/service-core/assets/classifier/classifier_test_assets_metadata.json3
-rw-r--r--adservices/tests/unittest/service-core/assets/classifier/test_model.tflitebin0 -> 5123808 bytes
-rw-r--r--adservices/tests/unittest/service-core/assets/event_report_service_test.json36
-rw-r--r--adservices/tests/unittest/service-core/assets/measurement_app_uninstall_deletion_test.json66
-rw-r--r--adservices/tests/unittest/service-core/assets/measurement_delete_expired_test.json6
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/registrant_not_found.json10
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain.json74
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain_preserve.json118
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain.json78
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain_preserve.json128
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_nor_range.json35
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain.json98
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_exclude_rate_limit.json346
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_preserve.json134
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_but_no_range.json44
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_and_origin.json66
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_but_no_origin.json38
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_and_origin.json64
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_no_origin.json38
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_and_origin.json76
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_no_origin.json42
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_and_origin.json76
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_no_origin.json38
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/basic_use_of_redirect.json48
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/limit_num_reports_for_click.json49
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/simple_1-1_matching.json20
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_many_trigger_data.json48
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_over_contributions_limit.json64
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_three_of_three_entries.json (renamed from adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_of_three_entries.json)56
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions.json50
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api.json127
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api_source.json124
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api_trigger.json124
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api.json127
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api_source.json126
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api_trigger.json126
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/basic_use_of_redirect.json50
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_default_expiry.json48
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_provided_expiry.json48
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/event_time_based_source_selection.json35
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/expired_source.json54
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_no_cooldown.json62
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_outside_window.json31
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_reinstall.json42
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown.json62
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown_1_day_window.json183
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_vtc_with_lose_once_lose_always.json96
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_click.json57
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_view.json33
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/no_matching_sources.json18
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/only_one_source_of_multiple_matches_can_be_attributed.json50
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/priority_based_source_selection.json39
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWebSource_3_sources_3_triggers_3_reports.json56
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-1_web-web_matching.json26
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_app_and_web_matching.json37
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_web_only_matching.json35
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_redirects_ignored.json38
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/register_1-1_app-app_matching.json22
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_one_trigger.json50
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_three_triggers_one_dedup.json70
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/two_simple_matches_testing_multiplicity.json44
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_filters.json22
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_notfilters.json22
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_default_expiry.json22
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_early.json24
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_late.json22
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_provided_expiry.json22
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_top_level_filters_mismatch.json20
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/aggregatable_budget.json214
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/aggregatable_contributions_creation.json233
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/basic.json60
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/basic_aggregatable.json87
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/config_trigger_data_cardinality.json63
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/dedup_key.json112
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/event_level_trigger_filter_data.json271
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/event_source.json60
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/expired_source.json47
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/most_recent_source.json77
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/rate_limit_max_attribution_reporting_endpoints.json223
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/rate_limit_max_source_registration_reporting_origin_endpoints.json240
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/same_destination_site.json79
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/same_reporting_origin.json79
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/source_priority.json97
-rw-r--r--adservices/tests/unittest/service-core/assets/msmt_interop_tests/top_level_filter_data.json356
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/concurrency/AdServicesExecutorTest.java73
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/DbHelperTest.java146
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/DbTestUtil.java9
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/RoomSchemaMigrationGuardrailTest.java181
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/AdSelectionEntryDaoTest.java83
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/DBAdSelectionTest.java2
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/consent/AppConsentDaoTest.java82
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceDaoTest.java492
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceStatsTest.java73
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/enrollment/EnrollmentDaoTest.java249
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AbstractDbIntegrationTest.java74
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AppDeletionIntegrationTest.java8
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DbHelperV1.java (renamed from adservices/service-core/java/com/android/adservices/data/measurement/migration/DbHelperV1.java)7
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DbState.java26
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteApiIntegrationTest.java30
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredDynamicIntegrationTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredIntegrationTest.java4
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java3106
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/deletion/MeasurementDataDeleterTest.java421
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/AbstractMeasurementDbMigratorTestBase.java1
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/MeasurementDbMigratorV3Test.java143
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/TopicsDaoTest.java323
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/AbstractTopicsDbMigratorTest.java66
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/TopicDbMigratorV3Test.java67
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/TopicsDbHelperV2.java98
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/download/MobileDataDownloadTest.java60
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java277
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java375
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdServiceImplTest.java34
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdWorkerTest.java5
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdBidGeneratorImplTest.java150
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionE2ETest.java529
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionScriptEngineTest.java11
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionServiceImplTest.java933
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdsScoreGeneratorImplTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceBiddingSignalsArgumentTest.java8
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceReportingSignalsArgumentTest.java71
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceScoringSignalsArgumentTest.java14
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/JsFetcherTest.java171
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/OnDeviceAdSelectionRunnerTest.java (renamed from adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionRunnerTest.java)666
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/PerBuyerBiddingRunnerTest.java232
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java494
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdServiceImplTest.java35
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdWorkerTest.java4
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdServicesCommonServiceImplTest.java2
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AllowListsTest.java67
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/BackgroundJobsManagerTest.java78
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ConsentNotificationJobServiceTest.java8
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAllowListsFilterTest.java28
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAuthorizationFilterTest.java111
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java176
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeMaintenanceTasksWorkerTests.java172
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/common/PackageChangedReceiverTest.java14
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java237
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchJobServiceTest.java18
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchRunnerTest.java91
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchWorkerTest.java80
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceQuantityCheckerTest.java58
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java40
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceImplTest.java142
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/enrollment/EnrollmentDataTest.java108
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/js/JSScriptEngineTest.java39
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueJobServiceTest.java283
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueRunnerTest.java1787
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java25
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java68
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteUninstalledJobServiceTest.java286
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java21
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java31
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EInteropMockTest.java247
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockStatic.java84
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java284
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java506
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EnqueueAsyncRegistrationTest.java840
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java198
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java74
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/FilterDataTest.java (renamed from adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateFilterDataTest.java)32
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java28
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java1436
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java36
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java144
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java80
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java118
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java18
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java4
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/AggregateReportingJob.java44
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/EventReportingJob.java (renamed from adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/ReportingJob.java)9
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java1
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java18
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java19
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java7
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java1
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java47
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java44
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java22
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java1001
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java63
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncSourceFetcherTest.java2635
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncTriggerFetcherTest.java2797
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java54
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceFetcherTest.java1937
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceRegistrationTest.java155
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerFetcherTest.java1231
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerRegistrationTest.java108
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java89
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java67
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java14
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java90
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java84
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/DebugReportingJobServiceTest.java287
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java81
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java141
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java18
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java7
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java131
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java75
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java6
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java133
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/UnsignedLongTest.java105
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java71
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/AdServicesLoggerImplTest.java469
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/ApiServiceLatencyCalculatorTest.java101
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/BackgroundFetchProcessReportedStatsTest.java43
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdBiddingPerCAProcessReportedStatsTest.java100
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdBiddingProcessReportedStatsTest.java76
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdScoringProcessReportedStatsTest.java100
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdSelectionProcessReportedStatsTest.java55
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/UpdateCustomAudienceProcessReportedStatsTest.java46
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/AppUpdateManagerTest.java701
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/CacheManagerTest.java258
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochJobServiceTest.java22
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochManagerTest.java285
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsServiceImplTest.java267
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsWorkerTest.java473
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/CommonClassifierHelperTest.java87
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/ModelManagerTest.java178
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/OnDeviceClassifierTest.java134
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PrecomputedClassifierTest.java47
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PreprocessorTest.java115
-rw-r--r--adservices/tests/unittest/service-core/src/jni/java/com/android/adservices/HpkeJniTest.java7
-rw-r--r--adservices/tests/unittest/system-service/Android.bp5
-rw-r--r--adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java172
-rw-r--r--adservices/tests/unittest/system-service/src/com/android/server/adservices/PhFlagsTest.java55
-rw-r--r--sdksandbox/Android.bp1
-rw-r--r--sdksandbox/SdkSandbox/AndroidManifest.xml9
-rw-r--r--sdksandbox/SdkSandbox/aidl/com/android/sdksandbox/ISdkSandboxService.aidl8
-rw-r--r--sdksandbox/SdkSandbox/src/com/android/sdksandbox/SandboxedSdkHolder.java10
-rw-r--r--sdksandbox/SdkSandbox/src/com/android/sdksandbox/SdkSandboxServiceImpl.java142
-rw-r--r--sdksandbox/TEST_MAPPING12
-rw-r--r--sdksandbox/framework/api/current.txt13
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl4
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/ISdkToServiceCallback.aidl4
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/LogUtil.java48
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdk.java76
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkContext.java12
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkProvider.java1
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxController.java65
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxLocalSingleton.java92
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java30
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManagerFrameworkInitializer.java3
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java36
-rw-r--r--sdksandbox/framework/java/android/app/sdksandbox/sdkprovider/SdkSandboxController.java130
-rw-r--r--sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java736
-rw-r--r--sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxStorageManager.java251
-rw-r--r--sdksandbox/tests/cts/endtoendtests/Android.bp3
-rw-r--r--sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml15
-rw-r--r--sdksandbox/tests/cts/endtoendtests/AndroidTest.xml7
-rw-r--r--sdksandbox/tests/cts/endtoendtests/DisabledAndroidManifest.xml2
-rw-r--r--sdksandbox/tests/cts/endtoendtests/DisabledAndroidTest.xml6
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/getLoadedSdkLibInfoSuccessfully/Android.bp3
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/Android.bp31
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/AndroidManifest.xml26
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/src/com/android/loadsdkandcheckclassloadersdkprovider/SdkProvider.java58
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfully/Android.bp3
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/Android.bp3
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/loadSdkWithInternalError/Android.bp3
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageSuccessfully/Android.bp3
-rw-r--r--sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageWithInternalError/Android.bp3
-rw-r--r--sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/disablede2e/SdkSandboxManagerDisabledTest.java2
-rw-r--r--sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java63
-rw-r--r--sdksandbox/tests/cts/hostside/Android.bp116
-rw-r--r--sdksandbox/tests/cts/hostside/AndroidTest.xml61
-rw-r--r--sdksandbox/tests/cts/hostside/app/AndroidManifest.xml52
-rw-r--r--sdksandbox/tests/cts/hostside/app/AndroidManifest2.xml34
-rw-r--r--sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/SdkSandboxDataIsolationTestApp.java118
-rw-r--r--sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/SdkSandboxStorageTestApp.java166
-rw-r--r--sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/TestActivity.java21
-rw-r--r--sdksandbox/tests/cts/hostside/provider/AndroidManifest.xml25
-rw-r--r--sdksandbox/tests/cts/hostside/provider/StorageTestManifest.xml25
-rw-r--r--sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/DataIsolationTestSdkApiImpl.java68
-rw-r--r--sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/IDataIsolationTestSdkApi.aidl19
-rw-r--r--sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/SdkSandboxDataIsolationTestProvider.java36
-rw-r--r--sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/IStorageTestSdkApi.aidl19
-rw-r--r--sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/SdkSandboxStorageTestProvider.java36
-rw-r--r--sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/StorageTestSdkApiImpl.java38
-rw-r--r--sdksandbox/tests/cts/hostside/src/com/android/sdksandbox/cts/host/SdkSandboxDataIsolationHostTest.java88
-rw-r--r--sdksandbox/tests/cts/hostside/src/com/android/sdksandbox/cts/host/SdkSandboxStorageHostTest.java90
-rw-r--r--sdksandbox/tests/cts/inprocess/src/com/android/sdksandbox/tests/cts/inprocess/SdkSandboxConfigurationTest.java14
-rw-r--r--sdksandbox/tests/hostsidetests/Android.bp14
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxLifecycleHostTest.xml5
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/Android.bp4
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/AndroidTest.xml5
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxRestrictionsTestApp.java4
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxShellHostTest.xml5
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/Android.bp17
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/SdkSandboxStorageHostTest.xml5
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxStorageTestApp.java134
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/IStorageTestSdk1Api.aidl21
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSandboxedSdkProvider.java115
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSdk1ApiImpl.java90
-rw-r--r--sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/src/com/android/tests/sdksandbox/host/SdkSandboxStorageHostTest.java374
-rw-r--r--sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java12
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient/Android.bp53
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client.xml (renamed from sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client1.xml)8
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/layout/activity_main.xml23
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/values/strings.xml5
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclient/MainActivity.java318
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclienttwo/MainActivity.java144
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/Android.bp28
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/AndroidManifest_Client.xml (renamed from sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client2.xml)6
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/res/layout/activity_main.xml77
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/res/values/strings.xml26
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/src/com/android/sdksandboxclient_shareduid/MainActivity.java342
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/Android.bp40
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_MediateeProvider.xml27
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_WebViewProvider.xml2
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/apiimplementation/SdkApi.java68
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_1/SampleSandboxedSdkProvider.java75
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_mediatee/SandboxedSdkMediateeProvider.java37
-rw-r--r--sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_webview/SandboxedSdkWebViewProvider.java7
-rw-r--r--sdksandbox/tests/manual-test-apps/sdkinterfaces/Android.bp23
-rw-r--r--sdksandbox/tests/manual-test-apps/sdkinterfaces/src/android/app/sdksandbox/interfaces/ISdkApi.aidl23
-rw-r--r--sdksandbox/tests/perf/scenarios/Android.bp40
-rw-r--r--sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/LoadSdk.java (renamed from sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxControllerUnitTest.java)39
-rw-r--r--sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/RemoteRenderAd.java59
-rw-r--r--sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/SdkSandboxTestHelper.java84
-rw-r--r--sdksandbox/tests/perf/scenarios/tests/Android.bp41
-rw-r--r--sdksandbox/tests/perf/scenarios/tests/AndroidManifest.xml14
-rw-r--r--sdksandbox/tests/perf/scenarios/tests/AndroidTest.xml62
-rw-r--r--sdksandbox/tests/perf/scenarios/tests/src/android/sdksandbox/test/scenario/LoadSdkMicrobenchmark.java32
-rw-r--r--sdksandbox/tests/perf/scenarios/tests/src/android/sdksandbox/test/scenario/RemoteRenderAdMicrobenchmark.java32
-rw-r--r--sdksandbox/tests/test-apps/CodeProviderWithResources/Android.bp2
-rw-r--r--sdksandbox/tests/test-apps/EmptyProvider/Android.bp29
-rw-r--r--sdksandbox/tests/test-apps/EmptyProvider/AndroidManifest.xml26
-rw-r--r--sdksandbox/tests/test-apps/EmptyProvider/src/com/android/emptysdkprovider/EmptySdkProvider.java42
-rw-r--r--sdksandbox/tests/test-apps/FailingSdkProvider/Android.bp2
-rw-r--r--sdksandbox/tests/test-apps/TestProvider/Android.bp2
-rw-r--r--sdksandbox/tests/test-apps/code-provider-app/Android.bp2
-rw-r--r--sdksandbox/tests/test-apps/service-app/Android.bp2
-rw-r--r--sdksandbox/tests/testutils/Android.bp12
-rw-r--r--sdksandbox/tests/testutils/src/android/app/sdksandbox/hosttestutils/AdoptableStorageUtils.java135
-rw-r--r--sdksandbox/tests/testutils/src/android/app/sdksandbox/hosttestutils/SecondaryUserUtils.java84
-rw-r--r--sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeLoadSdkCallback.java33
-rw-r--r--sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeRequestSurfacePackageCallback.java56
-rw-r--r--sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeSharedPreferencesSyncCallback.java30
-rw-r--r--sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java5
-rw-r--r--sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkToServiceLink.java26
-rw-r--r--sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/WaitableCountDownLatch.java53
-rw-r--r--sdksandbox/tests/testutils/testscenario/testrule/Android.bp37
-rw-r--r--sdksandbox/tests/testutils/testscenario/testrule/AndroidManifest.xml31
-rw-r--r--sdksandbox/tests/testutils/testscenario/testrule/res/layout/surfaceview_layout.xml28
-rw-r--r--sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/KeepSdkSandboxAliveRule.java82
-rw-r--r--sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxCtsActivity.java28
-rw-r--r--sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxScenarioRule.java173
-rw-r--r--sdksandbox/tests/testutils/testscenario/testrunner/Android.bp33
-rw-r--r--sdksandbox/tests/testutils/testscenario/testrunner/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxTestScenarioRunner.java166
-rw-r--r--sdksandbox/tests/testutils/testscenario/textexecutor/Android.bp28
-rw-r--r--sdksandbox/tests/testutils/testscenario/textexecutor/src/android/app/sdksandbox/testutils/testscenario/ISdkSandboxResultCallback.aidl23
-rw-r--r--sdksandbox/tests/testutils/testscenario/textexecutor/src/android/app/sdksandbox/testutils/testscenario/ISdkSandboxTestExecutor.aidl26
-rw-r--r--sdksandbox/tests/unittest/Android.bp4
-rw-r--r--sdksandbox/tests/unittest/SdkSandboxFrameworkTest.xml5
-rw-r--r--sdksandbox/tests/unittest/SdkSandboxManagerServiceTest.xml5
-rw-r--r--sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkContextUnitTest.java10
-rw-r--r--sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkUnitTest.java118
-rw-r--r--sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxLocalSingletonUnitTest.java96
-rw-r--r--sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxManagerUnitTest.java14
-rw-r--r--sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java30
-rw-r--r--sdksandbox/tests/unittest/src/android/app/sdksandbox/sdkprovider/SdkSandboxControllerUnitTest.java155
-rw-r--r--sdksandbox/tests/unittest/src/com/android/sdksandbox/SdkSandboxTest.java131
-rw-r--r--sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java491
-rw-r--r--sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxStorageManagerUnitTest.java339
806 files changed, 59506 insertions, 17403 deletions
diff --git a/adservices/TEST_MAPPING b/adservices/TEST_MAPPING
index 70629b3d4..9266c15ce 100644
--- a/adservices/TEST_MAPPING
+++ b/adservices/TEST_MAPPING
@@ -45,6 +45,22 @@
"name": "CtsAdServicesPermissionsNoPermEndToEndTests[com.google.android.adservices.apex]"
},
{
+ "name": "CtsAdServicesNotInAllowListEndToEndTests[com.google.android.adservices.apex]"
+ },
+ {
+ "name": "CtsAdServicesTopicsAppUpdateTests[com.google.android.adservices.apex]"
+ },
+ {
+ "name": "CtsAdServicesTopicsConnectionTests[com.google.android.adservices.apex]"
+ },
+ {
+ "name": "CtsAdServicesMddTests[com.google.android.adservices.apex]"
+ },
+ {
+ // Install com.google.android.adservices.apex and run CtsSandboxedAdIdManagerTests.
+ "name": "CtsSandboxedAdIdManagerTests[com.google.android.adservices.apex]"
+ },
+ {
// Install com.google.android.adservices.apex and run CtsSandboxedTopicsManagerTests.
"name": "CtsSandboxedTopicsManagerTests[com.google.android.adservices.apex]"
},
@@ -79,6 +95,15 @@
"name": "AdServicesEndToEndTests"
},
{
+ "name": "CtsAdServicesTopicsAppUpdateTests"
+ },
+ {
+ "name": "CtsAdServicesTopicsConnectionTests"
+ },
+ {
+ "name": "CtsAdServicesMddTests"
+ },
+ {
"name": "CtsAdServicesDeviceTestCases"
},
{
diff --git a/adservices/apk/AndroidManifest.xml b/adservices/apk/AndroidManifest.xml
index e19f8708b..93ce7502f 100644
--- a/adservices/apk/AndroidManifest.xml
+++ b/adservices/apk/AndroidManifest.xml
@@ -103,6 +103,72 @@
android:allowBackup="false"
android:supportsRtl="true"
android:theme="@style/FilterTouches">
+ <!-- Activity for the main view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.adservices.ui.SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for the topics view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.TopicsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.adservices.ui.TOPICS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for the blocked topics view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.BlockedTopicsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.adservices.ui.BLOCKED_TOPICS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for the apps view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.AppsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.adservices.ui.APPS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for the blocked apps view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.BlockedAppsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.adservices.ui.BLOCKED_APPS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for Adservices Detailed Notification UI -->
+ <activity
+ android:name="com.android.adservices.ui.notifications.ConsentNotificationActivity"
+ android:exported="true"
+ android:theme="@style/AdServices.NotificationTheme">
+ <intent-filter android:priority="1">
+ <action android:name="android.adservices.ui.NOTIFICATIONS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<service android:name="com.android.adservices.adselection.AdSelectionService"
android:exported="true"
android:visibleToInstantApps="false">
@@ -182,33 +248,16 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
- <!-- Activity for Adservices Settings UI -->
- <activity
- android:name="com.android.adservices.ui.settings.AdServicesSettingsActivity"
- android:exported="true"
- android:theme="@style/Theme.AdServices.Settings">
- <intent-filter android:priority="1">
- <action android:name="android.adservices.ui.SETTINGS" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
- <!-- Activity for Adservices Detailed Notification UI -->
- <activity
- android:name="com.android.adservices.ui.notifications.ConsentNotificationActivity"
- android:exported="true"
- android:theme="@style/AdServices.NotificationTheme">
- <intent-filter android:priority="1">
- <action android:name="android.adservices.ui.NOTIFICATIONS" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
<!-- Measurement event main report upload job. -->
<service android:name="com.android.adservices.service.measurement.reporting.EventReportingJobService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <!-- Measurement debug report upload job. -->
+ <service android:name="com.android.adservices.service.measurement.reporting.DebugReportingJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<!-- Measurement event fallback report upload job. -->
<service android:name=
"com.android.adservices.service.measurement.reporting.EventFallbackReportingJobService"
@@ -239,14 +288,27 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <!-- Measurement Deletion Uninstalled Apps Job. -->
+ <service android:name=
+ "com.android.adservices.service.measurement.DeleteUninstalledJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<!-- FLEDGE Background Fetch Job -->
<service android:name=
"com.android.adservices.service.customaudience.BackgroundFetchJobService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <!-- Registration Queue job. -->
+ <service android:name=
+ "com.android.adservices.service.measurement.AsyncRegistrationQueueJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<receiver android:name="com.android.adservices.service.common.PackageChangedReceiver"
- android:exported="true">
+ android:enabled= "false"
+ android:exported="true">
<intent-filter>
<!-- This intent must match the intent definition in the AdServices system
service. -->
diff --git a/adservices/apk/assets/classifier/classifier_assets_metadata.json b/adservices/apk/assets/classifier/classifier_assets_metadata.json
index a169a160b..fd2a7a3e0 100644
--- a/adservices/apk/assets/classifier/classifier_assets_metadata.json
+++ b/adservices/apk/assets/classifier/classifier_assets_metadata.json
@@ -3,6 +3,7 @@
"property": "version_info",
"taxonomy_type": "chrome_and_mobile_taxonomy",
"taxonomy_version": "2",
+ "build_id" : "2",
"updated_date": "2022-08-10"
},
{
@@ -16,8 +17,8 @@
"asset_name": "precomputed_app_list",
"asset_version": "2",
"path": "assets/classifier/precomputed_app_list.csv",
- "checksum": "780c4edecfe703bdddd57bd84ea7860bba2577f66f2d759558cfe9be3186bece",
- "updated_date": "2022-08-24"
+ "checksum": "8749598423bb8baca59e0da508739d544e40f230e7edcdb92438e9e76f75e830",
+ "updated_date": "2022-10-10"
},
{
"asset_name": "topic_id_to_name",
diff --git a/adservices/apk/assets/classifier/precomputed_app_list.csv b/adservices/apk/assets/classifier/precomputed_app_list.csv
index 718b4bf5a..385bd96e6 100644
--- a/adservices/apk/assets/classifier/precomputed_app_list.csv
+++ b/adservices/apk/assets/classifier/precomputed_app_list.csv
@@ -10011,5 +10011,8 @@ com.example.adservices.samples.topics.sampleapp8 10432,10132,10386,10344,10440
com.example.adservices.samples.topics.sampleapp9 10123,10163,10249,10246,10422
com.example.adservices.samples.topics.sampleapp10 10192,10136,10389,10332,10412
com.android.adservices.tests.cts.endtoendtest 10147,10253,10175,10254,10333
+com.android.adservices.tests.cts.topics.connection 10147,10253,10175,10254,10333
+com.android.adservices.tests.cts.topics.appupdate 10147,10253,10175,10254,10333
com.android.adservices.tests.cts.topics.testapp1 10147,10253,10175,10254,10333
-com.android.tests.sandbox.topics 10147,10253,10175,10254,10333 \ No newline at end of file
+com.android.tests.sandbox.topics 10147,10253,10175,10254,10333
+android.platform.test.scenario 10147,10253,10175,10254,10333 \ No newline at end of file
diff --git a/adservices/apk/assets/classifier/stopwords.txt b/adservices/apk/assets/classifier/stopwords.txt
index 49dd96e6c..526190123 100644
--- a/adservices/apk/assets/classifier/stopwords.txt
+++ b/adservices/apk/assets/classifier/stopwords.txt
@@ -1,127 +1 @@
-i
-me
-my
-myself
-we
-our
-ours
-ourselves
-you
-your
-yours
-yourself
-yourselves
-he
-him
-his
-himself
-she
-her
-hers
-herself
-it
-its
-itself
-they
-them
-their
-theirs
-themselves
-what
-which
-who
-whom
-this
-that
-these
-those
-am
-is
-are
-was
-were
-be
-been
-being
-have
-has
-had
-having
-do
-does
-did
-doing
-a
-an
-the
-and
-but
-if
-or
-because
-as
-until
-while
-of
-at
-by
-for
-with
-about
-against
-between
-into
-through
-during
-before
-after
-above
-below
-to
-from
-up
-down
-in
-out
-on
-off
-over
-under
-again
-further
-then
-once
-here
-there
-when
-where
-why
-how
-all
-any
-both
-each
-few
-more
-most
-other
-some
-such
-no
-nor
-not
-only
-own
-same
-so
-than
-too
-very
-s
-t
-can
-will
-just
-don
-should
-now \ No newline at end of file
+CustomStopWord1 \ No newline at end of file
diff --git a/adservices/apk/build.gradle b/adservices/apk/build.gradle
index 78cffbd11..78249e093 100644
--- a/adservices/apk/build.gradle
+++ b/adservices/apk/build.gradle
@@ -44,6 +44,7 @@ android {
java.srcDirs = [
'java',
'../service-core/java',
+ '../service/java',
'../framework/java',
'../../../../../external/guava/guava/src',
'../../../../../frameworks/base/packages/SettingsLib/' +
@@ -54,6 +55,8 @@ android {
'../../../../../frameworks/base/packages/SettingsLib/' +
'MainSwitchPreference/src',
'../../../../../out/soong/.intermediates/packages/modules/AdServices/adservices/service-core/statslog-adservices-java-gen/gen',
+ '../../../../../external/dexmaker/dexmaker-mockito-inline-extended/src/main/java',
+ '../../../../../external/mockito/src/main/java',
]
java.setIncludes(new HashSet([
'com/android/adservices/ui/**/*.java',
@@ -63,6 +66,8 @@ android {
'android/os/*.java',
'com/android/settingslib/collapsingtoolbar/**/*.java',
'com/android/adservices/service/stats/*.java',
+ 'com/android/dx/mockito/**/*.java',
+ 'org/mockito/**/*.java',
]))
res.srcDirs = [
'res',
@@ -83,9 +88,11 @@ android {
java.srcDirs = [
'tests/src',
'unittest/src',
+// '../../../../../external/dexmaker-mockito-inline-extended/src/main/java',
]
java.setIncludes(new HashSet([
'com/android/adservices/ui/**/*.java',
+// 'com/android/dx/mockito/**/*.java',
]))
}
}
diff --git a/adservices/apk/java/com/android/adservices/adid/AdIdService.java b/adservices/apk/java/com/android/adservices/adid/AdIdService.java
index 06dc01899..4c66d3b74 100644
--- a/adservices/apk/java/com/android/adservices/adid/AdIdService.java
+++ b/adservices/apk/java/com/android/adservices/adid/AdIdService.java
@@ -26,6 +26,7 @@ import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.adid.AdIdServiceImpl;
import com.android.adservices.service.adid.AdIdWorker;
import com.android.adservices.service.common.AppImportanceFilter;
+import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.stats.Clock;
@@ -62,6 +63,8 @@ public class AdIdService extends Service {
AdServicesLoggerImpl.getInstance(),
Clock.SYSTEM_CLOCK,
FlagsFactory.getFlags(),
+ Throttler.getInstance(
+ FlagsFactory.getFlags().getSdkRequestPermitsPerSecond()),
appImportanceFilter);
}
}
diff --git a/adservices/apk/java/com/android/adservices/adselection/AdSelectionService.java b/adservices/apk/java/com/android/adservices/adselection/AdSelectionService.java
index 077657616..12e0441b4 100644
--- a/adservices/apk/java/com/android/adservices/adselection/AdSelectionService.java
+++ b/adservices/apk/java/com/android/adservices/adselection/AdSelectionService.java
@@ -21,9 +21,13 @@ import android.content.Intent;
import android.os.IBinder;
import com.android.adservices.LogUtil;
+import com.android.adservices.download.MddJobService;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.MaintenanceJobService;
import com.android.adservices.service.adselection.AdSelectionServiceImpl;
+import com.android.adservices.service.common.PackageChangedReceiver;
+import com.android.adservices.service.consent.ConsentManager;
import com.google.common.annotations.VisibleForTesting;
@@ -53,9 +57,16 @@ public class AdSelectionService extends Service {
LogUtil.e("Select Ads API is disabled");
return;
}
+
if (mAdSelectionService == null) {
mAdSelectionService = AdSelectionServiceImpl.create(this);
}
+
+ if (hasUserConsent()) {
+ PackageChangedReceiver.enableReceiver(this);
+ MddJobService.scheduleIfNeeded(this, /* forceSchedule */ false);
+ MaintenanceJobService.scheduleIfNeeded(this, /* forceSchedule */ false);
+ }
}
@Override
@@ -74,4 +85,9 @@ public class AdSelectionService extends Service {
mAdSelectionService.destroy();
}
}
+
+ /** @return {@code true} if the Privacy Sandbox has user consent */
+ private boolean hasUserConsent() {
+ return ConsentManager.getInstance(this).getConsent().isGiven();
+ }
}
diff --git a/adservices/apk/java/com/android/adservices/appsetid/AppSetIdService.java b/adservices/apk/java/com/android/adservices/appsetid/AppSetIdService.java
index 7284058dc..1795a4726 100644
--- a/adservices/apk/java/com/android/adservices/appsetid/AppSetIdService.java
+++ b/adservices/apk/java/com/android/adservices/appsetid/AppSetIdService.java
@@ -26,6 +26,7 @@ import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.appsetid.AppSetIdServiceImpl;
import com.android.adservices.service.appsetid.AppSetIdWorker;
import com.android.adservices.service.common.AppImportanceFilter;
+import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.stats.Clock;
@@ -62,6 +63,8 @@ public class AppSetIdService extends Service {
AdServicesLoggerImpl.getInstance(),
Clock.SYSTEM_CLOCK,
FlagsFactory.getFlags(),
+ Throttler.getInstance(
+ FlagsFactory.getFlags().getSdkRequestPermitsPerSecond()),
appImportanceFilter);
}
}
diff --git a/adservices/apk/java/com/android/adservices/customaudience/CustomAudienceService.java b/adservices/apk/java/com/android/adservices/customaudience/CustomAudienceService.java
index 4951c47dc..1aefd24b8 100644
--- a/adservices/apk/java/com/android/adservices/customaudience/CustomAudienceService.java
+++ b/adservices/apk/java/com/android/adservices/customaudience/CustomAudienceService.java
@@ -21,8 +21,11 @@ import android.content.Intent;
import android.os.IBinder;
import com.android.adservices.LogUtil;
+import com.android.adservices.download.MddJobService;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.PackageChangedReceiver;
+import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.customaudience.CustomAudienceServiceImpl;
import com.google.common.annotations.VisibleForTesting;
@@ -53,9 +56,15 @@ public class CustomAudienceService extends Service {
LogUtil.e("Custom Audience API is disabled");
return;
}
+
if (mCustomAudienceService == null) {
mCustomAudienceService = CustomAudienceServiceImpl.create(this);
}
+
+ if (hasUserConsent()) {
+ PackageChangedReceiver.enableReceiver(this);
+ MddJobService.scheduleIfNeeded(this, /* forceSchedule */ false);
+ }
}
@Override
@@ -67,4 +76,9 @@ public class CustomAudienceService extends Service {
}
return Objects.requireNonNull(mCustomAudienceService);
}
+
+ /** @return {@code true} if the Privacy Sandbox has user consent */
+ private boolean hasUserConsent() {
+ return ConsentManager.getInstance(this).getConsent().isGiven();
+ }
}
diff --git a/adservices/apk/java/com/android/adservices/measurement/MeasurementService.java b/adservices/apk/java/com/android/adservices/measurement/MeasurementService.java
index 2ccf1ad51..335a736cd 100644
--- a/adservices/apk/java/com/android/adservices/measurement/MeasurementService.java
+++ b/adservices/apk/java/com/android/adservices/measurement/MeasurementService.java
@@ -23,11 +23,15 @@ import android.os.IBinder;
import com.android.adservices.LogUtil;
import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.download.MddJobService;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.AppImportanceFilter;
+import com.android.adservices.service.common.PackageChangedReceiver;
import com.android.adservices.service.consent.ConsentManager;
+import com.android.adservices.service.measurement.AsyncRegistrationQueueJobService;
import com.android.adservices.service.measurement.DeleteExpiredJobService;
+import com.android.adservices.service.measurement.DeleteUninstalledJobService;
import com.android.adservices.service.measurement.MeasurementServiceImpl;
import com.android.adservices.service.measurement.attribution.AttributionJobService;
import com.android.adservices.service.measurement.reporting.AggregateFallbackReportingJobService;
@@ -71,6 +75,7 @@ public class MeasurementService extends Service {
}
if (hasUserConsent()) {
+ PackageChangedReceiver.enableReceiver(this);
schedulePeriodicJobsIfNeeded();
}
}
@@ -86,7 +91,7 @@ public class MeasurementService extends Service {
}
private boolean hasUserConsent() {
- return ConsentManager.getInstance(this).getConsent(this.getPackageManager()).isGiven();
+ return ConsentManager.getInstance(this).getConsent().isGiven();
}
private void schedulePeriodicJobsIfNeeded() {
@@ -96,5 +101,8 @@ public class MeasurementService extends Service {
EventReportingJobService.scheduleIfNeeded(this, false);
EventFallbackReportingJobService.scheduleIfNeeded(this, false);
DeleteExpiredJobService.scheduleIfNeeded(this, false);
+ DeleteUninstalledJobService.scheduleIfNeeded(this, false);
+ MddJobService.scheduleIfNeeded(this, false);
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(this, false);
}
}
diff --git a/adservices/apk/java/com/android/adservices/topics/TopicsService.java b/adservices/apk/java/com/android/adservices/topics/TopicsService.java
index e7de2cdd3..5f2fcf017 100644
--- a/adservices/apk/java/com/android/adservices/topics/TopicsService.java
+++ b/adservices/apk/java/com/android/adservices/topics/TopicsService.java
@@ -29,6 +29,7 @@ import com.android.adservices.download.MobileDataDownloadFactory;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.MaintenanceJobService;
import com.android.adservices.service.common.AppImportanceFilter;
+import com.android.adservices.service.common.PackageChangedReceiver;
import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
@@ -54,7 +55,7 @@ public class TopicsService extends Service {
super.onCreate();
if (FlagsFactory.getFlags().getTopicsKillSwitch()) {
- LogUtil.e("Topics API is disabled");
+ LogUtil.e("onCreate(): Topics API is disabled");
return;
}
@@ -80,6 +81,7 @@ public class TopicsService extends Service {
mTopicsService.init();
}
if (hasUserConsent()) {
+ PackageChangedReceiver.enableReceiver(this);
schedulePeriodicJobs();
}
}
@@ -91,13 +93,13 @@ public class TopicsService extends Service {
}
private boolean hasUserConsent() {
- return ConsentManager.getInstance(this).getConsent(this.getPackageManager()).isGiven();
+ return ConsentManager.getInstance(this).getConsent().isGiven();
}
@Override
public IBinder onBind(Intent intent) {
if (FlagsFactory.getFlags().getTopicsKillSwitch()) {
- LogUtil.e("Topics API is disabled");
+ LogUtil.e("onBind(): Topics API is disabled, return nullBinding.");
// Return null so that clients can not bind to the service.
return null;
}
diff --git a/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationConfirmationFragment.java b/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationConfirmationFragment.java
index 375de35d9..5ef064e94 100644
--- a/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationConfirmationFragment.java
+++ b/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationConfirmationFragment.java
@@ -15,6 +15,8 @@
*/
package com.android.adservices.ui.notifications;
+import static com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity.FROM_NOTIFICATION_KEY;
+
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -26,7 +28,7 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.android.adservices.api.R;
-import com.android.adservices.ui.settings.AdServicesSettingsActivity;
+import com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity;
/**
* Fragment for the confirmation view after accepting or rejecting to be part of Privacy Sandbox
@@ -58,10 +60,11 @@ public class ConsentNotificationConfirmationFragment extends Fragment {
leftControlButton.setOnClickListener(
view -> {
// go to settings activity
- Intent intent = new Intent(requireActivity(), AdServicesSettingsActivity.class);
- intent.addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ Intent intent =
+ new Intent(requireActivity(), AdServicesSettingsMainActivity.class);
+ intent.putExtra(FROM_NOTIFICATION_KEY, true);
startActivity(intent);
+ requireActivity().finish();
});
Button rightControlButton =
diff --git a/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationFragment.java b/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationFragment.java
index e943afdb5..be43dc5da 100644
--- a/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationFragment.java
+++ b/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationFragment.java
@@ -20,27 +20,34 @@ import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICE
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
import static com.android.adservices.ui.notifications.ConsentNotificationConfirmationFragment.IS_CONSENT_GIVEN_ARGUMENT_KEY;
+import static com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity.FROM_NOTIFICATION_KEY;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnScrollChangeListener;
import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.android.adservices.api.R;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.stats.UIStats;
-import com.android.adservices.ui.settings.AdServicesSettingsActivity;
+import com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity;
/** Fragment for the topics view of the AdServices Settings App. */
public class ConsentNotificationFragment extends Fragment {
public static final String IS_EU_DEVICE_ARGUMENT_KEY = "isEUDevice";
+ private boolean mIsEUDevice;
+ private @Nullable ScrollToBottomController mScrollToBottomController;
@Override
public View onCreateView(
@@ -50,22 +57,23 @@ public class ConsentNotificationFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
- boolean isEUDevice =
+ mIsEUDevice =
requireActivity().getIntent().getBooleanExtra(IS_EU_DEVICE_ARGUMENT_KEY, true);
- logLandingPageDisplayed(isEUDevice);
- setupListeners(isEUDevice);
+ logLandingPageDisplayed();
+ setupListeners(savedInstanceState);
}
- private void logLandingPageDisplayed(boolean isEUDevice) {
- UIStats uiStats = new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(
- isEUDevice
- ? AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU
- : AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW)
- .setAction(
- AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__LANDING_PAGE_DISPLAYED)
- .build();
+ private void logLandingPageDisplayed() {
+ UIStats uiStats =
+ new UIStats.Builder()
+ .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
+ .setRegion(
+ mIsEUDevice
+ ? AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU
+ : AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW)
+ .setAction(
+ AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__LANDING_PAGE_DISPLAYED)
+ .build();
AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
}
@@ -82,7 +90,7 @@ public class ConsentNotificationFragment extends Fragment {
return rootView;
}
- private void setupListeners(boolean isEUDevice) {
+ private void setupListeners(Bundle savedInstanceState) {
TextView howItWorksExpander = requireActivity().findViewById(R.id.how_it_works_expander);
howItWorksExpander.setOnClickListener(
view -> {
@@ -101,37 +109,29 @@ public class ConsentNotificationFragment extends Fragment {
Button leftControlButton = requireActivity().findViewById(R.id.leftControlButton);
leftControlButton.setOnClickListener(
view -> {
- if (isEUDevice) {
+ if (mIsEUDevice) {
// opt-out confirmation activity
- ConsentManager.getInstance(requireContext())
- .disable(requireContext());
+ ConsentManager.getInstance(requireContext()).disable(requireContext());
Bundle args = new Bundle();
args.putBoolean(IS_CONSENT_GIVEN_ARGUMENT_KEY, false);
startConfirmationFragment(args);
} else {
// go to settings activity
Intent intent =
- new Intent(requireActivity(), AdServicesSettingsActivity.class);
- intent.addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ new Intent(requireActivity(), AdServicesSettingsMainActivity.class);
+ intent.putExtra(FROM_NOTIFICATION_KEY, true);
startActivity(intent);
+ requireActivity().finish();
}
});
Button rightControlButton = requireActivity().findViewById(R.id.rightControlButton);
- rightControlButton.setOnClickListener(
- view -> {
- if (isEUDevice) {
- // opt-in confirmation activity
- ConsentManager.getInstance(requireContext()).enable(requireContext());
- Bundle args = new Bundle();
- args.putBoolean(IS_CONSENT_GIVEN_ARGUMENT_KEY, true);
- startConfirmationFragment(args);
- } else {
- // acknowledge and dismiss
- requireActivity().finish();
- }
- });
+ ScrollView scrollView = requireView().findViewById(R.id.notification_fragment_scrollview);
+
+ mScrollToBottomController =
+ new ScrollToBottomController(
+ scrollView, leftControlButton, rightControlButton, savedInstanceState);
+ mScrollToBottomController.bind();
}
private void startConfirmationFragment(Bundle args) {
@@ -142,7 +142,101 @@ public class ConsentNotificationFragment extends Fragment {
R.id.fragment_container_view,
ConsentNotificationConfirmationFragment.class,
args)
+ .setReorderingAllowed(true)
.addToBackStack(null)
.commit();
}
+
+ /**
+ * Allows the positive, acceptance button to scroll the view.
+ *
+ * <p>When the positive button first appears it will show the text "More". When the user taps
+ * the button, the view will scroll to the bottom. Once the view has scrolled to the bottom, the
+ * button text will be replaced with the acceptance text. Once the text has changed, the button
+ * will trigger the positive action no matter where the view is scrolled.
+ */
+ private class ScrollToBottomController implements OnScrollChangeListener {
+ private static final String STATE_HAS_SCROLLED_TO_BOTTOM = "has_scrolled_to_bottom";
+ private static final int SCROLL_DIRECTION_DOWN = 1;
+ private static final double SCROLL_MULTIPLIER = 0.8;
+
+ private final ScrollView mScrollContainer;
+ private final Button mLeftControlButton;
+ private final Button mRightControlButton;
+
+ private boolean mHasScrolledToBottom;
+
+ ScrollToBottomController(
+ ScrollView scrollContainer,
+ Button leftControlButton,
+ Button rightControlButton,
+ @Nullable Bundle savedInstanceState) {
+ this.mScrollContainer = scrollContainer;
+ this.mLeftControlButton = leftControlButton;
+ this.mRightControlButton = rightControlButton;
+ mHasScrolledToBottom =
+ savedInstanceState != null
+ && savedInstanceState.containsKey(STATE_HAS_SCROLLED_TO_BOTTOM)
+ && savedInstanceState.getBoolean(STATE_HAS_SCROLLED_TO_BOTTOM);
+ }
+
+ public void bind() {
+ mScrollContainer.setOnScrollChangeListener(this);
+ mRightControlButton.setOnClickListener(this::onMoreOrAcceptClicked);
+ updateControlButtons();
+ }
+
+ public void saveInstanceState(Bundle bundle) {
+ if (mHasScrolledToBottom) {
+ bundle.putBoolean(STATE_HAS_SCROLLED_TO_BOTTOM, true);
+ }
+ }
+
+ private void updateControlButtons() {
+ if (mHasScrolledToBottom) {
+ mLeftControlButton.setVisibility(View.VISIBLE);
+ mRightControlButton.setText(
+ mIsEUDevice
+ ? R.string.notificationUI_right_control_button_text_eu
+ : R.string.notificationUI_right_control_button_text);
+ } else {
+ mLeftControlButton.setVisibility(View.INVISIBLE);
+ mRightControlButton.setText(R.string.notificationUI_more_button_text);
+ }
+ }
+
+ private void onMoreOrAcceptClicked(View view) {
+ Context context = getContext();
+ if (context == null) {
+ return;
+ }
+
+ if (mHasScrolledToBottom) {
+ if (mIsEUDevice) {
+ // opt-in confirmation activity
+ ConsentManager.getInstance(requireContext()).enable(requireContext());
+ Bundle args = new Bundle();
+ args.putBoolean(IS_CONSENT_GIVEN_ARGUMENT_KEY, true);
+ startConfirmationFragment(args);
+ } else {
+ // acknowledge and dismiss
+ requireActivity().finish();
+ }
+ } else {
+ mScrollContainer.smoothScrollTo(
+ 0,
+ mScrollContainer.getScrollY()
+ + (int) (mScrollContainer.getHeight() * SCROLL_MULTIPLIER));
+ }
+ }
+
+ @Override
+ public void onScrollChange(
+ View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
+ if (!mScrollContainer.canScrollVertically(SCROLL_DIRECTION_DOWN)) {
+ mHasScrolledToBottom = true;
+ updateControlButtons();
+ }
+ }
+ }
}
diff --git a/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationTrigger.java b/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationTrigger.java
index 633f8006b..7f2ef7181 100644
--- a/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationTrigger.java
+++ b/adservices/apk/java/com/android/adservices/ui/notifications/ConsentNotificationTrigger.java
@@ -80,7 +80,16 @@ public class ConsentNotificationTrigger {
.setStyle(textStyle)
.setPriority(NOTIFICATION_PRIORITY)
.setAutoCancel(true)
- .setContentIntent(pendingIntent);
+ .setContentIntent(pendingIntent)
+ .addAction(
+ isEuDevice
+ ? R.string.notificationUI_notification_cta_eu
+ : R.string.notificationUI_notification_cta,
+ context.getString(
+ isEuDevice
+ ? R.string.notificationUI_notification_cta_eu
+ : R.string.notificationUI_notification_cta),
+ pendingIntent);
}
/**
@@ -102,8 +111,7 @@ public class ConsentNotificationTrigger {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
if (!notificationManager.areNotificationsEnabled()) {
- ConsentManager.getInstance(context)
- .recordNotificationDisplayed(context.getPackageManager());
+ ConsentManager.getInstance(context).recordNotificationDisplayed();
// TODO(b/242001860): add logging
return;
}
@@ -118,8 +126,7 @@ public class ConsentNotificationTrigger {
getConsentNotificationBuilder(context, isEuDevice);
notificationManager.notify(NOTIFICATION_ID, consentNotificationBuilder.build());
- ConsentManager.getInstance(context)
- .recordNotificationDisplayed(context.getPackageManager());
+ ConsentManager.getInstance(context).recordNotificationDisplayed();
}
private static void createNotificationChannel(@NonNull Context context) {
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/ActionDelegate.java b/adservices/apk/java/com/android/adservices/ui/settings/ActionDelegate.java
deleted file mode 100644
index 84c3d5ee8..000000000
--- a/adservices/apk/java/com/android/adservices/ui/settings/ActionDelegate.java
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * 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.ui.settings;
-
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__BLOCK_APP_SELECTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__BLOCK_TOPIC_SELECTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__MANAGE_APPS_SELECTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__MANAGE_TOPICS_SELECTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__RESET_APP_SELECTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__RESET_TOPIC_SELECTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__UNBLOCK_APP_SELECTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__UNBLOCK_TOPIC_SELECTED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
-
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.fragment.app.FragmentManager;
-
-import com.android.adservices.api.R;
-import com.android.adservices.data.topics.Topic;
-import com.android.adservices.service.consent.App;
-import com.android.adservices.service.consent.DeviceRegionProvider;
-import com.android.adservices.service.stats.AdServicesLoggerImpl;
-import com.android.adservices.service.stats.UIStats;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsAppsFragment;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedAppsFragment;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedTopicsFragment;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsMainFragment;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsTopicsFragment;
-import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
-import com.android.adservices.ui.settings.viewmodels.MainViewModel;
-import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
-import com.android.adservices.ui.settings.viewmodels.TopicsViewModel.TopicsViewModelUiEvent;
-import com.android.settingslib.widget.MainSwitchBar;
-
-import java.io.IOException;
-
-/**
- * Delegate class that helps AdServices Settings fragments to respond to all view model/user events.
- */
-public class ActionDelegate {
- private final AdServicesSettingsActivity mAdServicesSettingsActivity;
- private final FragmentManager mFragmentManager;
- private final MainViewModel mMainViewModel;
- private final TopicsViewModel mTopicsViewModel;
- private final AppsViewModel mAppsViewModel;
- private final int mDeviceLoggingRegion;
-
- public ActionDelegate(
- AdServicesSettingsActivity adServicesSettingsActivity,
- FragmentManager fragmentManager,
- MainViewModel mainViewModel,
- TopicsViewModel topicsViewModel,
- AppsViewModel appsViewModel) {
- mAdServicesSettingsActivity = adServicesSettingsActivity;
- mFragmentManager = fragmentManager;
- mMainViewModel = mainViewModel;
- mTopicsViewModel = topicsViewModel;
- mAppsViewModel = appsViewModel;
- mDeviceLoggingRegion =
- DeviceRegionProvider.isEuDevice(adServicesSettingsActivity)
- ? AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU
- : AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
-
- listenToMainViewModelUiEvents();
- listenToTopicsViewModelUiEvents();
- listenToAppsViewModelUiEvents();
- }
-
- // ---------------------------------------------------------------------------------------------
- // UI Event Listeners
- // ---------------------------------------------------------------------------------------------
-
- private void listenToMainViewModelUiEvents() {
- mMainViewModel
- .getUiEvents()
- .observe(
- mAdServicesSettingsActivity,
- event -> {
- if (event == null) {
- return;
- }
- try {
- switch (event) {
- case SWITCH_ON_PRIVACY_SANDBOX_BETA:
- mMainViewModel.setConsent(true);
- break;
- case SWITCH_OFF_PRIVACY_SANDBOX_BETA:
- // TODO(b/235138016): confirmation for privacy sandbox
- // consent
- mMainViewModel.setConsent(false);
- break;
- case DISPLAY_APPS_FRAGMENT:
- logManageAppsSelected();
- mFragmentManager
- .beginTransaction()
- .replace(
- R.id.fragment_container_view,
- AdServicesSettingsAppsFragment.class,
- null)
- .setReorderingAllowed(true)
- .addToBackStack(null)
- .commit();
- mAppsViewModel.refresh();
- break;
- case DISPLAY_TOPICS_FRAGMENT:
- logManageTopicsSelected();
- mFragmentManager
- .beginTransaction()
- .replace(
- R.id.fragment_container_view,
- AdServicesSettingsTopicsFragment.class,
- null)
- .setReorderingAllowed(true)
- .addToBackStack(null)
- .commit();
- mTopicsViewModel.refresh();
- break;
- }
- } finally {
- mMainViewModel.uiEventHandled();
- }
- });
- }
-
- private void listenToTopicsViewModelUiEvents() {
- mTopicsViewModel
- .getUiEvents()
- .observe(
- mAdServicesSettingsActivity,
- eventTopicPair -> {
- if (eventTopicPair == null) {
- return;
- }
- TopicsViewModelUiEvent event = eventTopicPair.first;
- Topic topic = eventTopicPair.second;
- if (event == null) {
- return;
- }
- try {
- switch (event) {
- case BLOCK_TOPIC:
- logBlockTopicSelected();
- // TODO(b/229721429): show confirmation for blocking a
- // topic.
- mTopicsViewModel.revokeTopicConsent(topic);
- break;
- case RESTORE_TOPIC:
- logUnblockTopicSelected();
- // TODO(b/229721429): show confirmation for restoring a
- // topic.
- mTopicsViewModel.restoreTopicConsent(topic);
- break;
- case RESET_TOPICS:
- logResetTopicSelected();
- // TODO(b/229721429): show confirmation for resetting
- // topics.
- mTopicsViewModel.resetTopics();
- break;
- case DISPLAY_BLOCKED_TOPICS_FRAGMENT:
- mFragmentManager
- .beginTransaction()
- .replace(
- R.id.fragment_container_view,
- AdServicesSettingsBlockedTopicsFragment
- .class,
- null)
- .setReorderingAllowed(true)
- .addToBackStack(null)
- .commit();
- mTopicsViewModel.refresh();
- break;
- }
- } finally {
- mTopicsViewModel.uiEventHandled();
- }
- });
- }
-
- private void listenToAppsViewModelUiEvents() {
- mAppsViewModel
- .getUiEvents()
- .observe(
- mAdServicesSettingsActivity,
- eventAppPair -> {
- AppsViewModel.AppsViewModelUiEvent event = eventAppPair.first;
- App app = eventAppPair.second;
- if (event == null) {
- return;
- }
- try {
- switch (event) {
- // TODO(b/241605477): add RESET_APP with logging
- case BLOCK_APP:
- logBlockAppSelected();
- try {
- mAppsViewModel.revokeAppConsent(app);
- } catch (IOException e) {
- Toast.makeText(
- mMainViewModel.getApplication(),
- "Block app failed",
- Toast.LENGTH_SHORT);
- }
- break;
- case RESTORE_APP:
- logUnblockAppSelected();
- try {
- mAppsViewModel.restoreAppConsent(app);
- } catch (IOException e) {
- Toast.makeText(
- mMainViewModel.getApplication(),
- "Unblock app failed",
- Toast.LENGTH_SHORT);
- }
- break;
- case RESET_APPS:
- logResetAppSelected();
- try {
- mAppsViewModel.resetApps();
- } catch (IOException e) {
- Toast.makeText(
- mMainViewModel.getApplication(),
- "Reset app failed",
- Toast.LENGTH_SHORT);
- }
- break;
- case DISPLAY_BLOCKED_APPS_FRAGMENT:
- mFragmentManager
- .beginTransaction()
- .replace(
- R.id.fragment_container_view,
- AdServicesSettingsBlockedAppsFragment.class,
- null)
- .setReorderingAllowed(true)
- .addToBackStack(null)
- .commit();
- mAppsViewModel.refresh();
- break;
- }
- } finally {
- mAppsViewModel.uiEventHandled();
- }
- });
- }
-
- // ---------------------------------------------------------------------------------------------
- // Main Fragment
- // ---------------------------------------------------------------------------------------------
-
- /**
- * Configure all UI elements in {@link AdServicesSettingsMainFragment} to handle user
- * actions.
- *
- * @param fragment the fragment to be initialized.
- */
- public void initMainFragment(AdServicesSettingsMainFragment fragment) {
- mAdServicesSettingsActivity.setTitle(R.string.settingsUI_main_view_title);
- configureConsentSwitch(fragment);
- configureTopicsButton();
- configureAppsButton();
- }
-
- private void configureConsentSwitch(AdServicesSettingsMainFragment fragment) {
- MainSwitchBar mainSwitchBar =
- mAdServicesSettingsActivity.findViewById(R.id.main_switch_bar);
-
- mMainViewModel.getConsent().observe(fragment, mainSwitchBar::setChecked);
-
- mainSwitchBar.setOnClickListener(
- switchBar -> mMainViewModel.consentSwitchClickHandler(
- ((MainSwitchBar) switchBar).isChecked()));
- }
-
- private void configureTopicsButton() {
- View topicsButton = mAdServicesSettingsActivity.findViewById(R.id.topics_preference);
-
- topicsButton.setOnClickListener(preference -> mMainViewModel.topicsButtonClickHandler());
- }
-
- private void configureAppsButton() {
- View appsButton = mAdServicesSettingsActivity.findViewById(R.id.apps_preference);
-
- appsButton.setOnClickListener(preference -> mMainViewModel.appsButtonClickHandler());
- }
-
- // ---------------------------------------------------------------------------------------------
- // Topics Fragment
- // ---------------------------------------------------------------------------------------------
-
- /**
- * Configure all UI elements (except topics list) in {@link AdServicesSettingsTopicsFragment} to
- * handle user actions.
- */
- public void initTopicsFragment(AdServicesSettingsTopicsFragment fragment) {
- mAdServicesSettingsActivity.setTitle(R.string.settingsUI_topics_view_title);
- configureBlockedTopicsFragmentButton(fragment);
- configureResetTopicsButton(fragment);
- }
-
- private void configureBlockedTopicsFragmentButton(AdServicesSettingsTopicsFragment fragment) {
- View blockedTopicsButton = fragment.requireView().findViewById(R.id.blocked_topics_button);
-
- blockedTopicsButton.setOnClickListener(
- view -> {
- mTopicsViewModel.blockedTopicsFragmentButtonClickHandler();
- });
- }
-
- private void configureResetTopicsButton(AdServicesSettingsTopicsFragment fragment) {
- View resetTopicsButton = fragment.requireView().findViewById(R.id.reset_topics_button);
-
- resetTopicsButton.setOnClickListener(
- view -> {
- mTopicsViewModel.resetTopicsButtonClickHandler();
- });
- }
-
- // ---------------------------------------------------------------------------------------------
- // Apps Fragment
- // ---------------------------------------------------------------------------------------------
-
- /**
- * Configure all UI elements (except apps list) in {@link AdServicesSettingsAppsFragment} to
- * handle user actions.
- */
- public void initAppsFragment(AdServicesSettingsAppsFragment fragment) {
- mAdServicesSettingsActivity.setTitle(R.string.settingsUI_apps_view_title);
- configureBlockedAppsFragmentButton(fragment);
- configureResetAppsButton(fragment);
- }
-
- private void configureBlockedAppsFragmentButton(AdServicesSettingsAppsFragment fragment) {
- View blockedAppsButton = fragment.requireView().findViewById(R.id.blocked_apps_button);
-
- blockedAppsButton.setOnClickListener(
- view -> {
- mAppsViewModel.blockedAppsFragmentButtonClickHandler();
- });
- }
-
- private void configureResetAppsButton(AdServicesSettingsAppsFragment fragment) {
- View resetAppsButton = fragment.requireView().findViewById(R.id.reset_apps_button);
-
- resetAppsButton.setOnClickListener(
- view -> {
- mAppsViewModel.resetAppsButtonClickHandler();
- });
- }
-
- // ---------------------------------------------------------------------------------------------
- // Blocked Topics Fragment
- // ---------------------------------------------------------------------------------------------
-
- /**
- * Configure all UI elements (except blocked topics list) in
- * {@link AdServicesSettingsBlockedTopicsFragment} to handle user actions.
- */
- public void initBlockedTopicsFragment() {
- mAdServicesSettingsActivity.setTitle(R.string.settingsUI_blocked_topics_title);
- }
-
- // ---------------------------------------------------------------------------------------------
- // Blocked Apps Fragment
- // ---------------------------------------------------------------------------------------------
-
- /**
- * Configure all UI elements (except blocked apps list) in
- * {@link AdServicesSettingsBlockedAppsFragment} to handle user actions.
- */
- public void initBlockedAppsFragment() {
- mAdServicesSettingsActivity.setTitle(R.string.settingsUI_blocked_apps_title);
- }
-
- // ---------------------------------------------------------------------------------------------
- // Logging
- // ---------------------------------------------------------------------------------------------
-
- private void logManageTopicsSelected() {
- UIStats uiStats =
- new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(mDeviceLoggingRegion)
- .setAction(
- AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__MANAGE_TOPICS_SELECTED)
- .build();
- AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
- }
-
- private void logManageAppsSelected() {
- UIStats uiStats =
- new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(mDeviceLoggingRegion)
- .setAction(
- AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__MANAGE_APPS_SELECTED)
- .build();
- AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
- }
-
- private void logResetTopicSelected() {
- UIStats uiStats =
- new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(mDeviceLoggingRegion)
- .setAction(
- AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__RESET_TOPIC_SELECTED)
- .build();
- AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
- }
-
- private void logResetAppSelected() {
- UIStats uiStats =
- new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(mDeviceLoggingRegion)
- .setAction(AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__RESET_APP_SELECTED)
- .build();
- AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
- }
-
- private void logBlockTopicSelected() {
- UIStats uiStats =
- new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(mDeviceLoggingRegion)
- .setAction(
- AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__BLOCK_TOPIC_SELECTED)
- .build();
- AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
- }
-
- private void logUnblockTopicSelected() {
- UIStats uiStats =
- new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(mDeviceLoggingRegion)
- .setAction(
- AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__UNBLOCK_TOPIC_SELECTED)
- .build();
- AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
- }
-
- private void logBlockAppSelected() {
- UIStats uiStats =
- new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(mDeviceLoggingRegion)
- .setAction(AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__BLOCK_APP_SELECTED)
- .build();
- AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
- }
-
- private void logUnblockAppSelected() {
- UIStats uiStats =
- new UIStats.Builder()
- .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
- .setRegion(mDeviceLoggingRegion)
- .setAction(
- AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__UNBLOCK_APP_SELECTED)
- .build();
- AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
- }
-}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/AdServicesSettingsActivity.java b/adservices/apk/java/com/android/adservices/ui/settings/AdServicesSettingsActivity.java
deleted file mode 100644
index 074fbb373..000000000
--- a/adservices/apk/java/com/android/adservices/ui/settings/AdServicesSettingsActivity.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.ui.settings;
-
-import android.os.Bundle;
-import android.view.MenuItem;
-
-import androidx.core.view.WindowCompat;
-import androidx.lifecycle.ViewModelProvider;
-
-import com.android.adservices.api.R;
-import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
-import com.android.adservices.ui.settings.viewmodels.MainViewModel;
-import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
-import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
-
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Android application activity for controlling settings related to PP (Privacy Preserving) APIs.
- */
-public class AdServicesSettingsActivity extends CollapsingToolbarBaseActivity {
- private ActionDelegate mActionDelegate;
- private ViewModelProvider mViewModelProvider;
-
- /** @return the {@link ActionDelegate} for the activity. */
- public ActionDelegate getActionDelegate() {
- return mActionDelegate;
- }
-
- /**
- * Gets the {@link ViewModelProvider} for the activity. Need to use this implementation for
- * testing/mocking limitations.
- *
- * @return the {@link ViewModelProvider} for the activity.
- */
- public ViewModelProvider getViewModelProvider() {
- if (mViewModelProvider == null) {
- mViewModelProvider = new ViewModelProvider(this);
- }
- return mViewModelProvider;
- }
-
- public AdServicesSettingsActivity() {}
-
- @VisibleForTesting
- AdServicesSettingsActivity(ViewModelProvider viewModelProvider) {
- mViewModelProvider = viewModelProvider;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
- setContentView(R.layout.adservices_settings_main_activity);
- initActionDelegate();
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- onBackPressed();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- private void initActionDelegate() {
- mActionDelegate =
- new ActionDelegate(
- this,
- getSupportFragmentManager(),
- getViewModelProvider().get(MainViewModel.class),
- getViewModelProvider().get(TopicsViewModel.class),
- getViewModelProvider().get(AppsViewModel.class));
- }
-}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/DialogManager.java b/adservices/apk/java/com/android/adservices/ui/settings/DialogManager.java
new file mode 100644
index 000000000..82c5326c9
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/DialogManager.java
@@ -0,0 +1,225 @@
+/*
+ * 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.ui.settings;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+
+import androidx.annotation.NonNull;
+
+import com.android.adservices.api.R;
+import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.consent.App;
+import com.android.adservices.service.topics.TopicsMapper;
+import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
+import com.android.adservices.ui.settings.viewmodels.MainViewModel;
+import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
+
+import java.io.IOException;
+import java.util.concurrent.Semaphore;
+
+/** Creates and displays dialogs for the Privacy Sandbox application. */
+public class DialogManager {
+ public static Semaphore sSemaphore = new Semaphore(1);
+
+ /**
+ * Shows the dialog for opting out of Privacy Sandbox.
+ *
+ * @param context Application context.
+ * @param mainViewModel {@link MainViewModel}
+ */
+ public static void showOptOutDialog(@NonNull Context context, MainViewModel mainViewModel) {
+ if (!sSemaphore.tryAcquire()) return;
+ OnClickListener positiveOnClickListener =
+ (dialogInterface, buttonId) -> {
+ mainViewModel.setConsent(false);
+ sSemaphore.release();
+ };
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.settingsUI_dialog_opt_out_title)
+ .setMessage(R.string.settingsUI_dialog_opt_out_message)
+ .setPositiveButton(
+ R.string.settingsUI_dialog_opt_out_positive_text, positiveOnClickListener)
+ .setNegativeButton(
+ R.string.settingsUI_dialog_negative_text, getNegativeOnClickListener())
+ .setOnDismissListener(getOnDismissListener())
+ .show();
+ }
+
+ /**
+ * Shows the dialog for blocking a topic.
+ *
+ * @param context Application context.
+ * @param topicsViewModel {@link TopicsViewModel}
+ * @param topic topic to block.
+ */
+ public static void showBlockTopicDialog(
+ @NonNull Context context, TopicsViewModel topicsViewModel, Topic topic) {
+ if (!sSemaphore.tryAcquire()) return;
+ OnClickListener positiveOnClickListener =
+ (dialogInterface, buttonId) -> {
+ topicsViewModel.revokeTopicConsent(topic);
+ sSemaphore.release();
+ };
+ String topicName = context.getString(TopicsMapper.getResourceIdByTopic(topic, context));
+ new AlertDialog.Builder(context)
+ .setTitle(
+ context.getString(R.string.settingsUI_dialog_block_topic_title, topicName))
+ .setMessage(R.string.settingsUI_dialog_block_topic_message)
+ .setPositiveButton(
+ R.string.settingsUI_dialog_block_topic_positive_text,
+ positiveOnClickListener)
+ .setNegativeButton(
+ R.string.settingsUI_dialog_negative_text, getNegativeOnClickListener())
+ .setOnDismissListener(getOnDismissListener())
+ .show();
+ }
+
+ /**
+ * Shows the dialog for unblocking a topic.
+ *
+ * @param context Application context.
+ * @param topic topic to unblock.
+ */
+ public static void showUnblockTopicDialog(@NonNull Context context, Topic topic) {
+ if (!sSemaphore.tryAcquire()) return;
+ String topicName = context.getString(TopicsMapper.getResourceIdByTopic(topic, context));
+ new AlertDialog.Builder(context)
+ .setTitle(
+ context.getString(
+ R.string.settingsUI_dialog_unblock_topic_title, topicName))
+ .setMessage(R.string.settingsUI_dialog_unblock_topic_message)
+ .setPositiveButton(
+ R.string.settingsUI_dialog_unblock_topic_positive_text,
+ getNegativeOnClickListener())
+ .setOnDismissListener(getOnDismissListener())
+ .show();
+ }
+
+ /**
+ * Shows the dialog for resetting topics. (reset does not reset blocked topics
+ *
+ * @param context Application context.
+ * @param topicsViewModel {@link TopicsViewModel}
+ */
+ public static void showResetTopicDialog(
+ @NonNull Context context, TopicsViewModel topicsViewModel) {
+ if (!sSemaphore.tryAcquire()) return;
+ OnClickListener positiveOnClickListener =
+ (dialogInterface, buttonId) -> {
+ topicsViewModel.resetTopics();
+ sSemaphore.release();
+ };
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.settingsUI_dialog_reset_topic_title)
+ .setMessage(R.string.settingsUI_dialog_reset_topic_message)
+ .setPositiveButton(
+ R.string.settingsUI_dialog_reset_topic_positive_text,
+ positiveOnClickListener)
+ .setNegativeButton(
+ R.string.settingsUI_dialog_negative_text, getNegativeOnClickListener())
+ .setOnDismissListener(getOnDismissListener())
+ .show();
+ }
+
+ /**
+ * Shows the dialog for blocking an app from using FLEDGE.
+ *
+ * @param context Application context.
+ * @param appsViewModel {@link AppsViewModel}
+ * @param app the {@link App} to block.
+ */
+ public static void showBlockAppDialog(
+ @NonNull Context context, AppsViewModel appsViewModel, App app) {
+ if (!sSemaphore.tryAcquire()) return;
+ OnClickListener positiveOnClickListener =
+ (dialogInterface, buttonId) -> {
+ try {
+ appsViewModel.revokeAppConsent(app);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ sSemaphore.release();
+ };
+ String appName = app.getAppDisplayName(context.getPackageManager());
+ new AlertDialog.Builder(context)
+ .setTitle(context.getString(R.string.settingsUI_dialog_block_app_title, appName))
+ .setMessage(R.string.settingsUI_dialog_block_app_message)
+ .setPositiveButton(
+ R.string.settingsUI_dialog_block_app_positive_text, positiveOnClickListener)
+ .setNegativeButton(
+ R.string.settingsUI_dialog_negative_text, getNegativeOnClickListener())
+ .setOnDismissListener(getOnDismissListener())
+ .show();
+ }
+
+ /**
+ * Shows the dialog for unblocking an app from using FLEDGE.
+ *
+ * @param context Application context.
+ * @param app the {@link App} to unblock.
+ */
+ public static void showUnblockAppDialog(@NonNull Context context, App app) {
+ if (!sSemaphore.tryAcquire()) return;
+ String appName = app.getAppDisplayName(context.getPackageManager());
+ new AlertDialog.Builder(context)
+ .setTitle(context.getString(R.string.settingsUI_dialog_unblock_app_title, appName))
+ .setMessage(R.string.settingsUI_dialog_unblock_app_message)
+ .setPositiveButton(
+ R.string.settingsUI_dialog_unblock_app_positive_text,
+ getNegativeOnClickListener())
+ .setOnDismissListener(getOnDismissListener())
+ .show();
+ }
+
+ /**
+ * Shows the dialog for resetting FLEDGE data. (reset does not reset blocked apps)
+ *
+ * @param context Application context.
+ * @param appsViewModel {@link AppsViewModel}
+ */
+ public static void showResetAppDialog(@NonNull Context context, AppsViewModel appsViewModel) {
+ if (!sSemaphore.tryAcquire()) return;
+ OnClickListener positiveOnClickListener =
+ (dialogInterface, buttonId) -> {
+ try {
+ appsViewModel.resetApps();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ sSemaphore.release();
+ };
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.settingsUI_dialog_reset_app_title)
+ .setMessage(R.string.settingsUI_dialog_reset_app_message)
+ .setPositiveButton(
+ R.string.settingsUI_dialog_reset_app_positive_text, positiveOnClickListener)
+ .setNegativeButton(
+ R.string.settingsUI_dialog_negative_text, getNegativeOnClickListener())
+ .setOnDismissListener(getOnDismissListener())
+ .show();
+ }
+
+ private static OnClickListener getNegativeOnClickListener() {
+ return (dialogInterface, buttonId) -> sSemaphore.release();
+ }
+
+ private static DialogInterface.OnDismissListener getOnDismissListener() {
+ return dialogInterface -> sSemaphore.release();
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/activities/AdServicesBaseActivity.java b/adservices/apk/java/com/android/adservices/ui/settings/activities/AdServicesBaseActivity.java
new file mode 100644
index 000000000..99f5bea00
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/activities/AdServicesBaseActivity.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ui.settings.activities;
+
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.core.view.WindowCompat;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+
+/**
+ * Android application activity for controlling settings related to PP (Privacy Preserving) APIs.
+ * This class is the base class for all other activities. We need an activity for each page in order
+ * for {@link CollapsingToolbarBaseActivity} to work properly.
+ */
+public abstract class AdServicesBaseActivity extends CollapsingToolbarBaseActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ onBackPressed();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/activities/AdServicesSettingsMainActivity.java b/adservices/apk/java/com/android/adservices/ui/settings/activities/AdServicesSettingsMainActivity.java
new file mode 100644
index 000000000..a2aa89bd6
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/activities/AdServicesSettingsMainActivity.java
@@ -0,0 +1,65 @@
+/*
+ * 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.ui.settings.activities;
+
+import android.os.Bundle;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.adservices.api.R;
+import com.android.adservices.ui.settings.delegates.MainActionDelegate;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsMainFragment;
+import com.android.adservices.ui.settings.viewmodels.MainViewModel;
+
+/**
+ * Android application activity for controlling settings related to PP (Privacy Preserving) APIs.
+ */
+public class AdServicesSettingsMainActivity extends AdServicesBaseActivity {
+ public static final String FROM_NOTIFICATION_KEY = "FROM_NOTIFICATION";
+ private MainActionDelegate mActionDelegate;
+
+ /** @return the action delegate for the activity. */
+ public MainActionDelegate getActionDelegate() {
+ return mActionDelegate;
+ }
+
+ @Override
+ public void onBackPressed() {
+ // if navigated here from notification, then back button should not go back to notification.
+ if (getIntent().getBooleanExtra(FROM_NOTIFICATION_KEY, false)) {
+ moveTaskToBack(true);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.adservices_settings_main_activity);
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.fragment_container_view, AdServicesSettingsMainFragment.class, null)
+ .setReorderingAllowed(true)
+ .commit();
+ initActionDelegate();
+ }
+
+ private void initActionDelegate() {
+ mActionDelegate =
+ new MainActionDelegate(this, new ViewModelProvider(this).get(MainViewModel.class));
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/activities/AppsActivity.java b/adservices/apk/java/com/android/adservices/ui/settings/activities/AppsActivity.java
new file mode 100644
index 000000000..a62cacaa8
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/activities/AppsActivity.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ui.settings.activities;
+
+import android.os.Bundle;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.adservices.api.R;
+import com.android.adservices.ui.settings.delegates.AppsActionDelegate;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsAppsFragment;
+import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
+
+/**
+ * Android application activity for controlling applications which interacted with FLEDGE
+ * (Remarketing) APIs.
+ */
+public class AppsActivity extends AdServicesBaseActivity {
+ private AppsActionDelegate mActionDelegate;
+
+ /** @return the action delegate for the activity. */
+ public AppsActionDelegate getActionDelegate() {
+ return mActionDelegate;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.adservices_settings_main_activity);
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.fragment_container_view, AdServicesSettingsAppsFragment.class, null)
+ .setReorderingAllowed(true)
+ .commit();
+ initActionDelegate();
+ }
+
+ private void initActionDelegate() {
+ mActionDelegate =
+ new AppsActionDelegate(this, new ViewModelProvider(this).get(AppsViewModel.class));
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/activities/BlockedAppsActivity.java b/adservices/apk/java/com/android/adservices/ui/settings/activities/BlockedAppsActivity.java
new file mode 100644
index 000000000..381bda07d
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/activities/BlockedAppsActivity.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ui.settings.activities;
+
+import android.os.Bundle;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.adservices.api.R;
+import com.android.adservices.ui.settings.delegates.BlockedAppsActionDelegate;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedAppsFragment;
+import com.android.adservices.ui.settings.viewmodels.BlockedAppsViewModel;
+
+/**
+ * Android application activity for controlling applications which are blocked from interacting with
+ * FLEDGE (Remarketing) APIs.
+ */
+public class BlockedAppsActivity extends AdServicesBaseActivity {
+ private BlockedAppsActionDelegate mActionDelegate;
+
+ /** @return the action delegate for the activity. */
+ public BlockedAppsActionDelegate getActionDelegate() {
+ return mActionDelegate;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.adservices_settings_main_activity);
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(
+ R.id.fragment_container_view,
+ AdServicesSettingsBlockedAppsFragment.class,
+ null)
+ .setReorderingAllowed(true)
+ .commit();
+ initActionDelegate();
+ }
+
+ private void initActionDelegate() {
+ mActionDelegate =
+ new BlockedAppsActionDelegate(
+ this, new ViewModelProvider(this).get(BlockedAppsViewModel.class));
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/activities/BlockedTopicsActivity.java b/adservices/apk/java/com/android/adservices/ui/settings/activities/BlockedTopicsActivity.java
new file mode 100644
index 000000000..e02956ef2
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/activities/BlockedTopicsActivity.java
@@ -0,0 +1,58 @@
+/*
+ * 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.ui.settings.activities;
+
+import android.os.Bundle;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.adservices.api.R;
+import com.android.adservices.ui.settings.delegates.BlockedTopicsActionDelegate;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedTopicsFragment;
+import com.android.adservices.ui.settings.viewmodels.BlockedTopicsViewModel;
+
+/**
+ * Android application activity for controlling blocked topics that were generated from Topics API.
+ */
+public class BlockedTopicsActivity extends AdServicesBaseActivity {
+ private BlockedTopicsActionDelegate mActionDelegate;
+
+ /** @return the action delegate for the activity. */
+ public BlockedTopicsActionDelegate getActionDelegate() {
+ return mActionDelegate;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.adservices_settings_main_activity);
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(
+ R.id.fragment_container_view,
+ AdServicesSettingsBlockedTopicsFragment.class,
+ null)
+ .setReorderingAllowed(true)
+ .commit();
+ initActionDelegate();
+ }
+
+ private void initActionDelegate() {
+ mActionDelegate =
+ new BlockedTopicsActionDelegate(
+ this, new ViewModelProvider(this).get(BlockedTopicsViewModel.class));
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/activities/TopicsActivity.java b/adservices/apk/java/com/android/adservices/ui/settings/activities/TopicsActivity.java
new file mode 100644
index 000000000..fd1f887e3
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/activities/TopicsActivity.java
@@ -0,0 +1,53 @@
+/*
+ * 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.ui.settings.activities;
+
+import android.os.Bundle;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.adservices.api.R;
+import com.android.adservices.ui.settings.delegates.TopicsActionDelegate;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsTopicsFragment;
+import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
+
+/** Android application activity for controlling topics generated by Topics API. */
+public class TopicsActivity extends AdServicesBaseActivity {
+ private TopicsActionDelegate mActionDelegate;
+
+ /** @return the action delegate for the activity. */
+ public TopicsActionDelegate getActionDelegate() {
+ return mActionDelegate;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.adservices_settings_main_activity);
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.fragment_container_view, AdServicesSettingsTopicsFragment.class, null)
+ .setReorderingAllowed(true)
+ .commit();
+ initActionDelegate();
+ }
+
+ private void initActionDelegate() {
+ mActionDelegate =
+ new TopicsActionDelegate(
+ this, new ViewModelProvider(this).get(TopicsViewModel.class));
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/delegates/AppsActionDelegate.java b/adservices/apk/java/com/android/adservices/ui/settings/delegates/AppsActionDelegate.java
new file mode 100644
index 000000000..ee2cb8863
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/delegates/AppsActionDelegate.java
@@ -0,0 +1,130 @@
+/*
+ * 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.ui.settings.delegates;
+
+import android.content.Intent;
+import android.util.Log;
+import android.util.Pair;
+import android.view.View;
+
+import androidx.lifecycle.Observer;
+
+import com.android.adservices.api.R;
+import com.android.adservices.service.PhFlags;
+import com.android.adservices.service.consent.App;
+import com.android.adservices.ui.settings.DialogManager;
+import com.android.adservices.ui.settings.activities.AppsActivity;
+import com.android.adservices.ui.settings.activities.BlockedAppsActivity;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsAppsFragment;
+import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
+import com.android.adservices.ui.settings.viewmodels.AppsViewModel.AppsViewModelUiEvent;
+
+import java.io.IOException;
+
+/**
+ * Delegate class that helps AdServices Settings fragments to respond to all view model/user events.
+ */
+public class AppsActionDelegate extends BaseActionDelegate {
+ private final AppsActivity mAppsActivity;
+ private final AppsViewModel mAppsViewModel;
+
+ public AppsActionDelegate(AppsActivity appsActivity, AppsViewModel appsViewModel) {
+ super(appsActivity);
+ mAppsActivity = appsActivity;
+ mAppsViewModel = appsViewModel;
+ listenToAppsViewModelUiEvents();
+ }
+
+ private void listenToAppsViewModelUiEvents() {
+ Observer<Pair<AppsViewModelUiEvent, App>> observer =
+ eventAppPair -> {
+ if (eventAppPair == null) {
+ return;
+ }
+ AppsViewModelUiEvent event = eventAppPair.first;
+ App app = eventAppPair.second;
+ if (event == null) {
+ return;
+ }
+ try {
+ switch (event) {
+ case BLOCK_APP:
+ logUIAction(ActionEnum.BLOCK_APP_SELECTED);
+ if (PhFlags.getInstance().getUIDialogsFeatureEnabled()) {
+ DialogManager.showBlockAppDialog(
+ mAppsActivity, mAppsViewModel, app);
+ } else {
+ mAppsViewModel.revokeAppConsent(app);
+ }
+ break;
+ case RESET_APPS:
+ logUIAction(ActionEnum.RESET_APP_SELECTED);
+ if (PhFlags.getInstance().getUIDialogsFeatureEnabled()) {
+ DialogManager.showResetAppDialog(mAppsActivity, mAppsViewModel);
+ } else {
+ mAppsViewModel.resetApps();
+ }
+ break;
+ case DISPLAY_BLOCKED_APPS_FRAGMENT:
+ Intent intent =
+ new Intent(mAppsActivity, BlockedAppsActivity.class);
+ mAppsActivity.startActivity(intent);
+ break;
+ default:
+ Log.e("AdservicesUI", "Unknown Action for UI Logging");
+ }
+ } catch (IOException e) {
+ Log.e(
+ "AdServicesUI",
+ "Error while processing AppsViewModelUiEvent " + event + ":" + e);
+ } finally {
+ mAppsViewModel.uiEventHandled();
+ }
+ };
+ mAppsViewModel.getUiEvents().observe(mAppsActivity, observer);
+ }
+
+ /**
+ * Configure all UI elements (except apps list) in {@link AdServicesSettingsAppsFragment} to
+ * handle user actions.
+ */
+ public void initAppsFragment(AdServicesSettingsAppsFragment fragment) {
+ mAppsActivity.setTitle(R.string.settingsUI_apps_view_title);
+ configureBlockedAppsFragmentButton(fragment);
+ configureResetAppsButton(fragment);
+ }
+
+ private void configureBlockedAppsFragmentButton(AdServicesSettingsAppsFragment fragment) {
+ View blockedAppsButton = fragment.requireView().findViewById(R.id.blocked_apps_button);
+ View blockedAppsWhenEmptyListButton =
+ fragment.requireView().findViewById(R.id.blocked_apps_when_empty_state_button);
+
+ blockedAppsButton.setOnClickListener(
+ view -> {
+ mAppsViewModel.blockedAppsFragmentButtonClickHandler();
+ });
+ blockedAppsWhenEmptyListButton.setOnClickListener(
+ view -> {
+ mAppsViewModel.blockedAppsFragmentButtonClickHandler();
+ });
+ }
+
+ private void configureResetAppsButton(AdServicesSettingsAppsFragment fragment) {
+ View resetAppsButton = fragment.requireView().findViewById(R.id.reset_apps_button);
+
+ resetAppsButton.setOnClickListener(view -> mAppsViewModel.resetAppsButtonClickHandler());
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/delegates/BaseActionDelegate.java b/adservices/apk/java/com/android/adservices/ui/settings/delegates/BaseActionDelegate.java
new file mode 100644
index 000000000..43d33014d
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/delegates/BaseActionDelegate.java
@@ -0,0 +1,110 @@
+/*
+ * 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.ui.settings.delegates;
+
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__ACTION_UNSPECIFIED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__BLOCK_APP_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__BLOCK_TOPIC_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__MANAGE_APPS_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__MANAGE_TOPICS_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__RESET_APP_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__RESET_TOPIC_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__UNBLOCK_APP_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__UNBLOCK_TOPIC_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
+
+import android.app.Activity;
+import android.util.Log;
+
+import com.android.adservices.service.consent.DeviceRegionProvider;
+import com.android.adservices.service.stats.AdServicesLoggerImpl;
+import com.android.adservices.service.stats.UIStats;
+import com.android.adservices.ui.settings.activities.AdServicesBaseActivity;
+
+/**
+ * Base Delegate class that helps activities that extend {@link AdServicesBaseActivity} to respond
+ * to all view model/user events. Currently supports:
+ *
+ * <ul>
+ * <li>common logging events
+ * </ul>
+ */
+public class BaseActionDelegate {
+ private final int mDeviceLoggingRegion;
+
+ protected enum ActionEnum {
+ MANAGE_TOPICS_SELECTED,
+ MANAGE_APPS_SELECTED,
+ RESET_TOPIC_SELECTED,
+ RESET_APP_SELECTED,
+ BLOCK_TOPIC_SELECTED,
+ UNBLOCK_TOPIC_SELECTED,
+ BLOCK_APP_SELECTED,
+ UNBLOCK_APP_SELECTED,
+ }
+
+ public BaseActionDelegate(Activity activity) {
+ mDeviceLoggingRegion =
+ DeviceRegionProvider.isEuDevice(activity)
+ ? AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU
+ : AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
+ }
+
+ protected void logUIAction(ActionEnum action) {
+ int rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__ACTION_UNSPECIFIED;
+ switch (action) {
+ case MANAGE_TOPICS_SELECTED:
+ rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__MANAGE_TOPICS_SELECTED;
+ break;
+ case MANAGE_APPS_SELECTED:
+ rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__MANAGE_APPS_SELECTED;
+ break;
+ case RESET_TOPIC_SELECTED:
+ rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__RESET_TOPIC_SELECTED;
+ break;
+ case RESET_APP_SELECTED:
+ rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__RESET_APP_SELECTED;
+ break;
+ case BLOCK_TOPIC_SELECTED:
+ rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__BLOCK_TOPIC_SELECTED;
+ break;
+ case UNBLOCK_TOPIC_SELECTED:
+ rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__UNBLOCK_TOPIC_SELECTED;
+ break;
+ case BLOCK_APP_SELECTED:
+ rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__BLOCK_APP_SELECTED;
+ break;
+ case UNBLOCK_APP_SELECTED:
+ rawAction = AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__UNBLOCK_APP_SELECTED;
+ break;
+ default:
+ Log.e("AdservicesUI", "Unknown Action for UI Logging");
+ }
+ logUIActionHelper(rawAction);
+ }
+
+ private void logUIActionHelper(int action) {
+ UIStats uiStats =
+ new UIStats.Builder()
+ .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
+ .setRegion(mDeviceLoggingRegion)
+ .setAction(action)
+ .build();
+ AdServicesLoggerImpl.getInstance().logUIStats(uiStats);
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/delegates/BlockedAppsActionDelegate.java b/adservices/apk/java/com/android/adservices/ui/settings/delegates/BlockedAppsActionDelegate.java
new file mode 100644
index 000000000..e9ea02578
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/delegates/BlockedAppsActionDelegate.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ui.settings.delegates;
+
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.lifecycle.Observer;
+
+import com.android.adservices.api.R;
+import com.android.adservices.service.PhFlags;
+import com.android.adservices.service.consent.App;
+import com.android.adservices.ui.settings.DialogManager;
+import com.android.adservices.ui.settings.activities.BlockedAppsActivity;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedAppsFragment;
+import com.android.adservices.ui.settings.viewmodels.BlockedAppsViewModel;
+import com.android.adservices.ui.settings.viewmodels.BlockedAppsViewModel.BlockedAppsViewModelUiEvent;
+
+import java.io.IOException;
+
+/**
+ * Delegate class that helps AdServices Settings fragments to respond to all view model/user events.
+ */
+public class BlockedAppsActionDelegate extends BaseActionDelegate {
+ private final BlockedAppsActivity mBlockedAppsActivity;
+ private final BlockedAppsViewModel mBlockedAppsViewModel;
+
+ public BlockedAppsActionDelegate(
+ BlockedAppsActivity blockedAppsActivity, BlockedAppsViewModel blockedAppsViewModel) {
+ super(blockedAppsActivity);
+ mBlockedAppsActivity = blockedAppsActivity;
+ mBlockedAppsViewModel = blockedAppsViewModel;
+ listenToBlockedAppsViewModelUiEvents();
+ }
+
+ private void listenToBlockedAppsViewModelUiEvents() {
+ Observer<Pair<BlockedAppsViewModelUiEvent, App>> observer =
+ eventAppPair -> {
+ if (eventAppPair == null) {
+ return;
+ }
+ BlockedAppsViewModelUiEvent event = eventAppPair.first;
+ App app = eventAppPair.second;
+ if (event == null) {
+ return;
+ }
+ try {
+ if (event == BlockedAppsViewModelUiEvent.RESTORE_APP) {
+ logUIAction(ActionEnum.UNBLOCK_APP_SELECTED);
+ mBlockedAppsViewModel.restoreAppConsent(app);
+ if (PhFlags.getInstance().getUIDialogsFeatureEnabled()) {
+ DialogManager.showUnblockAppDialog(mBlockedAppsActivity, app);
+ }
+ } else {
+ Log.e("AdservicesUI", "Unknown Action for UI Logging");
+ }
+ } catch (IOException e) {
+ Log.e(
+ "AdServicesUI",
+ "Error while processing AppsViewModelUiEvent " + event + ":" + e);
+ } finally {
+ mBlockedAppsViewModel.uiEventHandled();
+ }
+ };
+ mBlockedAppsViewModel.getUiEvents().observe(mBlockedAppsActivity, observer);
+ }
+
+ /**
+ * Configure all UI elements (except blocked apps list) in {@link
+ * AdServicesSettingsBlockedAppsFragment} to handle user actions.
+ */
+ public void initBlockedAppsFragment() {
+ mBlockedAppsActivity.setTitle(R.string.settingsUI_blocked_apps_title);
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/delegates/BlockedTopicsActionDelegate.java b/adservices/apk/java/com/android/adservices/ui/settings/delegates/BlockedTopicsActionDelegate.java
new file mode 100644
index 000000000..70b794f55
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/delegates/BlockedTopicsActionDelegate.java
@@ -0,0 +1,83 @@
+/*
+ * 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.ui.settings.delegates;
+
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.lifecycle.Observer;
+
+import com.android.adservices.api.R;
+import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.PhFlags;
+import com.android.adservices.ui.settings.DialogManager;
+import com.android.adservices.ui.settings.activities.BlockedTopicsActivity;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedTopicsFragment;
+import com.android.adservices.ui.settings.viewmodels.BlockedTopicsViewModel;
+import com.android.adservices.ui.settings.viewmodels.BlockedTopicsViewModel.BlockedTopicsViewModelUiEvent;
+
+/**
+ * Delegate class that helps AdServices Settings fragments to respond to all view model/user events.
+ */
+public class BlockedTopicsActionDelegate extends BaseActionDelegate {
+ private final BlockedTopicsActivity mBlockedTopicsActivity;
+ private final BlockedTopicsViewModel mBlockedTopicsViewModel;
+
+ public BlockedTopicsActionDelegate(
+ BlockedTopicsActivity blockedTopicsActivity,
+ BlockedTopicsViewModel blockedTopicsViewModel) {
+ super(blockedTopicsActivity);
+ mBlockedTopicsActivity = blockedTopicsActivity;
+ mBlockedTopicsViewModel = blockedTopicsViewModel;
+ listenToBlockedTopicsViewModelUiEvents();
+ }
+
+ private void listenToBlockedTopicsViewModelUiEvents() {
+ Observer<Pair<BlockedTopicsViewModelUiEvent, Topic>> observer =
+ eventTopicPair -> {
+ if (eventTopicPair == null) {
+ return;
+ }
+ BlockedTopicsViewModelUiEvent event = eventTopicPair.first;
+ Topic topic = eventTopicPair.second;
+ if (event == null) {
+ return;
+ }
+ try {
+ if (event == BlockedTopicsViewModelUiEvent.RESTORE_TOPIC) {
+ logUIAction(ActionEnum.UNBLOCK_TOPIC_SELECTED);
+ mBlockedTopicsViewModel.restoreTopicConsent(topic);
+ if (PhFlags.getInstance().getUIDialogsFeatureEnabled()) {
+ DialogManager.showUnblockTopicDialog(mBlockedTopicsActivity, topic);
+ }
+ } else {
+ Log.e("AdservicesUI", "Unknown Action for UI Logging");
+ }
+ } finally {
+ mBlockedTopicsViewModel.uiEventHandled();
+ }
+ };
+ mBlockedTopicsViewModel.getUiEvents().observe(mBlockedTopicsActivity, observer);
+ }
+
+ /**
+ * Configure all UI elements (except blocked topics list) in {@link
+ * AdServicesSettingsBlockedTopicsFragment} to handle user actions.
+ */
+ public void initBlockedTopicsFragment() {
+ mBlockedTopicsActivity.setTitle(R.string.settingsUI_blocked_topics_title);
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/delegates/MainActionDelegate.java b/adservices/apk/java/com/android/adservices/ui/settings/delegates/MainActionDelegate.java
new file mode 100644
index 000000000..a48b8e9b1
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/delegates/MainActionDelegate.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ui.settings.delegates;
+
+import android.content.Intent;
+import android.view.View;
+
+import androidx.lifecycle.Observer;
+
+import com.android.adservices.api.R;
+import com.android.adservices.service.PhFlags;
+import com.android.adservices.ui.settings.DialogManager;
+import com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity;
+import com.android.adservices.ui.settings.activities.AppsActivity;
+import com.android.adservices.ui.settings.activities.TopicsActivity;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsMainFragment;
+import com.android.adservices.ui.settings.viewmodels.MainViewModel;
+import com.android.settingslib.widget.MainSwitchBar;
+
+/**
+ * Delegate class that helps AdServices Settings fragments to respond to all view model/user events.
+ */
+public class MainActionDelegate extends BaseActionDelegate {
+ private final AdServicesSettingsMainActivity mAdservicesSettingsMainActivity;
+ private final MainViewModel mMainViewModel;
+
+ public MainActionDelegate(
+ AdServicesSettingsMainActivity mainSettingsActivity, MainViewModel mainViewModel) {
+ super(mainSettingsActivity);
+ mAdservicesSettingsMainActivity = mainSettingsActivity;
+ mMainViewModel = mainViewModel;
+
+ listenToMainViewModelUiEvents();
+ }
+
+ private void listenToMainViewModelUiEvents() {
+ Observer<MainViewModel.MainViewModelUiEvent> observer =
+ event -> {
+ if (event == null) {
+ return;
+ }
+ try {
+ switch (event) {
+ case SWITCH_ON_PRIVACY_SANDBOX_BETA:
+ mMainViewModel.setConsent(true);
+ break;
+ case SWITCH_OFF_PRIVACY_SANDBOX_BETA:
+ if (PhFlags.getInstance().getUIDialogsFeatureEnabled()) {
+ DialogManager.showOptOutDialog(
+ mAdservicesSettingsMainActivity, mMainViewModel);
+ } else {
+ mMainViewModel.setConsent(false);
+ }
+ break;
+ case DISPLAY_APPS_FRAGMENT:
+ logUIAction(ActionEnum.MANAGE_APPS_SELECTED);
+ mAdservicesSettingsMainActivity.startActivity(
+ new Intent(
+ mAdservicesSettingsMainActivity,
+ AppsActivity.class));
+ break;
+ case DISPLAY_TOPICS_FRAGMENT:
+ logUIAction(ActionEnum.MANAGE_TOPICS_SELECTED);
+ mAdservicesSettingsMainActivity.startActivity(
+ new Intent(
+ mAdservicesSettingsMainActivity,
+ TopicsActivity.class));
+ break;
+ }
+ } finally {
+ mMainViewModel.uiEventHandled();
+ }
+ };
+ mMainViewModel.getUiEvents().observe(mAdservicesSettingsMainActivity, observer);
+ }
+
+ /**
+ * Configure all UI elements in {@link AdServicesSettingsMainFragment} to handle user actions.
+ *
+ * @param fragment the fragment to be initialized.
+ */
+ public void initMainFragment(AdServicesSettingsMainFragment fragment) {
+ mAdservicesSettingsMainActivity.setTitle(R.string.settingsUI_main_view_title);
+ configureConsentSwitch(fragment);
+ configureTopicsButton(fragment);
+ configureAppsButton(fragment);
+ }
+
+ private void configureConsentSwitch(AdServicesSettingsMainFragment fragment) {
+ MainSwitchBar mainSwitchBar =
+ mAdservicesSettingsMainActivity.findViewById(R.id.main_switch_bar);
+
+ mMainViewModel.getConsent().observe(fragment, mainSwitchBar::setChecked);
+
+ mainSwitchBar.setOnClickListener(
+ switchBar -> mMainViewModel.consentSwitchClickHandler((MainSwitchBar) switchBar));
+ }
+
+ private void configureTopicsButton(AdServicesSettingsMainFragment fragment) {
+ View topicsButton = fragment.requireView().findViewById(R.id.topics_preference);
+
+ topicsButton.setOnClickListener(preference -> mMainViewModel.topicsButtonClickHandler());
+ }
+
+ private void configureAppsButton(AdServicesSettingsMainFragment fragment) {
+ View appsButton = fragment.requireView().findViewById(R.id.apps_preference);
+
+ appsButton.setOnClickListener(preference -> mMainViewModel.appsButtonClickHandler());
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/delegates/TopicsActionDelegate.java b/adservices/apk/java/com/android/adservices/ui/settings/delegates/TopicsActionDelegate.java
new file mode 100644
index 000000000..2d3e95754
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/delegates/TopicsActionDelegate.java
@@ -0,0 +1,119 @@
+/*
+ * 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.ui.settings.delegates;
+
+import android.content.Intent;
+import android.util.Pair;
+import android.view.View;
+
+import androidx.lifecycle.Observer;
+
+import com.android.adservices.api.R;
+import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.PhFlags;
+import com.android.adservices.ui.settings.DialogManager;
+import com.android.adservices.ui.settings.activities.BlockedTopicsActivity;
+import com.android.adservices.ui.settings.activities.TopicsActivity;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsTopicsFragment;
+import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
+import com.android.adservices.ui.settings.viewmodels.TopicsViewModel.TopicsViewModelUiEvent;
+
+/**
+ * Delegate class that helps AdServices Settings fragments to respond to all view model/user events.
+ */
+public class TopicsActionDelegate extends BaseActionDelegate {
+ private final TopicsActivity mTopicsActivity;
+ private final TopicsViewModel mTopicsViewModel;
+
+ public TopicsActionDelegate(TopicsActivity topicsActivity, TopicsViewModel topicsViewModel) {
+ super(topicsActivity);
+ mTopicsActivity = topicsActivity;
+ mTopicsViewModel = topicsViewModel;
+ listenToTopicsViewModelUiEvents();
+ }
+
+ private void listenToTopicsViewModelUiEvents() {
+ Observer<Pair<TopicsViewModelUiEvent, Topic>> observer =
+ eventTopicPair -> {
+ if (eventTopicPair == null) {
+ return;
+ }
+ TopicsViewModelUiEvent event = eventTopicPair.first;
+ Topic topic = eventTopicPair.second;
+ if (event == null) {
+ return;
+ }
+ try {
+ switch (event) {
+ case BLOCK_TOPIC:
+ logUIAction(ActionEnum.BLOCK_TOPIC_SELECTED);
+ if (PhFlags.getInstance().getUIDialogsFeatureEnabled()) {
+ DialogManager.showBlockTopicDialog(
+ mTopicsActivity, mTopicsViewModel, topic);
+ } else {
+ mTopicsViewModel.revokeTopicConsent(topic);
+ }
+ break;
+ case RESET_TOPICS:
+ logUIAction(ActionEnum.RESET_TOPIC_SELECTED);
+ if (PhFlags.getInstance().getUIDialogsFeatureEnabled()) {
+ DialogManager.showResetTopicDialog(
+ mTopicsActivity, mTopicsViewModel);
+ } else {
+ mTopicsViewModel.resetTopics();
+ }
+ break;
+ case DISPLAY_BLOCKED_TOPICS_FRAGMENT:
+ Intent intent =
+ new Intent(mTopicsActivity, BlockedTopicsActivity.class);
+ mTopicsActivity.startActivity(intent);
+ break;
+ }
+ } finally {
+ mTopicsViewModel.uiEventHandled();
+ }
+ };
+ mTopicsViewModel.getUiEvents().observe(mTopicsActivity, observer);
+ }
+
+ /**
+ * Configure all UI elements (except topics list) in {@link AdServicesSettingsTopicsFragment} to
+ * handle user actions.
+ */
+ public void initTopicsFragment(AdServicesSettingsTopicsFragment fragment) {
+ mTopicsActivity.setTitle(R.string.settingsUI_topics_view_title);
+ configureBlockedTopicsFragmentButton(fragment);
+ configureResetTopicsButton(fragment);
+ }
+
+ private void configureBlockedTopicsFragmentButton(AdServicesSettingsTopicsFragment fragment) {
+ View blockedTopicsButton = fragment.requireView().findViewById(R.id.blocked_topics_button);
+ View blockedTopicsWhenEmptyListButton =
+ fragment.requireView().findViewById(R.id.blocked_topics_when_empty_state_button);
+
+ blockedTopicsButton.setOnClickListener(
+ view -> mTopicsViewModel.blockedTopicsFragmentButtonClickHandler());
+ blockedTopicsWhenEmptyListButton.setOnClickListener(
+ view -> mTopicsViewModel.blockedTopicsFragmentButtonClickHandler());
+ }
+
+ private void configureResetTopicsButton(AdServicesSettingsTopicsFragment fragment) {
+ View resetTopicsButton = fragment.requireView().findViewById(R.id.reset_topics_button);
+
+ resetTopicsButton.setOnClickListener(
+ view -> mTopicsViewModel.resetTopicsButtonClickHandler());
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsAppsFragment.java b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsAppsFragment.java
index 1b866efb3..b40a1fe15 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsAppsFragment.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsAppsFragment.java
@@ -19,68 +19,113 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.adservices.api.R;
-import com.android.adservices.ui.settings.ActionDelegate;
-import com.android.adservices.ui.settings.AdServicesSettingsActivity;
+import com.android.adservices.service.consent.App;
+import com.android.adservices.ui.settings.activities.AppsActivity;
+import com.android.adservices.ui.settings.delegates.AppsActionDelegate;
import com.android.adservices.ui.settings.viewadatpors.AppsListViewAdapter;
import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
+import java.util.function.Function;
+
/** Fragment for the apps view of the AdServices Settings App. */
public class AdServicesSettingsAppsFragment extends Fragment {
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.apps_fragment, container, false);
-
- setupViewModel(rootView);
-
- return rootView;
+ return inflater.inflate(R.layout.apps_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+ setupViewModel(view);
initActionListeners();
}
- // initialize all action listeners except for actions in topics list
+ @Override
+ public void onResume() {
+ super.onResume();
+ AppsViewModel viewModel = new ViewModelProvider(requireActivity()).get(AppsViewModel.class);
+ viewModel.refresh();
+ }
+
+ // initialize all action listeners except for actions in apps list
private void initActionListeners() {
- ActionDelegate actionDelegate =
- ((AdServicesSettingsActivity) requireActivity()).getActionDelegate();
+ AppsActionDelegate actionDelegate = ((AppsActivity) requireActivity()).getActionDelegate();
actionDelegate.initAppsFragment(this);
}
- // initializes view model connection with topics list.
+ // initializes view model connection with apps list.
// (Action listeners for each item in the list will be handled by the adapter)
private void setupViewModel(View rootView) {
- AppsViewModel viewModel =
- ((AdServicesSettingsActivity) requireActivity())
- .getViewModelProvider()
- .get(AppsViewModel.class);
+ // create adapter
+ AppsViewModel viewModel = new ViewModelProvider(requireActivity()).get(AppsViewModel.class);
+ Function<App, View.OnClickListener> getOnclickListener =
+ app -> view -> viewModel.revokeAppConsentButtonClickHandler(app);
+ AppsListViewAdapter adapter =
+ new AppsListViewAdapter(
+ requireContext(), viewModel.getApps(), getOnclickListener, false);
+
+ // set adapter for recyclerView
RecyclerView recyclerView = rootView.findViewById(R.id.apps_list);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
- AppsListViewAdapter adapter = new AppsListViewAdapter(viewModel, false);
recyclerView.setAdapter(adapter);
View noAppsMessage = rootView.findViewById(R.id.no_apps_message);
View emptyAppsHiddenSection = rootView.findViewById(R.id.empty_apps_hidden_section);
+ View blockedAppsBtn = rootView.findViewById(R.id.blocked_apps_button);
+ // "Empty State": the state when the non-blocked list of apps/topics is empty.
+ // blocked_apps_when_empty_state_button is added to noAppsMessage
+ // noAppsMessages is visible only when Empty State
+ // blocked_apps_when_empty_state_button differs from blocked_apps_button
+ // in style with rounded corners, centered, colored
viewModel
.getApps()
.observe(
getViewLifecycleOwner(),
appsList -> {
- noAppsMessage.setVisibility(
- appsList.isEmpty() ? View.VISIBLE : View.GONE);
- emptyAppsHiddenSection.setVisibility(
- appsList.isEmpty() ? View.GONE : View.VISIBLE);
+ if (appsList.isEmpty()) {
+ noAppsMessage.setVisibility(View.VISIBLE);
+ emptyAppsHiddenSection.setVisibility(View.GONE);
+ blockedAppsBtn.setVisibility(View.GONE);
+ } else {
+ noAppsMessage.setVisibility(View.GONE);
+ emptyAppsHiddenSection.setVisibility(View.VISIBLE);
+ blockedAppsBtn.setVisibility(View.VISIBLE);
+ }
adapter.notifyDataSetChanged();
});
+
+ Button blockedAppsWhenEmptyStateButton =
+ rootView.findViewById(R.id.blocked_apps_when_empty_state_button);
+ viewModel
+ .getBlockedApps()
+ .observe(
+ getViewLifecycleOwner(),
+ blockedAppsList -> {
+ if (blockedAppsList.isEmpty()) {
+ blockedAppsWhenEmptyStateButton.setEnabled(false);
+ blockedAppsWhenEmptyStateButton.setAlpha(
+ getResources().getFloat(R.dimen.disabled_button_alpha));
+ blockedAppsWhenEmptyStateButton.setText(
+ R.string.settingsUI_apps_view_no_blocked_apps_text);
+ } else {
+ blockedAppsWhenEmptyStateButton.setEnabled(true);
+ blockedAppsWhenEmptyStateButton.setAlpha(
+ getResources().getFloat(R.dimen.enabled_button_alpha));
+ blockedAppsWhenEmptyStateButton.setText(
+ R.string.settingsUI_blocked_apps_title);
+ }
+ });
}
}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedAppsFragment.java b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedAppsFragment.java
index ccdb532d8..b5d603b24 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedAppsFragment.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedAppsFragment.java
@@ -21,15 +21,20 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.adservices.api.R;
-import com.android.adservices.ui.settings.ActionDelegate;
-import com.android.adservices.ui.settings.AdServicesSettingsActivity;
+import com.android.adservices.service.consent.App;
+import com.android.adservices.ui.settings.activities.BlockedAppsActivity;
+import com.android.adservices.ui.settings.delegates.BlockedAppsActionDelegate;
import com.android.adservices.ui.settings.viewadatpors.AppsListViewAdapter;
-import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
+import com.android.adservices.ui.settings.viewmodels.BlockedAppsViewModel;
+
+import java.util.function.Function;
/** Fragment for the blocked apps view of the AdServices Settings App. */
public class AdServicesSettingsBlockedAppsFragment extends Fragment {
@@ -37,37 +42,40 @@ public class AdServicesSettingsBlockedAppsFragment extends Fragment {
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.blocked_apps_fragment, container, false);
+ return inflater.inflate(R.layout.blocked_apps_fragment, container, false);
+ }
- setupViewModel(rootView);
+ @Override
+ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+ setupViewModel(view);
initActionListeners();
-
- return rootView;
}
// initialize all action listeners except for actions in blocked apps list
private void initActionListeners() {
- ActionDelegate actionDelegate =
- ((AdServicesSettingsActivity) requireActivity()).getActionDelegate();
+ BlockedAppsActionDelegate actionDelegate =
+ ((BlockedAppsActivity) requireActivity()).getActionDelegate();
actionDelegate.initBlockedAppsFragment();
}
- /**
- * Initializes view model connection with blocked apps list. (Action listeners for each item in
- * the list will be handled by the adapter).
- */
+ // initializes view model connection with blocked apps list.
+ // (Action listeners for each item in the list will be handled by the adapter)
private void setupViewModel(View rootView) {
- AppsViewModel viewModel =
- ((AdServicesSettingsActivity) requireActivity())
- .getViewModelProvider()
- .get(AppsViewModel.class);
+ // create adapter
+ BlockedAppsViewModel viewModel =
+ new ViewModelProvider(requireActivity()).get(BlockedAppsViewModel.class);
+ Function<App, View.OnClickListener> getOnclickListener =
+ app -> view -> viewModel.restoreAppConsentButtonClickHandler(app);
+ AppsListViewAdapter adapter =
+ new AppsListViewAdapter(
+ requireContext(), viewModel.getBlockedApps(), getOnclickListener, true);
+
+ // set adapter for recyclerView
RecyclerView recyclerView = rootView.findViewById(R.id.blocked_apps_list);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
- AppsListViewAdapter adapter = new AppsListViewAdapter(viewModel, true);
recyclerView.setAdapter(adapter);
View noBlockedAppsMessage = rootView.findViewById(R.id.no_blocked_apps_message);
-
viewModel
.getBlockedApps()
.observe(getViewLifecycleOwner(), blockedAppsList -> {
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedTopicsFragment.java b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedTopicsFragment.java
index 2a532183f..21a462704 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedTopicsFragment.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsBlockedTopicsFragment.java
@@ -21,15 +21,20 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.adservices.api.R;
-import com.android.adservices.ui.settings.ActionDelegate;
-import com.android.adservices.ui.settings.AdServicesSettingsActivity;
+import com.android.adservices.data.topics.Topic;
+import com.android.adservices.ui.settings.activities.BlockedTopicsActivity;
+import com.android.adservices.ui.settings.delegates.BlockedTopicsActionDelegate;
import com.android.adservices.ui.settings.viewadatpors.TopicsListViewAdapter;
-import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
+import com.android.adservices.ui.settings.viewmodels.BlockedTopicsViewModel;
+
+import java.util.function.Function;
/** Fragment for the blocked topics view of the AdServices Settings App. */
public class AdServicesSettingsBlockedTopicsFragment extends Fragment {
@@ -37,37 +42,40 @@ public class AdServicesSettingsBlockedTopicsFragment extends Fragment {
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.blocked_topics_fragment, container, false);
+ return inflater.inflate(R.layout.blocked_topics_fragment, container, false);
+ }
- setupViewModel(rootView);
+ @Override
+ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+ setupViewModel(view);
initActionListeners();
-
- return rootView;
}
// initialize all action listeners except for actions in blocked topics list
private void initActionListeners() {
- ActionDelegate actionDelegate =
- ((AdServicesSettingsActivity) requireActivity()).getActionDelegate();
+ BlockedTopicsActionDelegate actionDelegate =
+ ((BlockedTopicsActivity) requireActivity()).getActionDelegate();
actionDelegate.initBlockedTopicsFragment();
}
- /**
- * initializes view model connection with blocked topics list. (Action listeners for each item
- * in the list will be handled by the adapter)
- */
+ // initializes view model connection with blocked topics list.
+ // (Action listeners for each item in the list will be handled by the adapter)
private void setupViewModel(View rootView) {
- TopicsViewModel viewModel =
- ((AdServicesSettingsActivity) requireActivity())
- .getViewModelProvider()
- .get(TopicsViewModel.class);
+ // create adapter
+ BlockedTopicsViewModel viewModel =
+ new ViewModelProvider(requireActivity()).get(BlockedTopicsViewModel.class);
+ Function<Topic, View.OnClickListener> getOnclickListener =
+ topic -> view -> viewModel.restoreTopicConsentButtonClickHandler(topic);
+ TopicsListViewAdapter adapter =
+ new TopicsListViewAdapter(
+ requireContext(), viewModel.getBlockedTopics(), getOnclickListener, true);
+
+ // set adapter for recyclerView
RecyclerView recyclerView = rootView.findViewById(R.id.blocked_topics_list);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
- TopicsListViewAdapter adapter = new TopicsListViewAdapter(viewModel, true);
recyclerView.setAdapter(adapter);
View noBlockedTopicsMessage = rootView.findViewById(R.id.no_blocked_topics_message);
-
viewModel
.getBlockedTopics()
.observe(getViewLifecycleOwner(), blockedTopicsList -> {
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsMainFragment.java b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsMainFragment.java
index 0a28ca641..e469acc49 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsMainFragment.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsMainFragment.java
@@ -22,10 +22,11 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
import com.android.adservices.api.R;
-import com.android.adservices.ui.settings.ActionDelegate;
-import com.android.adservices.ui.settings.AdServicesSettingsActivity;
+import com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity;
+import com.android.adservices.ui.settings.delegates.MainActionDelegate;
import com.android.adservices.ui.settings.viewmodels.MainViewModel;
import com.android.settingslib.widget.MainSwitchBar;
@@ -53,20 +54,17 @@ public class AdServicesSettingsMainFragment extends Fragment {
// initialize all action listeners
private void initActionListeners() {
- ActionDelegate actionDelegate =
- ((AdServicesSettingsActivity) requireActivity()).getActionDelegate();
+ MainActionDelegate actionDelegate =
+ ((AdServicesSettingsMainActivity) requireActivity()).getActionDelegate();
actionDelegate.initMainFragment(this);
}
private void setupViewModel() {
- MainViewModel model =
- ((AdServicesSettingsActivity) requireActivity())
- .getViewModelProvider()
- .get(MainViewModel.class);
+ MainViewModel model = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
MainSwitchBar mainSwitchBar =
- Objects.requireNonNull(requireActivity().findViewById(R.id.main_switch_bar));
- View privacySandboxControls = requireActivity().findViewById(R.id.privacy_sandbox_controls);
+ Objects.requireNonNull(requireView().findViewById(R.id.main_switch_bar));
+ View privacySandboxControls = requireView().findViewById(R.id.privacy_sandbox_controls);
model.getConsent()
.observe(
getViewLifecycleOwner(),
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsTopicsFragment.java b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsTopicsFragment.java
index 897bc9b47..69b269f4a 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsTopicsFragment.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/fragments/AdServicesSettingsTopicsFragment.java
@@ -19,18 +19,23 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.adservices.api.R;
-import com.android.adservices.ui.settings.ActionDelegate;
-import com.android.adservices.ui.settings.AdServicesSettingsActivity;
+import com.android.adservices.data.topics.Topic;
+import com.android.adservices.ui.settings.activities.TopicsActivity;
+import com.android.adservices.ui.settings.delegates.TopicsActionDelegate;
import com.android.adservices.ui.settings.viewadatpors.TopicsListViewAdapter;
import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
+import java.util.function.Function;
+
/** Fragment for the topics view of the AdServices Settings App. */
public class AdServicesSettingsTopicsFragment extends Fragment {
@@ -46,38 +51,85 @@ public class AdServicesSettingsTopicsFragment extends Fragment {
initActionListeners();
}
+ @Override
+ public void onResume() {
+ super.onResume();
+ TopicsViewModel viewModel =
+ new ViewModelProvider(requireActivity()).get(TopicsViewModel.class);
+ viewModel.refresh();
+ }
+
// initialize all action listeners except for actions in topics list
private void initActionListeners() {
- ActionDelegate actionDelegate =
- ((AdServicesSettingsActivity) requireActivity()).getActionDelegate();
+ TopicsActionDelegate actionDelegate =
+ ((TopicsActivity) requireActivity()).getActionDelegate();
actionDelegate.initTopicsFragment(this);
}
// initializes view model connection with topics list.
// (Action listeners for each item in the list will be handled by the adapter)
private void setupViewModel(View rootView) {
+ // create adapter
TopicsViewModel viewModel =
- ((AdServicesSettingsActivity) requireActivity())
- .getViewModelProvider()
- .get(TopicsViewModel.class);
+ new ViewModelProvider(requireActivity()).get(TopicsViewModel.class);
+ Function<Topic, View.OnClickListener> getOnclickListener =
+ topic -> view -> viewModel.revokeTopicConsentButtonClickHandler(topic);
+ TopicsListViewAdapter adapter =
+ new TopicsListViewAdapter(
+ requireContext(), viewModel.getTopics(), getOnclickListener, false);
+
+ // set adapter for recyclerView
RecyclerView recyclerView = rootView.findViewById(R.id.topics_list);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
- TopicsListViewAdapter adapter = new TopicsListViewAdapter(viewModel, false);
recyclerView.setAdapter(adapter);
View noTopicsMessage = rootView.findViewById(R.id.no_topics_message);
View emptyTopicsHiddenSection = rootView.findViewById(R.id.empty_topics_hidden_section);
+ View blockedTopicsButton = rootView.findViewById(R.id.blocked_topics_button);
+ // "Empty State": the state when the non-blocked list of apps/topics is empty.
+ // blocked_apps_when_empty_state_button is added to noTopicsMessage
+ // noTopicsMessages is visible only when Empty State
+ // blocked_topics_when_empty_state_button differs from blocked_topics_button
+ // in style with rounded corners, centered, colored
viewModel
.getTopics()
.observe(
getViewLifecycleOwner(),
topicsList -> {
- noTopicsMessage.setVisibility(
- topicsList.isEmpty() ? View.VISIBLE : View.GONE);
- emptyTopicsHiddenSection.setVisibility(
- topicsList.isEmpty() ? View.GONE : View.VISIBLE);
+ if (topicsList.isEmpty()) {
+ noTopicsMessage.setVisibility(View.VISIBLE);
+ emptyTopicsHiddenSection.setVisibility(View.GONE);
+ blockedTopicsButton.setVisibility(View.GONE);
+ } else {
+ noTopicsMessage.setVisibility(View.GONE);
+ emptyTopicsHiddenSection.setVisibility(View.VISIBLE);
+ blockedTopicsButton.setVisibility(View.VISIBLE);
+ }
adapter.notifyDataSetChanged();
});
+
+ // locked_topics_when_empty_state_button is disabled if there is no blocked topics
+ Button blockedTopicsWhenEmptyStateButton =
+ rootView.findViewById(R.id.blocked_topics_when_empty_state_button);
+ viewModel
+ .getBlockedTopics()
+ .observe(
+ getViewLifecycleOwner(),
+ blockedTopicsList -> {
+ if (blockedTopicsList.isEmpty()) {
+ blockedTopicsWhenEmptyStateButton.setEnabled(false);
+ blockedTopicsWhenEmptyStateButton.setAlpha(
+ getResources().getFloat(R.dimen.disabled_button_alpha));
+ blockedTopicsWhenEmptyStateButton.setText(
+ R.string.settingsUI_topics_view_no_blocked_topics_text);
+ } else {
+ blockedTopicsWhenEmptyStateButton.setEnabled(true);
+ blockedTopicsWhenEmptyStateButton.setAlpha(
+ getResources().getFloat(R.dimen.enabled_button_alpha));
+ blockedTopicsWhenEmptyStateButton.setText(
+ R.string.settingsUI_blocked_topics_title);
+ }
+ });
}
}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/AppsListViewAdapter.java b/adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/AppsListViewAdapter.java
index 652ff5c4e..7b86933e2 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/AppsListViewAdapter.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/AppsListViewAdapter.java
@@ -15,6 +15,7 @@
*/
package com.android.adservices.ui.settings.viewadatpors;
+import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,11 +32,11 @@ import com.android.adservices.api.R;
import com.android.adservices.service.consent.App;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsAppsFragment;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedAppsFragment;
-import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
import com.google.common.collect.ImmutableList;
import java.util.Objects;
+import java.util.function.Function;
/**
* ViewAdapter to handle data binding for the list of {@link App}s on {@link
@@ -43,14 +44,19 @@ import java.util.Objects;
* AdServicesSettingsBlockedAppsFragment}.
*/
public class AppsListViewAdapter extends RecyclerView.Adapter {
-
- private final AppsViewModel mViewModel;
+ private final Context mContext;
+ private final Function<App, View.OnClickListener> mGetOnclickListener;
private final LiveData<ImmutableList<App>> mAppsList;
private final boolean mIsBlockedAppsList;
- public AppsListViewAdapter(AppsViewModel viewModel, boolean isBlockedAppsList) {
- mViewModel = viewModel;
- mAppsList = isBlockedAppsList ? viewModel.getBlockedApps() : viewModel.getApps();
+ public AppsListViewAdapter(
+ Context context,
+ LiveData<ImmutableList<App>> appsList,
+ Function<App, View.OnClickListener> getOnclickListener,
+ boolean isBlockedAppsList) {
+ mContext = context;
+ mAppsList = appsList;
+ mGetOnclickListener = getOnclickListener;
mIsBlockedAppsList = isBlockedAppsList;
}
@@ -58,15 +64,18 @@ public class AppsListViewAdapter extends RecyclerView.Adapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
- return new AppsViewHolder(view);
+ return new com.android.adservices.ui.settings.viewadatpors.AppsListViewAdapter
+ .AppsViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
- ((AppsViewHolder) holder)
+ ((com.android.adservices.ui.settings.viewadatpors.AppsListViewAdapter.AppsViewHolder)
+ holder)
.initAppItem(
+ mContext,
+ mGetOnclickListener,
Objects.requireNonNull(mAppsList.getValue()).get(position),
- mViewModel,
mIsBlockedAppsList);
}
@@ -82,7 +91,6 @@ public class AppsListViewAdapter extends RecyclerView.Adapter {
/** ViewHolder to display the text for an app item */
public static class AppsViewHolder extends RecyclerView.ViewHolder {
-
private final TextView mAppTextView;
private final Button mOptionButtonView;
private final ImageView mImageView;
@@ -94,32 +102,28 @@ public class AppsListViewAdapter extends RecyclerView.Adapter {
mImageView = itemView.findViewById(R.id.app_icon);
}
- /** Set the human readable string for the app and listener for block app logic. */
- public void initAppItem(App app, AppsViewModel viewModel, boolean mIsBlockedAppsListItem) {
- prepareAppName(app, viewModel);
- prepareAppImageView(app, viewModel);
+ /** Set the human readable string for the app and listener for block/unblock app logic. */
+ public void initAppItem(
+ Context context,
+ Function<App, View.OnClickListener> getOnclickListener,
+ App app,
+ boolean mIsBlockedAppsListItem) {
+ prepareAppName(app, context);
+ prepareAppImageView(app, context);
if (mIsBlockedAppsListItem) {
mOptionButtonView.setText(R.string.settingsUI_unblock_app_title);
- mOptionButtonView.setOnClickListener(
- view -> {
- viewModel.restoreAppConsentButtonClickHandler(app);
- });
} else {
mOptionButtonView.setText(R.string.settingsUI_block_app_title);
- mOptionButtonView.setOnClickListener(
- view -> {
- viewModel.revokeAppConsentButtonClickHandler(app);
- });
}
+ mOptionButtonView.setOnClickListener(getOnclickListener.apply(app));
}
- private void prepareAppName(App app, AppsViewModel viewModel) {
- mAppTextView.setText(
- app.getAppDisplayName(viewModel.getApplication().getPackageManager()));
+ private void prepareAppName(App app, Context context) {
+ mAppTextView.setText(app.getAppDisplayName(context.getPackageManager()));
}
- private void prepareAppImageView(App app, AppsViewModel viewModel) {
- Drawable appIcon = app.getAppIcon(viewModel.getApplication().getApplicationContext());
+ private void prepareAppImageView(App app, Context context) {
+ Drawable appIcon = app.getAppIcon(context);
if (appIcon != null) {
mImageView.setImageDrawable(appIcon);
}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/TopicsListViewAdapter.java b/adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/TopicsListViewAdapter.java
index 30aa9cf0b..c82a477cb 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/TopicsListViewAdapter.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/viewadatpors/TopicsListViewAdapter.java
@@ -18,6 +18,7 @@ package com.android.adservices.ui.settings.viewadatpors;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
@@ -31,11 +32,11 @@ import com.android.adservices.data.topics.Topic;
import com.android.adservices.service.topics.TopicsMapper;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedTopicsFragment;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsTopicsFragment;
-import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
import com.google.common.collect.ImmutableList;
import java.util.Objects;
+import java.util.function.Function;
/**
* ViewAdapter to handle data binding for the list of {@link Topic}s on {@link
@@ -43,14 +44,19 @@ import java.util.Objects;
* AdServicesSettingsBlockedTopicsFragment}.
*/
public class TopicsListViewAdapter extends RecyclerView.Adapter {
-
- private final TopicsViewModel mViewModel;
+ private final Context mContext;
+ private final Function<Topic, OnClickListener> mGetOnclickListener;
private final LiveData<ImmutableList<Topic>> mTopicsList;
private final boolean mIsBlockedTopicsList;
- public TopicsListViewAdapter(TopicsViewModel viewModel, boolean isBlockedTopicsList) {
- mViewModel = viewModel;
- mTopicsList = isBlockedTopicsList ? viewModel.getBlockedTopics() : viewModel.getTopics();
+ public TopicsListViewAdapter(
+ Context context,
+ LiveData<ImmutableList<Topic>> topicsList,
+ Function<Topic, OnClickListener> getOnclickListener,
+ boolean isBlockedTopicsList) {
+ mContext = context;
+ mTopicsList = topicsList;
+ mGetOnclickListener = getOnclickListener;
mIsBlockedTopicsList = isBlockedTopicsList;
}
@@ -65,10 +71,10 @@ public class TopicsListViewAdapter extends RecyclerView.Adapter {
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
((TopicsViewHolder) holder)
.initTopicItem(
+ mContext,
+ mGetOnclickListener,
Objects.requireNonNull(mTopicsList.getValue()).get(position),
- mViewModel,
- mIsBlockedTopicsList,
- mViewModel.getApplication().getApplicationContext());
+ mIsBlockedTopicsList);
}
@Override
@@ -95,11 +101,10 @@ public class TopicsListViewAdapter extends RecyclerView.Adapter {
/** Set the human readable string for the topic and listener for block topic logic. */
public void initTopicItem(
+ Context context,
+ Function<Topic, OnClickListener> getOnclickListener,
Topic topic,
- TopicsViewModel viewModel,
- boolean mIsBlockedTopicsListItem,
- Context context) {
- // TODO(b/234655984): show readable string of topic
+ boolean mIsBlockedTopicsListItem) {
int resourceId = TopicsMapper.getResourceIdByTopic(topic, context);
if (resourceId == 0) {
throw new IllegalArgumentException(
@@ -108,17 +113,10 @@ public class TopicsListViewAdapter extends RecyclerView.Adapter {
mTopicTextView.setText(resourceId);
if (mIsBlockedTopicsListItem) {
mOptionButtonView.setText(R.string.settingsUI_unblock_topic_title);
- mOptionButtonView.setOnClickListener(
- view -> {
- viewModel.restoreTopicConsentButtonClickHandler(topic);
- });
} else {
mOptionButtonView.setText(R.string.settingsUI_block_topic_title);
- mOptionButtonView.setOnClickListener(
- view -> {
- viewModel.revokeTopicConsentButtonClickHandler(topic);
- });
}
+ mOptionButtonView.setOnClickListener(getOnclickListener.apply(topic));
}
}
}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/AppsViewModel.java b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/AppsViewModel.java
index a57c66128..1f5569861 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/AppsViewModel.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/AppsViewModel.java
@@ -26,7 +26,6 @@ import androidx.lifecycle.MutableLiveData;
import com.android.adservices.service.consent.App;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsAppsFragment;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedAppsFragment;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -49,7 +48,6 @@ public class AppsViewModel extends AndroidViewModel {
/** UI event triggered by view model */
public enum AppsViewModelUiEvent {
BLOCK_APP,
- RESTORE_APP,
RESET_APPS,
DISPLAY_BLOCKED_APPS_FRAGMENT,
}
@@ -71,21 +69,25 @@ public class AppsViewModel extends AndroidViewModel {
mBlockedApps = new MutableLiveData<>(getBlockedAppsFromConsentManager());
}
- // ---------------------------------------------------------------------------------------------
- // Apps View
- // ---------------------------------------------------------------------------------------------
-
/**
* Provides the apps displayed in {@link AdServicesSettingsAppsFragment}.
*
- * @return {@link mApps} a list of apps that represents the apps that use specific interest
- * groups.
+ * @return A list of {@link App}s that represents apps that use FLEDGE.
*/
public LiveData<ImmutableList<App>> getApps() {
return mApps;
}
/**
+ * Provides the blocked apps list.
+ *
+ * @return a list of apps that represents the user's blocked interests.
+ */
+ public LiveData<ImmutableList<App>> getBlockedApps() {
+ return mBlockedApps;
+ }
+
+ /**
* Revoke the consent for the specified app (i.e. block the app).
*
* @param app the app to be blocked.
@@ -111,34 +113,6 @@ public class AppsViewModel extends AndroidViewModel {
mApps.postValue(getAppsFromConsentManager());
}
- // ---------------------------------------------------------------------------------------------
- // Blocked Apps View
- // ---------------------------------------------------------------------------------------------
-
- /**
- * Provides the blocked apps displayed in {@link AdServicesSettingsBlockedAppsFragment}.
- *
- * @return {@link mBlockedApps} a list of apps that represents the apps the user has blocked
- * from generating specific interest groups.
- */
- public LiveData<ImmutableList<App>> getBlockedApps() {
- return mBlockedApps;
- }
-
- /**
- * Restore the consent for the specified app (i.e. unblock the app).
- *
- * @param app the app to be restored.
- */
- public void restoreAppConsent(App app) throws IOException {
- mConsentManager.restoreConsentForApp(app);
- refresh();
- }
-
- // ---------------------------------------------------------------------------------------------
- // Action Handlers
- // ---------------------------------------------------------------------------------------------
-
/** Returns an observable but immutable event enum representing an view action on UI. */
public LiveData<Pair<AppsViewModelUiEvent, App>> getUiEvents() {
return mEventTrigger;
@@ -162,16 +136,6 @@ public class AppsViewModel extends AndroidViewModel {
mEventTrigger.postValue(new Pair<>(AppsViewModelUiEvent.BLOCK_APP, app));
}
- /**
- * Triggers the block of the specified app in the list of apps in {@link
- * AdServicesSettingsAppsFragment}.
- *
- * @param app the app to be blocked.
- */
- public void restoreAppConsentButtonClickHandler(App app) {
- mEventTrigger.postValue(new Pair<>(AppsViewModelUiEvent.RESTORE_APP, app));
- }
-
/** Triggers a reset of all apps related data. */
public void resetAppsButtonClickHandler() {
mEventTrigger.postValue(new Pair<>(AppsViewModelUiEvent.RESET_APPS, null));
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/BlockedAppsViewModel.java b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/BlockedAppsViewModel.java
new file mode 100644
index 000000000..6f0895fae
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/BlockedAppsViewModel.java
@@ -0,0 +1,122 @@
+/*
+ * 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.ui.settings.viewmodels;
+
+import android.app.Application;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.adservices.service.consent.App;
+import com.android.adservices.service.consent.ConsentManager;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsAppsFragment;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedAppsFragment;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+
+import java.io.IOException;
+
+/**
+ * View model for the apps view and blocked apps view of the AdServices Settings App. This view
+ * model is responsible for serving apps to the apps view and blocked apps view, and interacting
+ * with the {@link ConsentManager} that persists and changes the apps data in a storage.
+ */
+public class BlockedAppsViewModel extends AndroidViewModel {
+
+ private final MutableLiveData<Pair<BlockedAppsViewModelUiEvent, App>> mEventTrigger =
+ new MutableLiveData<>();
+ private final MutableLiveData<ImmutableList<App>> mBlockedApps;
+ private final ConsentManager mConsentManager;
+
+ /** UI event triggered by view model */
+ public enum BlockedAppsViewModelUiEvent {
+ RESTORE_APP,
+ }
+
+ public BlockedAppsViewModel(@NonNull Application application) {
+ super(application);
+
+ mConsentManager = ConsentManager.getInstance(application);
+ mBlockedApps = new MutableLiveData<>(getBlockedAppsFromConsentManager());
+ }
+
+ @VisibleForTesting
+ public BlockedAppsViewModel(@NonNull Application application, ConsentManager consentManager) {
+ super(application);
+
+ mConsentManager = consentManager;
+ mBlockedApps = new MutableLiveData<>(getBlockedAppsFromConsentManager());
+ }
+
+ /**
+ * Provides the blocked apps displayed in {@link AdServicesSettingsBlockedAppsFragment}.
+ *
+ * @return a list of apps that represents the user's blocked interests.
+ */
+ public LiveData<ImmutableList<App>> getBlockedApps() {
+ return mBlockedApps;
+ }
+
+ /**
+ * Restore the consent for the specified app (i.e. unblock the app).
+ *
+ * @param app the app to be restored.
+ */
+ public void restoreAppConsent(App app) throws IOException {
+ mConsentManager.restoreConsentForApp(app);
+ refresh();
+ }
+
+ /**
+ * Reads all the data from {@link ConsentManager}.
+ *
+ * <p>TODO(b/238387560): To be moved to private when is fixed.
+ */
+ public void refresh() {
+ mBlockedApps.postValue(getBlockedAppsFromConsentManager());
+ }
+
+ /** Returns an observable but immutable event enum representing a view action on UI. */
+ public LiveData<Pair<BlockedAppsViewModelUiEvent, App>> getUiEvents() {
+ return mEventTrigger;
+ }
+
+ /**
+ * Sets the UI Event as handled so the action will not be handled again if activity is
+ * recreated.
+ */
+ public void uiEventHandled() {
+ mEventTrigger.postValue(new Pair<>(null, null));
+ }
+
+ /**
+ * Triggers the block of the specified app in the list of apps in {@link
+ * AdServicesSettingsAppsFragment}.
+ *
+ * @param app the app to be blocked.
+ */
+ public void restoreAppConsentButtonClickHandler(App app) {
+ mEventTrigger.postValue(new Pair<>(BlockedAppsViewModelUiEvent.RESTORE_APP, app));
+ }
+
+ private ImmutableList<App> getBlockedAppsFromConsentManager() {
+ return mConsentManager.getAppsWithRevokedConsent();
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/BlockedTopicsViewModel.java b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/BlockedTopicsViewModel.java
new file mode 100644
index 000000000..a5ff60527
--- /dev/null
+++ b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/BlockedTopicsViewModel.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ui.settings.viewmodels;
+
+import android.app.Application;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.consent.ConsentManager;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedTopicsFragment;
+import com.android.adservices.ui.settings.fragments.AdServicesSettingsTopicsFragment;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * View model for the topics view and blocked topics view of the AdServices Settings App. This view
+ * model is responsible for serving topics to the topics view and blocked topics view, and
+ * interacting with the {@link ConsentManager} that persists and changes the topics data in a
+ * storage.
+ */
+public class BlockedTopicsViewModel extends AndroidViewModel {
+
+ private final MutableLiveData<Pair<BlockedTopicsViewModelUiEvent, Topic>> mEventTrigger =
+ new MutableLiveData<>();
+ private final MutableLiveData<ImmutableList<Topic>> mBlockedTopics;
+ private final ConsentManager mConsentManager;
+
+ /** UI event triggered by view model */
+ public enum BlockedTopicsViewModelUiEvent {
+ RESTORE_TOPIC,
+ }
+
+ public BlockedTopicsViewModel(@NonNull Application application) {
+ super(application);
+
+ mConsentManager = ConsentManager.getInstance(application);
+ mBlockedTopics = new MutableLiveData<>(getBlockedTopicsFromConsentManager());
+ }
+
+ @VisibleForTesting
+ public BlockedTopicsViewModel(@NonNull Application application, ConsentManager consentManager) {
+ super(application);
+
+ mConsentManager = consentManager;
+ mBlockedTopics = new MutableLiveData<>(getBlockedTopicsFromConsentManager());
+ }
+
+ /**
+ * Provides the blocked topics displayed in {@link AdServicesSettingsBlockedTopicsFragment}.
+ *
+ * @return a list of topics that represents the user's blocked interests.
+ */
+ public LiveData<ImmutableList<Topic>> getBlockedTopics() {
+ return mBlockedTopics;
+ }
+
+ /**
+ * Restore the consent for the specified topic (i.e. unblock the topic).
+ *
+ * @param topic the topic to be restored.
+ */
+ public void restoreTopicConsent(Topic topic) {
+ mConsentManager.restoreConsentForTopic(topic);
+ refresh();
+ }
+
+ /**
+ * Reads all the data from {@link ConsentManager}.
+ *
+ * <p>TODO(b/238387560): To be moved to private when is fixed.
+ */
+ public void refresh() {
+ mBlockedTopics.postValue(getBlockedTopicsFromConsentManager());
+ }
+
+ /** Returns an observable but immutable event enum representing a view action on UI. */
+ public LiveData<Pair<BlockedTopicsViewModelUiEvent, Topic>> getUiEvents() {
+ return mEventTrigger;
+ }
+
+ /**
+ * Sets the UI Event as handled so the action will not be handled again if activity is
+ * recreated.
+ */
+ public void uiEventHandled() {
+ mEventTrigger.postValue(new Pair<>(null, null));
+ }
+
+ /**
+ * Triggers the block of the specified topic in the list of topics in {@link
+ * AdServicesSettingsTopicsFragment}.
+ *
+ * @param topic the topic to be blocked.
+ */
+ public void restoreTopicConsentButtonClickHandler(Topic topic) {
+ mEventTrigger.postValue(new Pair<>(BlockedTopicsViewModelUiEvent.RESTORE_TOPIC, topic));
+ }
+
+ private ImmutableList<Topic> getBlockedTopicsFromConsentManager() {
+ return mConsentManager.getTopicsWithRevokedConsent();
+ }
+}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/MainViewModel.java b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/MainViewModel.java
index ac1810484..cc1cbccaa 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/MainViewModel.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/MainViewModel.java
@@ -27,6 +27,7 @@ import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsAppsFragment;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsMainFragment;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsTopicsFragment;
+import com.android.settingslib.widget.MainSwitchBar;
/**
* View model for the main view of the AdServices Settings App. This view model is responsible
@@ -104,16 +105,21 @@ public class MainViewModel extends AndroidViewModel {
mEventTrigger.postValue(MainViewModelUiEvent.DISPLAY_APPS_FRAGMENT);
}
- /** Triggers {@link AdServicesSettingsTopicsFragment}. */
- public void consentSwitchClickHandler(Boolean newConsentValue) {
- if (newConsentValue) {
+ /**
+ * Triggers opt out process for Privacy Sandbox. Also reverts the switch state, since
+ * confirmation dialog will handle switch change.
+ */
+ public void consentSwitchClickHandler(MainSwitchBar mainSwitchBar) {
+ if (mainSwitchBar.isChecked()) {
+ mainSwitchBar.setChecked(false);
mEventTrigger.postValue(MainViewModelUiEvent.SWITCH_ON_PRIVACY_SANDBOX_BETA);
} else {
+ mainSwitchBar.setChecked(true);
mEventTrigger.postValue(MainViewModelUiEvent.SWITCH_OFF_PRIVACY_SANDBOX_BETA);
}
}
private boolean getConsentFromConsentManager() {
- return mConsentManager.getConsent(getApplication().getPackageManager()).isGiven();
+ return mConsentManager.getConsent().isGiven();
}
}
diff --git a/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/TopicsViewModel.java b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/TopicsViewModel.java
index fb6f50a85..3eb7e1b7f 100644
--- a/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/TopicsViewModel.java
+++ b/adservices/apk/java/com/android/adservices/ui/settings/viewmodels/TopicsViewModel.java
@@ -25,7 +25,6 @@ import androidx.lifecycle.MutableLiveData;
import com.android.adservices.data.topics.Topic;
import com.android.adservices.service.consent.ConsentManager;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsBlockedTopicsFragment;
import com.android.adservices.ui.settings.fragments.AdServicesSettingsTopicsFragment;
import com.google.common.annotations.VisibleForTesting;
@@ -48,7 +47,6 @@ public class TopicsViewModel extends AndroidViewModel {
/** UI event triggered by view model */
public enum TopicsViewModelUiEvent {
BLOCK_TOPIC,
- RESTORE_TOPIC,
RESET_TOPICS,
DISPLAY_BLOCKED_TOPICS_FRAGMENT,
}
@@ -70,20 +68,25 @@ public class TopicsViewModel extends AndroidViewModel {
mBlockedTopics = new MutableLiveData<>(getBlockedTopicsFromConsentManager());
}
- // ---------------------------------------------------------------------------------------------
- // Topics View
- // ---------------------------------------------------------------------------------------------
-
/**
* Provides the topics displayed in {@link AdServicesSettingsTopicsFragment}.
*
- * @return {@link mTopics} a list of topics that represents the user's interests.
+ * @return A list of {@link Topic}s that represents the user's interests.
*/
public LiveData<ImmutableList<Topic>> getTopics() {
return mTopics;
}
/**
+ * Provides the blocked topics list.
+ *
+ * @return a list of topics that represents the user's blocked interests.
+ */
+ public LiveData<ImmutableList<Topic>> getBlockedTopics() {
+ return mBlockedTopics;
+ }
+
+ /**
* Revoke the consent for the specified topic (i.e. block the topic).
*
* @param topic the topic to be blocked.
@@ -109,33 +112,6 @@ public class TopicsViewModel extends AndroidViewModel {
mTopics.postValue(getTopicsFromConsentManager());
}
- // ---------------------------------------------------------------------------------------------
- // Blocked Topics View
- // ---------------------------------------------------------------------------------------------
-
- /**
- * Provides the blocked topics displayed in {@link AdServicesSettingsBlockedTopicsFragment}.
- *
- * @return {@link mBlockedTopics} a list of topics that represents the user's blocked interests.
- */
- public LiveData<ImmutableList<Topic>> getBlockedTopics() {
- return mBlockedTopics;
- }
-
- /**
- * Restore the consent for the specified topic (i.e. unblock the topic).
- *
- * @param topic the topic to be restored.
- */
- public void restoreTopicConsent(Topic topic) {
- mConsentManager.restoreConsentForTopic(topic);
- refresh();
- }
-
- // ---------------------------------------------------------------------------------------------
- // Action Handlers
- // ---------------------------------------------------------------------------------------------
-
/** Returns an observable but immutable event enum representing an view action on UI. */
public LiveData<Pair<TopicsViewModelUiEvent, Topic>> getUiEvents() {
return mEventTrigger;
@@ -159,16 +135,6 @@ public class TopicsViewModel extends AndroidViewModel {
mEventTrigger.postValue(new Pair<>(TopicsViewModelUiEvent.BLOCK_TOPIC, topic));
}
- /**
- * Triggers the block of the specified topic in the list of topics in {@link
- * AdServicesSettingsTopicsFragment}.
- *
- * @param topic the topic to be blocked.
- */
- public void restoreTopicConsentButtonClickHandler(Topic topic) {
- mEventTrigger.postValue(new Pair<>(TopicsViewModelUiEvent.RESTORE_TOPIC, topic));
- }
-
/** Triggers a reset of all topics related data. */
public void resetTopicsButtonClickHandler() {
mEventTrigger.postValue(new Pair<>(TopicsViewModelUiEvent.RESET_TOPICS, null));
diff --git a/adservices/apk/res/drawable-night/ic_main_view_image.xml b/adservices/apk/res/drawable-night/ic_main_view_image.xml
new file mode 100644
index 000000000..fee500955
--- /dev/null
+++ b/adservices/apk/res/drawable-night/ic_main_view_image.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="412"
+ android:viewportHeight="300"
+ android:width="412dp"
+ android:height="300dp">
+ <path
+ android:pathData="M384.17937 300H27.82062C12.5299 300 0 287.17393 0 271.52174V28.47826C0 12.82609 12.5299 0 27.82062 0H384.28556C399.47012 0 412 12.82609 412 28.47826V271.63043C412 287.17393 399.47012 300 384.17937 300Z"
+ android:fillColor="#000000" />
+ <path
+ android:pathData="M275.13055 97.4248V87.91541a2.37726 2.37726 0 0 0 -2.37725 -2.37714v-49.924a11.901 11.901 0 0 0 -11.8866 -11.8877H151.50824a11.901 11.901 0 0 0 -11.88654 11.8877V263.83881a11.901 11.901 0 0 0 11.88654 11.88775H260.8667a11.901 11.901 0 0 0 11.8866 -11.88775V147.34906a2.37731 2.37731 0 0 0 2.37725 -2.3772V121.19849a2.37755 2.37755 0 0 0 -2.37725 -2.37751V99.80225A2.37753 2.37753 0 0 0 275.13055 97.4248Zm-4.75506 166.414a9.52023 9.52023 0 0 1 -9.50879 9.50994H151.50824a9.52023 9.52023 0 0 1 -9.50879 -9.50994V35.61426a9.52028 9.52028 0 0 1 9.50879 -9.50995H260.8667a9.52028 9.52028 0 0 1 9.50879 9.51Z"
+ android:fillColor="#80868B" />
+ <path
+ android:pathData="M178.5051 231.569H282.7454A5.24519 5.24519 0 0 1 287.9906 236.8142V258.7619A5.24519 5.24519 0 0 1 282.7454 264.0071H178.5051A5.24519 5.24519 0 0 1 173.2599 258.7619V236.8142A5.24519 5.24519 0 0 1 178.5051 231.569Z"
+ android:fillColor="#FFEFBD" />
+ <path
+ android:pathData="M182.39078 251.63082l1.18675 -1.18675 2.82559 0.39558 4.40792 -4.40791 -8.81584 -4.80351 1.58233 -1.58233 10.79375 2.76908 4.40792 -4.35141a1.67849 1.67849 0 1 1 2.37349 2.3735l-4.3514 4.40792 2.76907 10.79375 -1.58233 1.58233 -4.8035 -8.81584 -4.40792 4.40792 0.39558 2.82559 -1.18674 1.18675 -1.97792 -3.61676Z"
+ android:fillColor="#FFA800" />
+ <path
+ android:pathData="M117.7704 119.245H222.0106A5.24519 5.24519 0 0 1 227.2558 124.4902V228.7304A5.24519 5.24519 0 0 1 222.0106 233.9756H117.7704A5.24519 5.24519 0 0 1 112.5252 228.7304V124.4902A5.24519 5.24519 0 0 1 117.7704 119.245Z"
+ android:fillColor="#FFD0CE" />
+ <path
+ android:pathData="M150.9397 199.13555a3.41561 3.41561 0 0 1 -2.09813 -0.71066 3.50012 3.50012 0 0 1 -1.28595 -1.86124l-7.58033 -27.34333a1.73429 1.73429 0 0 1 0.30456 -1.75972 2.08759 2.08759 0 0 1 1.72588 -0.81218H155.6774l11.91195 -17.73256a2.55073 2.55073 0 0 1 0.94754 -0.87986 2.61249 2.61249 0 0 1 2.5719 0 2.55073 2.55073 0 0 1 0.94754 0.87986l11.91195 17.73256h13.807a2.08759 2.08759 0 0 1 1.72588 0.81218 1.73427 1.73427 0 0 1 0.30456 1.75972l-7.58032 27.34333a3.50023 3.50023 0 0 1 -1.28595 1.86124 3.41563 3.41563 0 0 1 -2.09813 0.71066Zm11.30281 -32.48713h15.22834l-7.648 -11.3705Zm-15.90516 5.41452H193.4437l-5.956 21.65809H152.29333Zm23.55317 16.24357a5.43045 5.43045 0 1 0 -3.824 -1.59052A5.21382 5.21382 0 0 0 169.89052 188.30651ZM152.29333 193.721h35.19439l5.956 -21.65809H146.33735Z"
+ android:fillColor="#EC070E" />
+ <path
+ android:pathData="M222.69806 234.33831H117.0153a4.52392 4.52392 0 0 1 -4.52392 -4.52393V218.66973H227.222v11.14465A4.52393 4.52393 0 0 1 222.69806 234.33831Z"
+ android:fillColor="#FFAAA7" />
+ <path
+ android:pathData="M193.8775 39.73552H298.1178A5.24519 5.24519 0 0 1 303.3629 44.98071V149.2209A5.24519 5.24519 0 0 1 298.1178 154.4661H193.8775A5.24519 5.24519 0 0 1 188.6323 149.2209V44.98071A5.24519 5.24519 0 0 1 193.8775 39.73552Z"
+ android:fillColor="#CEE3FF" />
+ <path
+ android:pathData="M250.83253 123.953a5.34413 5.34413 0 0 1 -2.14817 -0.43635 5.57478 5.57478 0 0 1 -1.7454 -1.17478L223.309 98.712a5.57488 5.57488 0 0 1 -1.17479 -1.74539 5.34422 5.34422 0 0 1 -0.43635 -2.14818V75.61906a5.38624 5.38624 0 0 1 5.37045 -5.37044h19.19933a5.34413 5.34413 0 0 1 2.14817 0.43635 5.57478 5.57478 0 0 1 1.7454 1.17478l23.62994 23.69708a5.43062 5.43062 0 0 1 0 7.58575l-19.19933 19.19933a5.6893 5.6893 0 0 1 -1.71182 1.17478A4.95 4.95 0 0 1 250.83253 123.953Zm-0.06713 -5.37044l19.19933 -19.19933L246.26766 75.61906H227.06833V94.81839Zm-16.984 -32.22266a4.01107 4.01107 0 1 0 -2.85305 -1.17478A3.8843 3.8843 0 0 0 233.78138 86.35994Zm16.984 32.22266L227.06833 94.81839V75.61906h19.19933l23.69707 23.76421Z"
+ android:fillColor="#0073F0" />
+ <path
+ android:pathData="M188.63233 138.79756H303.36294a0 0 0 0 1 0 0v11.14465a4.52393 4.52393 0 0 1 -4.52393 4.52393H193.15626a4.52393 4.52393 0 0 1 -4.52393 -4.52393V138.79756A0 0 0 0 1 188.63233 138.79756Z"
+ android:fillColor="#A7CBFE" />
+ <path
+ android:pathData="M154.626 50.01519H173.156A4.58311 4.58311 0 0 1 177.7391 54.5983V73.12828A4.58311 4.58311 0 0 1 173.156 77.71139H154.626A4.58311 4.58311 0 0 1 150.0429 73.12828V54.5983A4.58311 4.58311 0 0 1 154.626 50.01519Z"
+ android:fillColor="#C7EBD4" />
+ <path
+ android:pathData="M156.64019 70.67614a2.30727 2.30727 0 0 1 -1.9222 -0.86377 2.67014 2.67014 0 0 1 -0.511 -2.10469l1.02193 -7.29949a3.8631 3.8631 0 0 1 1.30174 -2.40883 3.735 3.735 0 0 1 2.54266 -0.94893h9.63532a3.735 3.735 0 0 1 2.54266 0.94893 3.86315 3.86315 0 0 1 1.30174 2.40883l1.02193 7.29949a2.67014 2.67014 0 0 1 -0.511 2.10469 2.30727 2.30727 0 0 1 -1.9222 0.86377 2.44246 2.44246 0 0 1 -0.94893 -0.18249 2.4835 2.4835 0 0 1 -0.80294 -0.54746l-2.18985 -2.18985h-6.6182l-2.18985 2.18985a2.4835 2.4835 0 0 1 -0.80294 0.54746A2.44246 2.44246 0 0 1 156.64019 70.67614Zm0.3893 -2.09252l2.77381 -2.77381h8.17542l2.77381 2.77381a1.34172 1.34172 0 0 0 0.3893 0.146 0.57541 0.57541 0 0 0 0.42581 -0.15816 0.45173 0.45173 0 0 0 0.10949 -0.4258l-1.07059 -7.49414a1.872 1.872 0 0 0 -0.63263 -1.18008 1.84508 1.84508 0 0 0 -1.26524 -0.47447h-9.63532a1.84512 1.84512 0 0 0 -1.26525 0.47447 1.872 1.872 0 0 0 -0.63262 1.18008l-1.07059 7.49414a0.4517 0.4517 0 0 0 0.10949 0.4258 0.57539 0.57539 0 0 0 0.42581 0.15816A2.14925 2.14925 0 0 0 157.02949 68.58362Zm11.72784 -3.74707a0.95588 0.95588 0 1 0 -0.69345 -0.27981A0.94172 0.94172 0 0 0 168.75733 64.83655Zm-1.94653 -2.91979a0.9559 0.9559 0 1 0 -0.69345 -0.27982A0.94173 0.94173 0 0 0 166.8108 61.91676Zm-7.05617 2.91979h1.4599V63.13334h1.70321v-1.4599h-1.70321V59.97023h-1.4599v1.70321h-1.70321v1.4599h1.70321Zm-2.72514 3.74707a0.47289 0.47289 0 0 1 -0.17032 0.10949 0.61986 0.61986 0 0 1 -0.219 0.0365 0.57539 0.57539 0 0 1 -0.42581 -0.15816 0.4517 0.4517 0 0 1 -0.10949 -0.4258l1.07059 -7.49414a1.872 1.872 0 0 1 0.63262 -1.18008 1.84512 1.84512 0 0 1 1.26525 -0.47447h9.63532a1.84508 1.84508 0 0 1 1.26524 0.47447 1.872 1.872 0 0 1 0.63263 1.18008l1.07059 7.49414a0.45173 0.45173 0 0 1 -0.10949 0.4258 0.57541 0.57541 0 0 1 -0.42581 0.15816 0.61986 0.61986 0 0 1 -0.219 -0.0365 0.47281 0.47281 0 0 1 -0.17032 -0.10949l-2.77381 -2.77381H159.8033Z"
+ android:fillColor="#009131" />
+ <path
+ android:pathData="M154.626 84.55184H173.156A4.58311 4.58311 0 0 1 177.7391 89.13495V107.6649A4.58311 4.58311 0 0 1 173.156 112.248H154.626A4.58311 4.58311 0 0 1 150.0429 107.6649V89.13495A4.58311 4.58311 0 0 1 154.626 84.55184Z"
+ android:fillColor="#FFEFBD" />
+ <path
+ android:pathData="M158.00213 104.68631a1.57619 1.57619 0 0 1 -1.57159 -1.57159V99.97153a1.57159 1.57159 0 0 0 0 -3.14318V93.68516a1.57621 1.57621 0 0 1 1.57159 -1.57159h12.57274a1.57621 1.57621 0 0 1 1.57159 1.57159v3.14319a1.57159 1.57159 0 0 0 0 3.14318v3.14319a1.57619 1.57619 0 0 1 -1.57159 1.57159Zm0 -1.57159h12.57274v-2.00378a2.92089 2.92089 0 0 1 -1.15905 -1.14923 3.1625 3.1625 0 0 1 0 -3.12354 2.92089 2.92089 0 0 1 1.15905 -1.14923V93.68516H158.00213v2.00378a2.92089 2.92089 0 0 1 1.15905 1.14923 3.1625 3.1625 0 0 1 0 3.12354 2.92089 2.92089 0 0 1 -1.15905 1.14923Zm4.08614 -1.57159L164.2885 99.893l2.16094 1.65018 -0.82509 -2.67171 2.20023 -1.72875h-2.6717L164.2885 94.471l-0.86438 2.67171h-2.6717l2.16094 1.72875Zm-4.08614 1.57159v-2.00378a2.92089 2.92089 0 0 0 1.15905 -1.14923 3.1625 3.1625 0 0 0 0 -3.12354 2.92089 2.92089 0 0 0 -1.15905 -1.14923V93.68516h12.57274v2.00378a2.92089 2.92089 0 0 0 -1.15905 1.14923 3.1625 3.1625 0 0 0 0 3.12354 2.92089 2.92089 0 0 0 1.15905 1.14923v2.00378Z"
+ android:fillColor="#FFA800" />
+ <path
+ android:pathData="M178.5051 231.569H282.7454A5.24519 5.24519 0 0 1 287.9906 236.8142V258.7619A5.24519 5.24519 0 0 1 282.7454 264.0071H178.5051A5.24519 5.24519 0 0 1 173.2599 258.7619V236.8142A5.24519 5.24519 0 0 1 178.5051 231.569Z"
+ android:fillColor="#3C4043" />
+ <path
+ android:pathData="M182.39078 251.63082l1.18675 -1.18675 2.82559 0.39558 4.40792 -4.40791 -8.81584 -4.80351 1.58233 -1.58233 10.79375 2.76908 4.40792 -4.35141a1.67849 1.67849 0 1 1 2.37349 2.3735l-4.3514 4.40792 2.76907 10.79375 -1.58233 1.58233 -4.8035 -8.81584 -4.40792 4.40792 0.39558 2.82559 -1.18674 1.18675 -1.97792 -3.61676Z"
+ android:fillColor="#5F6368" />
+ <path
+ android:pathData="M117.7704 119.245H222.0106A5.24519 5.24519 0 0 1 227.2558 124.4902V228.7304A5.24519 5.24519 0 0 1 222.0106 233.9756H117.7704A5.24519 5.24519 0 0 1 112.5252 228.7304V124.4902A5.24519 5.24519 0 0 1 117.7704 119.245Z"
+ android:fillColor="#3C4043" />
+ <path
+ android:pathData="M150.9397 199.13555a3.41561 3.41561 0 0 1 -2.09813 -0.71066 3.50012 3.50012 0 0 1 -1.28595 -1.86124l-7.58033 -27.34333a1.73429 1.73429 0 0 1 0.30456 -1.75972 2.08759 2.08759 0 0 1 1.72588 -0.81218H155.6774l11.91195 -17.73256a2.55073 2.55073 0 0 1 0.94754 -0.87986 2.61249 2.61249 0 0 1 2.5719 0 2.55073 2.55073 0 0 1 0.94754 0.87986l11.91195 17.73256h13.807a2.08759 2.08759 0 0 1 1.72588 0.81218 1.73427 1.73427 0 0 1 0.30456 1.75972l-7.58032 27.34333a3.50023 3.50023 0 0 1 -1.28595 1.86124 3.41563 3.41563 0 0 1 -2.09813 0.71066Zm11.30281 -32.48713h15.22834l-7.648 -11.3705Zm-15.90516 5.41452H193.4437l-5.956 21.65809H152.29333Zm23.55317 16.24357a5.43045 5.43045 0 1 0 -3.824 -1.59052A5.21382 5.21382 0 0 0 169.89052 188.30651ZM152.29333 193.721h35.19439l5.956 -21.65809H146.33735Z"
+ android:fillColor="#5F6368" />
+ <path
+ android:pathData="M112.49138 218.66973H227.222a0 0 0 0 1 0 0v11.14465a4.52393 4.52393 0 0 1 -4.52393 4.52393H117.0153a4.52393 4.52393 0 0 1 -4.52393 -4.52393V218.66973A0 0 0 0 1 112.49138 218.66973Z"
+ android:fillColor="#5F6368" />
+ <path
+ android:pathData="M193.8775 39.73552H298.1178A5.24519 5.24519 0 0 1 303.3629 44.98071V149.2209A5.24519 5.24519 0 0 1 298.1178 154.4661H193.8775A5.24519 5.24519 0 0 1 188.6323 149.2209V44.98071A5.24519 5.24519 0 0 1 193.8775 39.73552Z"
+ android:fillColor="#3C4043" />
+ <path
+ android:pathData="M250.83253 123.953a5.34413 5.34413 0 0 1 -2.14817 -0.43635 5.57478 5.57478 0 0 1 -1.7454 -1.17478L223.309 98.712a5.57488 5.57488 0 0 1 -1.17479 -1.74539 5.34422 5.34422 0 0 1 -0.43635 -2.14818V75.61906a5.38624 5.38624 0 0 1 5.37045 -5.37044h19.19933a5.34413 5.34413 0 0 1 2.14817 0.43635 5.57478 5.57478 0 0 1 1.7454 1.17478l23.62994 23.69708a5.43062 5.43062 0 0 1 0 7.58575l-19.19933 19.19933a5.6893 5.6893 0 0 1 -1.71182 1.17478A4.95 4.95 0 0 1 250.83253 123.953Zm-0.06713 -5.37044l19.19933 -19.19933L246.26766 75.61906H227.06833V94.81839Zm-16.984 -32.22266a4.01107 4.01107 0 1 0 -2.85305 -1.17478A3.8843 3.8843 0 0 0 233.78138 86.35994Zm16.984 32.22266L227.06833 94.81839V75.61906h19.19933l23.69707 23.76421Z"
+ android:fillColor="#5F6368" />
+ <path
+ android:pathData="M188.63233 138.79756H303.36294a0 0 0 0 1 0 0v11.14465a4.52393 4.52393 0 0 1 -4.52393 4.52393H193.15626a4.52393 4.52393 0 0 1 -4.52393 -4.52393V138.79756A0 0 0 0 1 188.63233 138.79756Z"
+ android:fillColor="#5F6368" />
+ <path
+ android:pathData="M154.626 50.01519H173.156A4.58311 4.58311 0 0 1 177.7391 54.5983V73.12828A4.58311 4.58311 0 0 1 173.156 77.71139H154.626A4.58311 4.58311 0 0 1 150.0429 73.12828V54.5983A4.58311 4.58311 0 0 1 154.626 50.01519Z"
+ android:fillColor="#3C4043" />
+ <path
+ android:pathData="M156.64019 70.67614a2.30727 2.30727 0 0 1 -1.9222 -0.86377 2.67014 2.67014 0 0 1 -0.511 -2.10469l1.02193 -7.29949a3.8631 3.8631 0 0 1 1.30174 -2.40883 3.735 3.735 0 0 1 2.54266 -0.94893h9.63532a3.735 3.735 0 0 1 2.54266 0.94893 3.86315 3.86315 0 0 1 1.30174 2.40883l1.02193 7.29949a2.67014 2.67014 0 0 1 -0.511 2.10469 2.30727 2.30727 0 0 1 -1.9222 0.86377 2.44246 2.44246 0 0 1 -0.94893 -0.18249 2.4835 2.4835 0 0 1 -0.80294 -0.54746l-2.18985 -2.18985h-6.6182l-2.18985 2.18985a2.4835 2.4835 0 0 1 -0.80294 0.54746A2.44246 2.44246 0 0 1 156.64019 70.67614Zm0.3893 -2.09252l2.77381 -2.77381h8.17542l2.77381 2.77381a1.34172 1.34172 0 0 0 0.3893 0.146 0.57541 0.57541 0 0 0 0.42581 -0.15816 0.45173 0.45173 0 0 0 0.10949 -0.4258l-1.07059 -7.49414a1.872 1.872 0 0 0 -0.63263 -1.18008 1.84508 1.84508 0 0 0 -1.26524 -0.47447h-9.63532a1.84512 1.84512 0 0 0 -1.26525 0.47447 1.872 1.872 0 0 0 -0.63262 1.18008l-1.07059 7.49414a0.4517 0.4517 0 0 0 0.10949 0.4258 0.57539 0.57539 0 0 0 0.42581 0.15816A2.14925 2.14925 0 0 0 157.02949 68.58362Zm11.72784 -3.74707a0.95588 0.95588 0 1 0 -0.69345 -0.27981A0.94172 0.94172 0 0 0 168.75733 64.83655Zm-1.94653 -2.91979a0.9559 0.9559 0 1 0 -0.69345 -0.27982A0.94173 0.94173 0 0 0 166.8108 61.91676Zm-7.05617 2.91979h1.4599V63.13334h1.70321v-1.4599h-1.70321V59.97023h-1.4599v1.70321h-1.70321v1.4599h1.70321Zm-2.72514 3.74707a0.47289 0.47289 0 0 1 -0.17032 0.10949 0.61986 0.61986 0 0 1 -0.219 0.0365 0.57539 0.57539 0 0 1 -0.42581 -0.15816 0.4517 0.4517 0 0 1 -0.10949 -0.4258l1.07059 -7.49414a1.872 1.872 0 0 1 0.63262 -1.18008 1.84512 1.84512 0 0 1 1.26525 -0.47447h9.63532a1.84508 1.84508 0 0 1 1.26524 0.47447 1.872 1.872 0 0 1 0.63263 1.18008l1.07059 7.49414a0.45173 0.45173 0 0 1 -0.10949 0.4258 0.57541 0.57541 0 0 1 -0.42581 0.15816 0.61986 0.61986 0 0 1 -0.219 -0.0365 0.47281 0.47281 0 0 1 -0.17032 -0.10949l-2.77381 -2.77381H159.8033Z"
+ android:fillColor="#5F6368" />
+ <path
+ android:pathData="M154.626 84.55184H173.156A4.58311 4.58311 0 0 1 177.7391 89.13495V107.6649A4.58311 4.58311 0 0 1 173.156 112.248H154.626A4.58311 4.58311 0 0 1 150.0429 107.6649V89.13495A4.58311 4.58311 0 0 1 154.626 84.55184Z"
+ android:fillColor="#3C4043" />
+ <path
+ android:pathData="M158.00213 104.68631a1.57619 1.57619 0 0 1 -1.57159 -1.57159V99.97153a1.57159 1.57159 0 0 0 0 -3.14318V93.68516a1.57621 1.57621 0 0 1 1.57159 -1.57159h12.57274a1.57621 1.57621 0 0 1 1.57159 1.57159v3.14319a1.57159 1.57159 0 0 0 0 3.14318v3.14319a1.57619 1.57619 0 0 1 -1.57159 1.57159Zm0 -1.57159h12.57274v-2.00378a2.92089 2.92089 0 0 1 -1.15905 -1.14923 3.1625 3.1625 0 0 1 0 -3.12354 2.92089 2.92089 0 0 1 1.15905 -1.14923V93.68516H158.00213v2.00378a2.92089 2.92089 0 0 1 1.15905 1.14923 3.1625 3.1625 0 0 1 0 3.12354 2.92089 2.92089 0 0 1 -1.15905 1.14923Zm4.08614 -1.57159L164.2885 99.893l2.16094 1.65018 -0.82509 -2.67171 2.20023 -1.72875h-2.6717L164.2885 94.471l-0.86438 2.67171h-2.6717l2.16094 1.72875Zm-4.08614 1.57159v-2.00378a2.92089 2.92089 0 0 0 1.15905 -1.14923 3.1625 3.1625 0 0 0 0 -3.12354 2.92089 2.92089 0 0 0 -1.15905 -1.14923V93.68516h12.57274v2.00378a2.92089 2.92089 0 0 0 -1.15905 1.14923 3.1625 3.1625 0 0 0 0 3.12354 2.92089 2.92089 0 0 0 1.15905 1.14923v2.00378Z"
+ android:fillColor="#5F6368" />
+ <path
+ android:pathData="M211.24417 93.10332l-39.77208 14.91453V138.344a57.09207 57.09207 0 0 0 11.248 34.36556q11.24805 15.47382 28.524 19.82388 17.276 -4.35009 28.524 -19.82388a57.09218 57.09218 0 0 0 11.248 -34.36556V108.01785Z"
+ android:fillColor="#669DF6" />
+ <path
+ android:pathData="M211.24416 117.59976A10.12522 10.12522 0 0 1 221.383 127.73858v4.05552h2.02777a4.06745 4.06745 0 0 1 4.05552 4.05553v20.27763a4.06745 4.06745 0 0 1 -4.05552 4.05553H199.07758a4.06745 4.06745 0 0 1 -4.05553 -4.05553V135.84963a4.06745 4.06745 0 0 1 4.05553 -4.05553h2.02776v-4.05552a10.12522 10.12522 0 0 1 10.13882 -10.13882Zm0 4.05553a6.058 6.058 0 0 0 -6.08329 6.08329v4.05552h12.16658v-4.05552a6.058 6.058 0 0 0 -6.08329 -6.08329Zm-12.16658 34.472h24.33316V135.84963H199.07758v20.27763Zm12.16658 -14.19434a4.06745 4.06745 0 1 0 2.86421 1.19131A3.90521 3.90521 0 0 0 211.24416 141.93292Zm-12.16658 -6.08329h24.33316v20.27763H199.07758V135.84963Z"
+ android:fillColor="#000000" />
+</vector> \ No newline at end of file
diff --git a/adservices/apk/res/drawable/ic_placeholder_icon_for_empty_apps_list.xml b/adservices/apk/res/drawable/ic_placeholder_icon_for_empty_apps_list.xml
new file mode 100644
index 000000000..69cc7e26c
--- /dev/null
+++ b/adservices/apk/res/drawable/ic_placeholder_icon_for_empty_apps_list.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="44"
+ android:viewportHeight="44"
+ android:width="44dp"
+ android:height="44dp"
+ android:tint="?android:attr/colorControlNormal">
+<path
+ android:pathData="M6.00008 11.3337C8.93341 11.3337 11.3334 8.93366 11.3334 6.00033C11.3334 3.06699 8.93341 0.666992 6.00008 0.666992C3.06675 0.666992 0.666748 3.06699 0.666748 6.00033C0.666748 8.93366 3.06675 11.3337 6.00008 11.3337ZM22.0001 43.3337C24.9334 43.3337 27.3334 40.9337 27.3334 38.0003C27.3334 35.067 24.9334 32.667 22.0001 32.667C19.0667 32.667 16.6667 35.067 16.6667 38.0003C16.6667 40.9337 19.0667 43.3337 22.0001 43.3337ZM11.3334 38.0003C11.3334 40.9337 8.93341 43.3337 6.00008 43.3337C3.06675 43.3337 0.666748 40.9337 0.666748 38.0003C0.666748 35.067 3.06675 32.667 6.00008 32.667C8.93341 32.667 11.3334 35.067 11.3334 38.0003ZM6.00008 27.3337C8.93341 27.3337 11.3334 24.9337 11.3334 22.0003C11.3334 19.067 8.93341 16.667 6.00008 16.667C3.06675 16.667 0.666748 19.067 0.666748 22.0003C0.666748 24.9337 3.06675 27.3337 6.00008 27.3337ZM27.3334 22.0003C27.3334 24.9337 24.9334 27.3337 22.0001 27.3337C19.0667 27.3337 16.6667 24.9337 16.6667 22.0003C16.6667 19.067 19.0667 16.667 22.0001 16.667C24.9334 16.667 27.3334 19.067 27.3334 22.0003ZM32.6667 6.00033C32.6667 8.93366 35.0667 11.3337 38.0001 11.3337C40.9334 11.3337 43.3334 8.93366 43.3334 6.00033C43.3334 3.06699 40.9334 0.666992 38.0001 0.666992C35.0667 0.666992 32.6667 3.06699 32.6667 6.00033ZM27.3334 6.00033C27.3334 8.93366 24.9334 11.3337 22.0001 11.3337C19.0667 11.3337 16.6667 8.93366 16.6667 6.00033C16.6667 3.06699 19.0667 0.666992 22.0001 0.666992C24.9334 0.666992 27.3334 3.06699 27.3334 6.00033ZM38.0001 27.3337C40.9334 27.3337 43.3334 24.9337 43.3334 22.0003C43.3334 19.067 40.9334 16.667 38.0001 16.667C35.0667 16.667 32.6667 19.067 32.6667 22.0003C32.6667 24.9337 35.0667 27.3337 38.0001 27.3337ZM43.3334 38.0003C43.3334 40.9337 40.9334 43.3337 38.0001 43.3337C35.0667 43.3337 32.6667 40.9337 32.6667 38.0003C32.6667 35.067 35.0667 32.667 38.0001 32.667C40.9334 32.667 43.3334 35.067 43.3334 38.0003Z"
+ android:fillType="evenOdd"
+ android:fillColor="#474747" />
+</vector> \ No newline at end of file
diff --git a/adservices/apk/res/drawable/ic_placeholder_icon.xml b/adservices/apk/res/drawable/ic_placeholder_icon_for_empty_topics_list.xml
index 2ed34161d..2ed34161d 100644
--- a/adservices/apk/res/drawable/ic_placeholder_icon.xml
+++ b/adservices/apk/res/drawable/ic_placeholder_icon_for_empty_topics_list.xml
diff --git a/adservices/apk/res/layout/adservices_settings_main_activity.xml b/adservices/apk/res/layout/adservices_settings_main_activity.xml
index 5a51fb21c..8d50407c5 100644
--- a/adservices/apk/res/layout/adservices_settings_main_activity.xml
+++ b/adservices/apk/res/layout/adservices_settings_main_activity.xml
@@ -22,6 +22,5 @@
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:name="com.android.adservices.ui.settings.fragments.AdServicesSettingsMainFragment" />
+ android:layout_height="match_parent" />
</LinearLayout>
diff --git a/adservices/apk/res/layout/apps_fragment.xml b/adservices/apk/res/layout/apps_fragment.xml
index 735d3153d..71a4c6ec9 100644
--- a/adservices/apk/res/layout/apps_fragment.xml
+++ b/adservices/apk/res/layout/apps_fragment.xml
@@ -48,7 +48,7 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/ic_placeholder_icon"
+ android:src="@drawable/ic_placeholder_icon_for_empty_apps_list"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
@@ -58,6 +58,11 @@
android:gravity="center"
android:text="@string/settingsUI_apps_view_no_apps_text"
android:textAppearance="@style/TextAppearance.Body2.Bold" />
+ <Button
+ android:id="@+id/blocked_apps_when_empty_state_button"
+ style="@style/MainStyle.PrimaryButton"
+ android:layout_marginTop="20dp"
+ android:text="@string/settingsUI_blocked_apps_title"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
diff --git a/adservices/apk/res/layout/blocked_apps_fragment.xml b/adservices/apk/res/layout/blocked_apps_fragment.xml
index ce9736fac..c8ae80cf9 100644
--- a/adservices/apk/res/layout/blocked_apps_fragment.xml
+++ b/adservices/apk/res/layout/blocked_apps_fragment.xml
@@ -44,7 +44,7 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/ic_placeholder_icon" />
+ android:src="@drawable/ic_placeholder_icon_for_empty_apps_list" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/adservices/apk/res/layout/blocked_topics_fragment.xml b/adservices/apk/res/layout/blocked_topics_fragment.xml
index 2cb23a9cc..8467f2f5e 100644
--- a/adservices/apk/res/layout/blocked_topics_fragment.xml
+++ b/adservices/apk/res/layout/blocked_topics_fragment.xml
@@ -42,7 +42,7 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/ic_placeholder_icon" />
+ android:src="@drawable/ic_placeholder_icon_for_empty_topics_list" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/adservices/apk/res/layout/consent_notification_accept_confirmation_fragment.xml b/adservices/apk/res/layout/consent_notification_accept_confirmation_fragment.xml
index 2cd7f5ec4..5afd8f64f 100644
--- a/adservices/apk/res/layout/consent_notification_accept_confirmation_fragment.xml
+++ b/adservices/apk/res/layout/consent_notification_accept_confirmation_fragment.xml
@@ -73,20 +73,14 @@
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/leftControlButtonConfirmation"
- style="@style/MainStyle.NotificationPrimaryButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ style="@style/MainStyle.PrimaryButton"
android:text="@string/notificationUI_confirmation_left_control_button_text"
- android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/rightControlButtonConfirmation"
- style="@style/MainStyle.NotificationPrimaryButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ style="@style/MainStyle.PrimaryButton"
android:text="@string/notificationUI_confirmation_right_control_button_text"
- android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/adservices/apk/res/layout/consent_notification_decline_confirmation_fragment.xml b/adservices/apk/res/layout/consent_notification_decline_confirmation_fragment.xml
index 66c3d096b..6e7edd1ae 100644
--- a/adservices/apk/res/layout/consent_notification_decline_confirmation_fragment.xml
+++ b/adservices/apk/res/layout/consent_notification_decline_confirmation_fragment.xml
@@ -74,20 +74,14 @@
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/leftControlButtonConfirmation"
- style="@style/MainStyle.NotificationPrimaryButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ style="@style/MainStyle.PrimaryButton"
android:text="@string/notificationUI_confirmation_left_control_button_text"
- android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/rightControlButtonConfirmation"
- style="@style/MainStyle.NotificationPrimaryButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ style="@style/MainStyle.PrimaryButton"
android:text="@string/notificationUI_confirmation_right_control_button_text"
- android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/adservices/apk/res/layout/consent_notification_fragment.xml b/adservices/apk/res/layout/consent_notification_fragment.xml
index 45b038797..d567d5a20 100644
--- a/adservices/apk/res/layout/consent_notification_fragment.xml
+++ b/adservices/apk/res/layout/consent_notification_fragment.xml
@@ -23,6 +23,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
+ android:id="@+id/notification_fragment_scrollview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scrollbars="vertical"
@@ -64,7 +65,8 @@
android:layout_marginTop="16dp"
android:padding="24dp"
android:background="@drawable/ic_rounded_background"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:animateLayoutChanges="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -79,28 +81,7 @@
android:layout_marginTop="12dp"
android:text="@string/notificationUI_container1_body_text"
style="@style/MainStyle.Body1" />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/container2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:padding="24dp"
- android:background="@drawable/ic_rounded_background"
- android:orientation="vertical">
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/notificationUI_container2_title"
- style="@style/MainStyle.H4"
- android:gravity="center_vertical"
- android:drawableStart="@drawable/ic_notification_icon_2"
- android:drawablePadding="24dp"/>
- <TextView
- android:layout_marginTop="12dp"
- android:text="@string/notificationUI_container2_body_text"
- style="@style/MainStyle.Body1" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
@@ -114,7 +95,6 @@
android:layout_marginTop="0dp"
android:text="@string/notificationUI_container1_control_text"
style="@style/MainStyle.Body1" />
-
<LinearLayout
android:id="@+id/how_it_works_expanded_text"
android:layout_width="match_parent"
@@ -142,6 +122,28 @@
</LinearLayout>
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/container2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:padding="24dp"
+ android:background="@drawable/ic_rounded_background"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/notificationUI_container2_title"
+ style="@style/MainStyle.H4"
+ android:gravity="center_vertical"
+ android:drawableStart="@drawable/ic_notification_icon_2"
+ android:drawablePadding="24dp"/>
+ <TextView
+ android:layout_marginTop="12dp"
+ android:text="@string/notificationUI_container2_body_text"
+ style="@style/MainStyle.Body1" />
+ </LinearLayout>
+
</LinearLayout>
</ScrollView>
@@ -162,20 +164,14 @@
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/leftControlButton"
- style="@style/MainStyle.NotificationPrimaryButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ style="@style/MainStyle.PrimaryButton"
android:text="@string/notificationUI_left_control_button_text"
- android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/rightControlButton"
- style="@style/MainStyle.NotificationPrimaryButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ style="@style/MainStyle.PrimaryButton"
android:text="@string/notificationUI_right_control_button_text"
- android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/adservices/apk/res/layout/consent_notification_fragment_eu.xml b/adservices/apk/res/layout/consent_notification_fragment_eu.xml
index d552f9896..8479d899f 100644
--- a/adservices/apk/res/layout/consent_notification_fragment_eu.xml
+++ b/adservices/apk/res/layout/consent_notification_fragment_eu.xml
@@ -23,6 +23,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
+ android:id="@+id/notification_fragment_scrollview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scrollbars="vertical"
@@ -50,11 +51,9 @@
android:layout_marginStart="0dp"
android:src="@drawable/ic_android_icon"
tools:ignore="ContentDescription" />
-
<TextView
android:text="@string/notificationUI_header_title_eu"
style="@style/MainStyle.H1" />
-
</LinearLayout>
<LinearLayout
@@ -64,7 +63,8 @@
android:layout_marginTop="16dp"
android:padding="24dp"
android:background="@drawable/ic_rounded_background"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:animateLayoutChanges="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -79,28 +79,7 @@
android:layout_marginTop="12dp"
android:text="@string/notificationUI_container1_body_text_eu"
style="@style/MainStyle.Body1" />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/container2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:padding="24dp"
- android:background="@drawable/ic_rounded_background"
- android:orientation="vertical">
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/notificationUI_container2_title_eu"
- style="@style/MainStyle.H4"
- android:gravity="center_vertical"
- android:drawableStart="@drawable/ic_notification_icon_2"
- android:drawablePadding="24dp"/>
- <TextView
- android:layout_marginTop="12dp"
- android:text="@string/notificationUI_container2_body_text_eu"
- style="@style/MainStyle.Body1" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
@@ -114,7 +93,6 @@
android:layout_marginTop="0dp"
android:text="@string/notificationUI_container1_control_text_eu"
style="@style/MainStyle.Body1" />
-
<LinearLayout
android:id="@+id/how_it_works_expanded_text"
android:layout_width="match_parent"
@@ -142,6 +120,27 @@
</LinearLayout>
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/container2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:padding="24dp"
+ android:background="@drawable/ic_rounded_background"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/notificationUI_container2_title_eu"
+ style="@style/MainStyle.H4"
+ android:gravity="center_vertical"
+ android:drawableStart="@drawable/ic_notification_icon_2"
+ android:drawablePadding="24dp"/>
+ <TextView
+ android:layout_marginTop="12dp"
+ android:text="@string/notificationUI_container2_body_text_eu"
+ style="@style/MainStyle.Body1" />
+ </LinearLayout>
</LinearLayout>
</ScrollView>
@@ -162,20 +161,14 @@
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/leftControlButton"
- style="@style/MainStyle.NotificationPrimaryButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ style="@style/MainStyle.PrimaryButton"
android:text="@string/notificationUI_left_control_button_text_eu"
- android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/rightControlButton"
- style="@style/MainStyle.NotificationPrimaryButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ style="@style/MainStyle.PrimaryButton"
android:text="@string/notificationUI_right_control_button_text_eu"
- android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/adservices/apk/res/layout/main_fragment.xml b/adservices/apk/res/layout/main_fragment.xml
index ec8189f77..a02359c84 100644
--- a/adservices/apk/res/layout/main_fragment.xml
+++ b/adservices/apk/res/layout/main_fragment.xml
@@ -36,6 +36,7 @@
android:orientation="vertical"
android:focusableInTouchMode="true" >
<TextView
+ android:layout_marginTop="8dp"
android:text="@string/settingsUI_main_view_subtitle"
style="@style/MainStyle.Body2" />
diff --git a/adservices/apk/res/layout/topics_fragment.xml b/adservices/apk/res/layout/topics_fragment.xml
index bba1df56f..1cf58baff 100644
--- a/adservices/apk/res/layout/topics_fragment.xml
+++ b/adservices/apk/res/layout/topics_fragment.xml
@@ -48,7 +48,7 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/ic_placeholder_icon"
+ android:src="@drawable/ic_placeholder_icon_for_empty_topics_list"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
@@ -58,6 +58,12 @@
android:gravity="center"
android:text="@string/settingsUI_topics_view_no_topics_text"
android:textAppearance="@style/TextAppearance.Body2.Bold" />
+
+ <Button
+ android:id="@+id/blocked_topics_when_empty_state_button"
+ style="@style/MainStyle.PrimaryButton"
+ android:layout_marginTop="20dp"
+ android:text="@string/settingsUI_blocked_topics_title"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
diff --git a/adservices/apk/res/values-af/strings.xml b/adservices/apk/res/values-af/strings.xml
index bb3d77f6a..bd224fdf5 100644
--- a/adservices/apk/res/values-af/strings.xml
+++ b/adservices/apk/res/values-af/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Hoe om deel te neem"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"As die beta aangeskakel is, word programme toegelaat om hierdie nuwe, meer private maniere te toets om vir jou advertensies te wys. Jy kan die beta enige tyd in jou privaatheidinstellings afskakel."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nee, dankie"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ja, ek sal aansluit"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Skakel aan"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Meer"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Dankie vir jou deelname"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Jy is deel van Android se advertensieprivaatheidbeta. Die Privacy Sandbox is vir jou toestel aangeskakel.\n\nJy kan enige tyd in jou privaatheidinstellings meer te wete kom of die beta afskakel."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Jy het gekies om nie deel te neem nie"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Geen belangstellings om op die oomblik te wys nie"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android se privaatheidbeta bied nuwe kenmerke wat programme kan gebruik om vir jou advertensies te wys waarvan jy dalk sal hou. Hierdie tegnologieë gebruik nie toestelidentifiseerders nie.\n\nAndroid kan die soorte advertensies skat waarin jy dalk belang sal stel, en stoor hierdie belangstellings tydelik op jou toestel. Dit maak dit vir programme moontlik om vir jou relevante advertesies te wys sonder om jou aktiwiteit op webwerwe en programme van ander ontwikkelaars na te spoor."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Jy het geen geblokkeerde belangstellings nie"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Programme kan jou belangstellings skat en hulle tydelik met Android stoor. ’n Ander program kan later vir jou ’n advertensie wys wat op hierdie belangstellings gegrond is.\n\nAs jy ’n program blokkeer, sal dit nie meer enige belangstellings skat nie. Dit sal nie weer by hierdie lys programme gevoeg word nie, tensy jy dit deblokkeer. Belangstellings wat reeds deur die program geskat is, sal uitgevee word, maar jy sal steeds verwante advertensies sien."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Programme wat jy geblokkeer het"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Stel belangstellings terug wat deur programme geskat is"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android se privaatheidbeta bied nuwe kenmerke wat programme kan gebruik om vir jou advertensies te wys waarvan jy dalk sal hou. Hierdie tegnologieë gebruik nie toestelidentifiseerders nie.\n\nProgramme kan die soorte advertensies skat waarin jy dalk belang sal stel, en stoor hierdie belangstellings tydelik op jou toestel. Dit maak dit vir programme moontlik om vir jou relevante advertesies te wys sonder om jou aktiwiteit op webwerwe en programme van ander ontwikkelaars na te spoor."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Jy het geen geblokkeerde programme nie"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Jy het geen geblokkeerde belangstellings nie"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Kanselleer"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Skakel Privacy Sandbox af?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Gaan na jou privaatheidinstellings as jy van plan verander of meer oor Android se advertensieprivaatheidbeta te wete wil kom."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Skakel af"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Blokkeer <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Hierdie belangstelling sal geblokkeer word, en sal nie weer by jou lys gevoeg word nie, tensy jy dit weer byvoeg. Jy kan steeds ’n paar verwante advertensies sien."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokkeer"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> is gedeblokkeer"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android kan hierdie belangstelling weer by jou lys voeg, maar dit sal dalk nie dadelik verskyn nie"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Stel al jou belangstellings terug?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Jou lys sal uitgevee word, en nuwe belangstellings sal van nou af geskat word. Jy kan steeds ’n paar advertensies sien wat verband hou met belangstellings wat uitgevee is."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Stel terug"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Blokkeer <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Hiedie app sal nie belangstellings vir die Privacy Sandbox skat nie, en sal nie weer by jou lys gevoeg word nie, tensy jy dit deblokkeer.\n\nBelangstellings wat reeds deur hierdie app geskat is, sal uitgevee word, maar jy kan steeds ’n paar verwante advertensies sien."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> is gedeblokkeer"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Hierdie app kan weer belangstellings vir jou skat, maar dit sal dalk nie dadelik op jou lys verskyn nie. Dit kan ’n rukkie duur voordat jy verwante advertensies sien."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Stel belangstellings terug wat deur apps gegenereer is?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Belangstellings wat die apps op jou lys geskat het, sal in die Privacy Sandbox uitgevee word, en die apps sal van nou af nuwe belangstellings skat. Jy kan steeds ’n paar soortgelyke advertensies sien."</string>
<string name="topic10001" msgid="1636806320891333775">"Kuns en vermaak"</string>
<string name="topic10002" msgid="1226367977754287428">"Toneelspel en teater"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime en Manga"</string>
diff --git a/adservices/apk/res/values-am/strings.xml b/adservices/apk/res/values-am/strings.xml
index ea2ea025a..f16b7af01 100644
--- a/adservices/apk/res/values-am/strings.xml
+++ b/adservices/apk/res/values-am/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"እንዴት መሳተፍ እንደሚቻል"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"የቅድመ-ይሁንታውን ማብራት መተግበሪያዎች ለእርስዎ ማስታወቂያዎችን ለማሳየት እነዚህን አዲስና ይበልጥ የግል የሆኑ መንገዶች እንዲሞክሩ ያስችላቸዋል። በማንኛውም ጊዜ በእርስዎ የግላዊነት ቅንብሮች ውስጥ ቅድመ-ይሁንታውን ማጥፋት ይችላሉ።"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"አይ፣ አመሰግናለሁ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"አዎ፣ እቀላቀላለሁ"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"አብራ"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"ተጨማሪ"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"ስለተሳትፎዎ እናመሰግናለን"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"የAndroid የማስታወቂያዎች ግላዊነት ቅድመ-ይሁንታ አካል ነዎት። የግላዊነት Sandbox ለመሣሪያዎ በርቷል።\n\nበማንኛውም ሰዓት በእርስዎ የግላዊነት ቅንብሮች ውስጥ የበለጠ ማወቅ ወይም ቅድመ-ይሁንታውን ማጥፋት ይችላሉ።"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"ላለመሳተፍ መርጠዋል"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"አሁን ምንም የሚታዩ ዝንባሌዎች የሉም"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"የግላዊነት Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"የAndroid ማስታወቂያዎች የግላዊነት ቅድመ ይሁንታ መተግበሪያዎች እርስዎ ሊወዷቸው የሚችሏቸው ማስታወቂያዎችን ለእርስዎ ለማሳየት ሊጠቀሙባቸው የሚችሉ አዲስ ባህሪያትን ያቀርባል። እነዚህ ቴክኖሎጂዎች የመሣሪያ ለዪዎችን አይጠቀሙም።\n\nAndroid እርስዎ ሊፈልጓቸው የሚችሉ የማስታወቂያ አይነቶችን መገመት እና እነዚህን ዝንባሌዎች በመሣሪያዎ ላይ በጊዜያዊነት ማስቀመጥ ይችላል። ይህ መተግበሪያዎች በሌሎች ገንቢዎች በተሰሩ ድር ጣቢያዎች እና መተግበሪያዎች ላይ እርስዎ የሚኖርዎትን እንቅስቃሴ ሳይከታተሉ ተዛማጅነት ያላቸው ማስታወቂያዎችን ለእርስዎ እንዲያሳዩ ያስችላቸዋል።"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ምንም የታገዱ ዝንባሌዎች የሉዎትም"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"መተግበሪያዎች የእርስዎን ዝንባሌዎች መገመት እና እነዚህን Android ላይ በጊዜያዊነት ማስቀመጥ ይችላሉ። በኋላ የተለየ መተግበሪያ በእነዚህ ዝንባሌዎች ላይ ተመስርቶ ማስታወቂያ ለእርስዎ ማሳየት ይችላል።\n\nአንድን መተግበሪያ ካገዱ ተጨማሪ ዝንባሌዎችን አይገምትም። እርስዎ እገዳውን ካላነሱ በስተቀር ወደዚህ የመተግበሪያዎች ዝርዝር ተመልሶ አይታከልም። በመተግበሪያው አስቀድመው የተገመቱ ዝንባሌዎች ይሰረዛሉ ነገር ግን አሁንም አንዳንድ ተዛማጅ ማስታወቂያዎችን ሊያዩ ይችላሉ።"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"እርስዎ ያገዷቸው መተግበሪያዎች"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"በመተግበሪያዎች የተገመቱ ዝንባሌዎችን ዳግም አስጀምር"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"የግላዊነት Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"የAndroid ማስታወቂያዎች የግላዊነት ቅድመ ይሁንታ መተግበሪያዎች እርስዎ ሊወዷቸው የሚችሏቸው ማስታወቂያዎችን ለእርስዎ ለማሳየት ሊጠቀሙባቸው የሚችሉ አዲስ ባህሪያትን ያቀርባል። እነዚህ ቴክኖሎጂዎች የመሣሪያ ለዪዎችን አይጠቀሙም።\n\nመተግበሪያዎች እርስዎ ሊፈልጓቸው የሚችሉ የማስታወቂያ አይነቶችን መገመት እና እነዚህን ዝንባሌዎች በመሣሪያዎ ላይ በጊዜያዊነት ማስቀመጥ ይችላሉ። ይህ መተግበሪያዎች በሌሎች ገንቢዎች በተሰሩ ድር ጣቢያዎች እና መተግበሪያዎች ላይ እርስዎ የሚኖርዎትን እንቅስቃሴ ሳይከታተሉ ተዛማጅነት ያላቸው ማስታወቂያዎችን ለእርስዎ እንዲያሳዩ ያስችላቸዋል።"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ምንም የታገዱ መተግበሪያዎች የሉዎትም"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ምንም የታገዱ ዝንባሌዎች የሉዎትም"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"ይቅር"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"የግላዊነት Sandbox ይጥፋ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"ሐሳብዎን ከቀየሩ ወይም ስለAndroid የማስታወቂያዎች ግላዊነት ቅድመ ይሁንታ የበለጠ ማወቅ ከፈለጉ ወደ የግላዊነት ቅንብሮችዎ ይሂዱ"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"አጥፋ"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> ይታገድ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ይህ ዝንባሌ ይታገዳል እና እርስዎ መልሰው ካላከሉት በስተቀር በዝርዝርዎ ላይ እንደገና አይታከልም። አሁንም አንዳንድ ተዛማጅ ማስታወቂያዎችን ሊመለከቱ ይችላሉ።"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"አግድ"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"የ<xliff:g id="TOPIC">%1$s</xliff:g> እገዳ ተነስቷል"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ይህን ዝንባሌ እንደገና በእርስዎ ዝርዝር ላይ ሊያክል ይችላል ነገር ግን ወዲያውኑ ላይታይ ይችላል"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"እሺ"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"ሁሉም ዝንባሌዎችዎ ዳግም ይጀመሩ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"ዝርዝርዎ ይጸዳል እና ከዚህ በኋላ አዲስ ዝንባሌዎች ይገመታሉ። አሁንም ከተጸዱት ዝንባሌዎች ጋር የሚዛመዱ አንዳንድ ማስታወቂያዎችን ሊያዩ ይችላሉ።"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"ዳግም አስጀምር"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> ይታገድ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ይህ መተግበሪያ ዝንባሌዎችን ለግላዊነት Sandbox አይገምትም እና እርስዎ እገዳውን ካላነሱለት በስተቀር እንደገና በዝርዝርዎ ላይ አይታከልም።\n\nበዚህ መተግበሪያ አስቀድመው የተገመቱ ዝንባሌዎች ይሰረዛሉ ነገር ግን አሁንም አንዳንድ ተዛማጅ ማስታወቂያዎችን ሊያዩ ይችላሉ።"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"የ<xliff:g id="APP">%1$s</xliff:g> እገዳ ተነስቷል"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ይህ መተግበሪያ እንደገና ዝንባሌዎችን ለእርስዎ መገመት ይችላል ነገር ግን በዝርዝርዎ ላይ ወዲያውኑ ላይታይ ይችላል። ተዛማጅ ማስታወቂያዎችን እስከሚያዩ ድረስ ጊዜ ሊወስድ ይችላል።"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"በመተግበሪያዎች የመነጩ ዝንባሌዎች ዳግም ይጀመሩ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"በዝርዝርዎ ላይ ባሉ መተግበሪያዎች የተገመቱ ዝንባሌዎች ከግላዊነት Sandbox ይሰረዛሉ፣ እና መተግበሪያዎቹ ከዚህ በኋላ አዲስ ዝንባሌዎችን ይገምታሉ። አሁንም አንዳንድ ተዛማጅ ማስታወቂያዎችን ሊመለከቱ ይችላሉ።"</string>
<string name="topic10001" msgid="1636806320891333775">"ሥነ ጥበባት እና መዝናኛ"</string>
<string name="topic10002" msgid="1226367977754287428">"ትወና እና ቲያትር"</string>
<string name="topic10003" msgid="6949890838957814881">"አኒሜ እና ማንጋ"</string>
@@ -173,7 +191,7 @@
<string name="topic10082" msgid="7168659489029594320">"የቅንጦት ተሽከርካሪዎች"</string>
<string name="topic10083" msgid="1532745144715857802">"አነስተኛ መኪናዎች እና ንዑስ ኮምፓክቶች"</string>
<string name="topic10084" msgid="1984231414410381476">"ሞተርሳይክሎች"</string>
- <string name="topic10085" msgid="5235555028227554661">"ከመንገድ ውጪ ተሽከርካሪዎች"</string>
+ <string name="topic10085" msgid="5235555028227554661">"ከመንገድ ውጭ ተሽከርካሪዎች"</string>
<string name="topic10086" msgid="8360336602646460411">"ፒካፕ መኪናዎች"</string>
<string name="topic10087" msgid="6566900167002195406">"ስኩተሮች እና ሞፔዶች"</string>
<string name="topic10088" msgid="2899313082601001223">"ሴዳኖች"</string>
@@ -263,7 +281,7 @@
<string name="topic10172" msgid="3626573644414074730">"የዴስክቶፕ ህትመት"</string>
<string name="topic10173" msgid="7320053835183057144">"ቅርጸ-ቁምፊዎች"</string>
<string name="topic10174" msgid="5770469447869586710">"የአውርድ አስተዳዳሪዎች"</string>
- <string name="topic10175" msgid="3475235810022334041">"ነጻ ዌር እና ተከፋይ ዌር"</string>
+ <string name="topic10175" msgid="3475235810022334041">"ነፃ ዌር እና ተከፋይ ዌር"</string>
<string name="topic10176" msgid="6881704501596479881">"የግራፊክስ እና የእነማ ሶፍትዌር"</string>
<string name="topic10177" msgid="6740430730079682579">"ብልህ የግል ረዳቶች"</string>
<string name="topic10178" msgid="7261811053741593091">"የሚዲያ ማጫወቻዎች"</string>
@@ -457,7 +475,7 @@
<string name="topic10366" msgid="7460927054539097025">"ካርታዎች"</string>
<string name="topic10367" msgid="3900361839783743700">"ሳይንስ"</string>
<string name="topic10368" msgid="2069447039109930660">"የላቀ እውነታ እና ምናባዊ እውነታ"</string>
- <string name="topic10369" msgid="5747888306428024793">"የስነ ህይወት ሳይንሶች"</string>
+ <string name="topic10369" msgid="5747888306428024793">"የስነ ሕይወት ሳይንሶች"</string>
<string name="topic10370" msgid="4860829940151353583">"ስነ ዘረመል"</string>
<string name="topic10371" msgid="8229818210867472466">"ኬሚስትሪ"</string>
<string name="topic10372" msgid="6400467418954574231">"ሥነ ምህዳር እና አካባቢ"</string>
@@ -514,7 +532,7 @@
<string name="topic10423" msgid="6333426807705188459">"ቴኒስ"</string>
<string name="topic10424" msgid="5495511215629567375">"ትራክ እና ሜዳ"</string>
<string name="topic10425" msgid="4009138228283995179">"መረብ ኳስ"</string>
- <string name="topic10426" msgid="7998590461761852196">"ነጻ ትግል"</string>
+ <string name="topic10426" msgid="7998590461761852196">"ነፃ ትግል"</string>
<string name="topic10427" msgid="6711468161729797769">"ጉዞ እና መጓጓዣ"</string>
<string name="topic10428" msgid="661919563628063196">"የጀብዱ ጉዞ"</string>
<string name="topic10429" msgid="408270392813634227">"የአየር ጉዞ"</string>
diff --git a/adservices/apk/res/values-ar/strings.xml b/adservices/apk/res/values-ar/strings.xml
index fd87bbdd4..6a949c9cd 100644
--- a/adservices/apk/res/values-ar/strings.xml
+++ b/adservices/apk/res/values-ar/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"كيفية المشاركة"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"يؤدي تفعيل الإصدار التجريبي إلى السماح للتطبيقات بتجربة هذه الطرق الجديدة التي توفر المزيد من الخصوصية لعرض إعلاناتك. يمكنك إيقاف الإصدار التجريبي في أي وقت من إعدادات الخصوصية لديك."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"لا، شكرًا"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"نعم، أريد الانضمام"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"تفعيل"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"المزيد"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"نشكرك على المشاركة"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"‏أنت تشارك في حماية خصوصية الإعلانات من Android (الإصدار التجريبي) تم تفعيل \"مبادرة حماية الخصوصية\" على جهازك.\n\nيمكنك الاطّلاع على المزيد من المعلومات أو إيقاف الإصدار التجريبي في أي وقت في إعدادات الخصوصية لديك."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"لقد اختَرت عدم المشاركة"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"لا تتوفّر اهتمامات لعرضها في الوقت الحالي."</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"مبادرة حماية الخصوصية"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"‏توفّر حماية خصوصية الإعلانات من Android (الإصدار التجريبي) ميزات جديدة يمكن للتطبيقات استخدامها لعرض التطبيقات التي قد تعجبك. لا تستخدم هذه التقنيات معرّفات الأجهزة.\n\nيمكن لنظام Android أن يُحدّد أنواع الإعلانات التي قد تهتم بمشاهدتها، وحفظ فئات الاهتمام على جهازك مؤقّتًا. يتيح هذا الأمر للتطبيقات عرض الإعلانات ذات الصلة، بدون أن يتم تتبُّع نشاطك بواسطة المواقع الإلكترونية والتطبيقات التابعة لمطورين آخرين."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ليس لديك فئات اهتمام محظورة"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"‏يمكن أن تُحدّد التطبيقات اهتماماتك وتحفظها مؤقتًا على نظام Android. وفي وقت لاحق، يمكن أن يعرض تطبيق آخر إعلانًا وفقًا لهذه الاهتمامات.\n\nفي حال حظر أحد التطبيقات، لن يُحدّد هذا التطبيق أي اهتمامات بعد ذلك. لن تتم إضافة هذا التطبيق إلى قائمة التطبيقات مرة أخرى ما لم يتم إزالة حظره. وسيتم أيضًا حذف الاهتمامات التي سبق وحدّدها التطبيق، ولكن قد يستمر ظهور بعض الإعلانات ذات الصلة."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"التطبيقات المحظورة"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"إعادة ضبط الاهتمامات المحددّة وفقًا للتطبيقات"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"مبادرة حماية الخصوصية"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"‏توفّر حماية خصوصية الإعلانات من Android (الإصدار التجريبي) ميزات جديدة يمكن للتطبيقات استخدامها لعرض التطبيقات التي قد تعجبك. لا تستخدم هذه التقنيات معرّفات الأجهزة.\n\nيمكن أن تُحدّد التطبيقات أنواع الإعلانات التي قد تهتم بمشاهدتها، وحفظ فئات الاهتمام هذه على جهازك مؤقّتًا. يتيح هذا الأمر للتطبيقات عرض الإعلانات ذات الصلة، بدون أن يتم تتبُّع نشاطك بواسطة المواقع الإلكترونية والتطبيقات التابعة لمطورين آخرين."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ليس هناك تطبيقات محظورة."</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ليس لديك فئات اهتمام محظورة"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"إلغاء"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"هل تريد إيقاف \"مبادرة حماية الخصوصية\"؟"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"‏إذا غيّرت رأيك أو أردت معرفة المزيد من المعلومات عن الإصدار التجريبي من الخصوصية في عرض الإعلانات على نظام Android، انتقِل إلى إعدادات الخصوصية."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"إيقاف"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"هل تريد حظر <xliff:g id="TOPIC">%1$s</xliff:g>؟"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"سيتم حظر هذا الاهتمام ولن تتم إضافته إلى قائمتك مرة أخرى ما لم تضِفه مجددًا. قد تظلّ تظهر لك بعض الإعلانات ذات الصلة."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"حظر"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"تمت إزالة حظر <xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"‏قد يضيف نظام Android هذا الاهتمام إلى قائمتك مرة أخرى، ولكنه قد لا يظهر على الفور."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"حسنًا"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"هل تريد إعادة ضبط كل الاهتمامات؟"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"سيتم محو قائمتك وتقدير اهتمامات جديدة من الآن فصاعدًا. قد تظلّ تظهر لك بعض الإعلانات ذات الصلة بالاهتمامات التي تم محوها."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"إعادة الضبط"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"هل تريد حظر <xliff:g id="APP">%1$s</xliff:g>؟"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"لن يقدِّر هذا التطبيق اهتماماتك من أجل \"مبادرة حماية الخصوصية\" ولن تتم إضافته إلى قائمتك مرة أخرى ما لم تتم إزالة حظره.\n\nسيتم حذف الاهتمامات التي سبق أن قدّرها هذا التطبيق، ولكن قد تظلّ تظهر لك بعض الإعلانات ذات الصلة."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"تمت إزالة حظر <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"يمكن أن يقدِّر هذا التطبيق اهتماماتك نيابةً عنك مرة أخرى، ولكنها قد لا تظهر في قائمتك على الفور. وقد يستغرق ظهور الإعلانات ذات الصلة لك بعض الوقت."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"هل تريد إعادة ضبط الاهتمامات التي تُنشئها التطبيقات؟"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"الاهتمامات التي تم تقديرها وفقًا للتطبيقات في قائمتك سيتم حذفها من \"مبادرة حماية الخصوصية\" وستقدِّر التطبيقات اهتمامات جديدة من الآن فصاعدًا. قد تظلّ تظهر لك بعض الإعلانات ذات الصلة."</string>
<string name="topic10001" msgid="1636806320891333775">"فنون وترفيه"</string>
<string name="topic10002" msgid="1226367977754287428">"تمثيل ومسرح"</string>
<string name="topic10003" msgid="6949890838957814881">"أنمي وقصص مصورة"</string>
diff --git a/adservices/apk/res/values-as/strings.xml b/adservices/apk/res/values-as/strings.xml
index f41b1aeba..b32890301 100644
--- a/adservices/apk/res/values-as/strings.xml
+++ b/adservices/apk/res/values-as/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"কেনেকৈ অংশগ্ৰহণ কৰিব লাগে"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"বিটা অন কৰাটোৱে এপক আপোনাক বিজ্ঞাপন দেখুৱাবলৈ এই নতুন অধিক গোপনীয় উপায়সমূহ পৰীক্ষা কৰি চাবলৈ অনুমতি দিয়ে। আপুনি যিকোনো সময়তে নিজৰ গোপনীয়তাৰ ছেটিঙত বিটা অফ কৰিব পাৰে।"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"নালাগে, ধন্যবাদ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"হয়, মই যোগদান কৰিম"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"অন কৰক"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"অধিক"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"অংশগ্ৰহণ কৰাৰ বাবে ধন্যবাদ"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"আপুনি Androidৰ বিজ্ঞাপনৰ গোপনীয়তাৰ বিটা। আপোনাৰ ডিভাইচটোৰ বাবে প্ৰাইভেচি ছেণ্ডবক্স অন কৰা হৈছে।\n\nআপুনি অধিক জানিব পাৰে অথবা নিজৰ গোপনীয়তাৰ ছেটিঙত বিটাটো যিকোনো সময়তে অফ কৰিব পাৰে।"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"আপুনি অংশগ্ৰহণ নকৰিবলৈ বাছনি কৰিছে"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"এই মুহূৰ্তত দেখুৱাবলৈ কোনো আগ্ৰহ নাই"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"প্ৰাইভেচি ছেণ্ডবক্স"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androidৰ বিজ্ঞাপনৰ গোপনীয়তাৰ বিটায়ে নতুন সুবিধাসমূহ দিয়ে, যিসমূহ সুবিধা এপে আপোনাক আপুনি পচন্দ কৰিব পৰা ধৰণৰ বিজ্ঞাপন দেখুৱাবলৈ ব্যৱহাৰ কৰিব পাৰে। এই প্ৰযুক্তিসমূহে ডিভাইচ চিনাক্তকাৰী ব্যৱহাৰ নকৰে।\n\nAndroidএ আপুনি আগ্ৰহী হ’ব পৰা ধৰণৰ বিজ্ঞাপন অনুমান কৰিব পাৰে আৰু আপোনাৰ ডিভাইচত অস্থায়ীভাৱে আগ্ৰহসমূহ ছেভ কৰিব পাৰে। এইটোৱে সমগ্ৰ ৱেবছাইট আৰু অন্য বিকাশকৰ্তাৰ এপত আপোনাৰ কাৰ্যকলাপ ট্ৰেক নকৰাকৈয়ে আপোনাৰ বাবে প্ৰাসংগিক বিজ্ঞাপন দেখুৱাবলৈ এপক অনুমতি দিয়ে।"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"আপোনাৰ ওচৰত অৱৰোধ কৰি থোৱা কোনো আগ্ৰহ নাই"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"এপে আপোনাৰ আগ্ৰহসমূহ অনুমান কৰিব আৰু সেইবোৰ Androidৰ জৰিয়তে অস্থায়ীভাৱে ছেভ কৰিব পাৰে। পাছত, অন্য এটা এপে এই আগ্ৰহসমূহৰ ভিত্তিত আপোনাক কোনো বিজ্ঞাপন দেখুৱাব পাৰে।\n\nযদি আপুনি কোনো এপ্ অৱৰোধ কৰে, সেইটোৱে আৰু কোনো আগ্ৰহ অনুমান কৰিব নোৱাৰিব। আপুনি ইয়াক অৱৰোধৰ পৰা আঁতৰাই নিদিয়ালৈকে ইয়াক এপৰ এই সূচীখনত যোগ দিয়া নহ’ব। এপ্‌টোৱে ইতিমধ্যে অনুমান কৰা আগ্ৰহসমূহ মচি পেলোৱা হ’ব, কিন্তু আপুনি তথাপি কিছুমান প্ৰাসংগিক বিজ্ঞাপন দেখিব পাৰে।"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"আপুনি অৱৰোধ কৰা এপ্"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"এপে অনুমান কৰা আগ্ৰহ ৰিছেট কৰক"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"প্ৰাইভেচি ছেণ্ডবক্স"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androidৰ বিজ্ঞাপনৰ গোপনীয়তাৰ বিটায়ে নতুন সুবিধাসমূহ দিয়ে, যিসমূহ সুবিধা এপে আপোনাক আপুনি পচন্দ কৰিব পৰা ধৰণৰ বিজ্ঞাপন দেখুৱাবলৈ ব্যৱহাৰ কৰিব পাৰে। এই প্ৰযুক্তিসমূহে ডিভাইচ চিনাক্তকাৰী ব্যৱহাৰ নকৰে।\n\nএপে আপুনি আগ্ৰহী হ’ব পৰা ধৰণৰ বিজ্ঞাপন অনুমান কৰিব পাৰে আৰু আপোনাৰ ডিভাইচত অস্থায়ীভাৱে আগ্ৰহসমূহ ছেভ কৰিব পাৰে। এইটোৱে সমগ্ৰ ৱেবছাইট আৰু অন্য বিকাশকৰ্তাৰ এপত আপোনাৰ কাৰ্যকলাপ ট্ৰেক নকৰাকৈয়ে আপোনাৰ বাবে প্ৰাসংগিক বিজ্ঞাপন দেখুৱাবলৈ এপক অনুমতি দিয়ে।"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"আপোনাৰ ওচৰত কোনো অৱৰোধিত এপ্ নাই"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"আপোনাৰ ওচৰত অৱৰোধ কৰি থোৱা কোনো আগ্ৰহ নাই"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"বাতিল কৰক"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"প্ৰাইভেচি ছেণ্ডবক্স অফ কৰিবনে?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"আপুনি যদি নিজৰ সিদ্ধান্ত সলনি কৰে অথবা Androidৰ বিজ্ঞাপনৰ গোপনীয়তা বিটাৰ বিষয়ে অধিক জানিবলৈ বিচাৰে, আপোনাৰ গোপনীয়তাৰ ছেটিঙলৈ যাওক"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"অফ কৰক"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> অৱৰোধ কৰিবনে?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"এই আগ্ৰহটো অৱৰোধ কৰা হ’ব আৰু আপুনি ইয়াক পুনৰ যোগ নিদিয়া পৰ্যন্ত এইটো আপোনাৰ সূচীত পুনৰ যোগ দিয়া নহ’ব। আপুনি তথাপি কিছুমান প্ৰাসংগিক বিজ্ঞাপন দেখা পাব পাৰে।"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"অৱৰোধ কৰক"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g>ক অৱৰোধৰ পৰা আঁতৰোৱা হ’ল"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Androidএ আপোনাৰ সূচীখনত এই আগ্ৰহটো পুনৰ যোগ দিব পাৰে, কিন্তু আপোনাৰ তৎক্ষণাৎ প্ৰদৰ্শিত নহ’ব পাৰে"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ঠিক আছে"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"আপোনাৰ আটাইবোৰ আগ্ৰহ ৰিছেট কৰিবনে?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"আপোনাৰ সূচীখন মচা হ’ব আৰু এতিয়াৰ পৰা নতুন আগ্ৰহৰ অনুমান কৰা হ’ব। আপুনি তথাপি মচি পেলোৱা আগ্ৰহসমূহৰ সৈতে প্ৰাসংগিক কিছুমান বিজ্ঞাপন দেখা পাব পাৰে।"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"ৰিছেট কৰক"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> অৱৰোধ কৰিবনে?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"এই এপ্‌টোৱে প্ৰাইভেচি ছেণ্ডবক্সৰ বাবে আগ্ৰহসমূহ অনুমান নকৰে আৰু আপুনি ইয়াক অৱৰোধৰ পৰা আঁতৰাই নিদিয়ালৈকে ইয়াক আপোনাৰ সূচীখনত পুনৰ যোগ দিয়া নহ’ব।\n\nএই এপ্‌টোৱে ইতিমধ্যে অনুমান কৰা আগ্ৰহসমূহ মচা হ’ব কিন্তু আপুনি তথাপিও কিছুমান প্ৰাসংগিক বিজ্ঞাপন দেখা পাব পাৰে।"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g>ক অৱৰোধৰ পৰা আঁতৰোৱা হ’ল"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"এপ্‌টোৱে পুনৰ আপোনাৰ বাবে আগ্ৰহসমূহ অনুমান কৰিব পাৰে, কিন্তু সেয়া আপোনাৰ সূচীখনত তৎক্ষণাৎ প্ৰদৰ্শিত নহ’ব পাৰে। আপুনি প্ৰাসংগিক বিজ্ঞাপনসমূহ দেখা পাবলৈ কিছু সময় লাগিব পাৰে।"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"এপে সৃষ্টি কৰা আগ্ৰহসমূহ ৰিছেট কৰিবনে?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"আপোনাৰ সূচীত থকা এপ্‌সমূহে অনুমান কৰা আগ্ৰহসমূহ প্ৰাইভেচি ছেণ্ডবক্স পৰা মচি পেলোৱা হ’ব আৰু এতিয়াৰ পৰা এপ্‌সমূহে নতুন আগ্ৰহৰ অনুমান কৰিব। আপুনি তথাপি কিছুমান প্ৰাসংগিক বিজ্ঞাপন দেখা পাব পাৰে।"</string>
<string name="topic10001" msgid="1636806320891333775">"কলা আৰু বিনোদন"</string>
<string name="topic10002" msgid="1226367977754287428">"অভিনয় আৰু থিয়েটাৰ"</string>
<string name="topic10003" msgid="6949890838957814881">"এনিমে’ আৰু মাংগা"</string>
diff --git a/adservices/apk/res/values-az/strings.xml b/adservices/apk/res/values-az/strings.xml
index 14fd95ed7..5cde26dcb 100644
--- a/adservices/apk/res/values-az/strings.xml
+++ b/adservices/apk/res/values-az/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Necə iştirak etmək olar"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Beta versiyasını aktiv etmək tətbiqlərə sizə reklam göstərmək üçün bu yeni, daha məxfi üsulları sınamağa imkan verir. İstənilən vaxt məxfilik ayarlarınızda beta versiyasını deaktiv edə bilərsiniz."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Xeyr"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Bəli, qoşulacağam"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Aktiv edin"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Ardı"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"İştirak etdiyiniz üçün təşəkkür edirik"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Android\'in reklam məxfiliyi beta versiyasına qoşulmusunuz. Məxfilik Sendboksu cihazınız üçün aktiv edilib.\n\nİstənilən vaxt məxfilik ayarlarınızda daha çox öyrənə və ya beta versiyasını deaktiv edə bilərsiniz."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"İştirak etməməyi seçmisiniz"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Hazırda göstəriləcək maraq yoxdur"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Məxfilik Sendboksu"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android\'in reklam məxfiliyi beta versiyası bəyəndiyiniz reklamları göstərmək üçün tətbiqlərin istifadə edə biləcəyi yeni funksiyalar təqdim edir. Bu texnologiyalar cihaz identifikatorlarından istifadə etmir.\n\nAndroid sizi maraqlandıra biləcək reklam növlərini təxmin edə və maraqlarınızı müvəqqəti olaraq cihazınızda saxlaya bilər. Bu, tətbiqlərə vebsaytlar və digər tərtibatçıların tətbiqlərində fəaliyyətinizi izləmədən sizə müvafiq reklamlar göstərməyə imkan verir."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Bloklanmış maraqlarınız yoxdur"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Tətbiqlər maraqlarınızı təxmin edilə və onları Android\'də müvəqqəti saxlaya bilər. Daha sonra fərqli tətbiq sizə bu maraqlara əsaslanan reklam göstərə bilər.\n\nTətbiqi bloklasanız, başqa maraqları təxmin etməyəcək. Onu blokdan çıxarmasanız, yenidən bu tətbiq siyahısına əlavə edilməyəcək. Tətbiq tərəfindən artıq təxmin edilən maraqlar silinəcək, lakin siz hələ də bəzi əlaqəli reklamları görə bilərsiniz."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Blok etdiyiniz tətbiqlər"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Tətbiqlər tərəfindən təxmin edilən maraqları sıfırlayın"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Məxfilik Sendboksu"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android\'in reklam məxfiliyi beta versiyası bəyəndiyiniz reklamları göstərmək üçün tətbiqlərin istifadə edə biləcəyi yeni funksiyalar təqdim edir. Bu texnologiyalar cihaz identifikatorlarından istifadə etmir.\n\nTətbiqlər sizi maraqlandıra biləcək reklam növlərini təxmin edə və maraqlarınızı müvəqqəti olaraq cihazınızda saxlaya bilər. Bu, tətbiqlərə vebsaytlar və digər tərtibatçıların tətbiqlərində fəaliyyətinizi izləmədən sizə müvafiq reklamlar göstərməyə imkan verir."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Bloklanmış tətbiqləriniz yoxdur"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Bloklanmış maraqlarınız yoxdur"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Ləğv edin"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Məxfilik Sendboksu deaktiv edilsin?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Fikrinizi dəyişsəniz və ya Android\'in reklam məxfiliyi beta versiyası haqqında ətraflı öyrənmək istəyirsinizsə, məxfilik ayarlarınıza keçin"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Deaktiv edin"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> blok edilsin?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Bu maraq bloklanacaq və onu geri əlavə etməyincə yenidən siyahınıza əlavə edilməyəcək. Hələ də bəzi əlaqəli reklamları görə bilərsiniz."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blok edin"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> blokdan çıxarılıb"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android bu marağı yenidən siyahınıza əlavə edə bilər, lakin o, dərhal görünməyə bilər"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Bütün maraqlarınız sıfırlansın?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Siyahınız təmizlənəcək və irəlidə yeni maraqlar təxmin ediləcək. Hələ də təmizlənmiş maraqlarla əlaqəli bəzi reklamları görə bilərsiniz."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Sıfırlayın"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> blok edilsin?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Bu tətbiq Məxfilik Sendboksu üçün maraqları təxmin etməyəcək və onu blokdan çıxarmayınca yenidən siyahınıza əlavə edilməyəcək.\n\nBu tətbiq tərəfindən artıq təxmin edilən maraqlar silinəcək, lakin siz hələ də bəzi əlaqəli reklamları görə bilərsiniz."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> blokdan çıxarılıb"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Bu tətbiq maraqlarınızı yenidən qiymətləndirə bilər, lakin o, dərhal siyahınızda görünməyə bilər. Əlaqədar reklamları görmək bir qədər çəkə bilər."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Tətbiqlər tərəfindən yaradılan maraqlar sıfırlansın?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Siyahınızdakı tətbiqlər tərəfindən təxmin edilən maraqlar Məxfilik Sendboksundan silinəcək və tətbiqlər yeni maraqları təxmin edəcək. Hələ də bəzi əlaqəli reklamları görə bilərsiniz."</string>
<string name="topic10001" msgid="1636806320891333775">"İncəsənət &amp; Əyləncə"</string>
<string name="topic10002" msgid="1226367977754287428">"Aktyorluq &amp; Teatr"</string>
<string name="topic10003" msgid="6949890838957814881">"Animasiya &amp; Manqa"</string>
diff --git a/adservices/apk/res/values-b+sr+Latn/strings.xml b/adservices/apk/res/values-b+sr+Latn/strings.xml
index 8e1406ec6..84fe3b2df 100644
--- a/adservices/apk/res/values-b+sr+Latn/strings.xml
+++ b/adservices/apk/res/values-b+sr+Latn/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Kako se učestvuje"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Uključivanje beta verzije omogućava aplikacijama da testiraju ove nove privatnije načine za prikazivanje oglasa. Uvek možete da isključite beta verziju u podešavanjima privatnosti."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ne, hvala"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Da, pridružiću se"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Uključi"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Još"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Hvala vam na učešću"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Deo ste beta verzije Android privatnosti za oglase. Zaštićeno okruženje privatnosti je uključeno za uređaj.\n\nMožete da saznate više ili isključite beta verziju u bilo kom trenutku u podešavanjima privatnosti."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Odabrali ste da ne učestvujete"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Trenutno nema interesovanja za prikaz"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Zaštićeno okruženje privatnosti"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Beta verzija Android privatnosti za oglase pruža nove funkcije koje aplikacije mogu da koriste da bi vam prikazivale oglase koji će vam se možda dopasti. Ove tehnologije ne koriste identifikatore uređaja.\n\nAndroid može da procenjuje vrste oglasa koji će vas možda zanimati i čuva interesovanja privremeno na uređaju. To omogućava aplikacijama da vam prikazuju relevantne oglase, bez praćenja vaših aktivnosti na veb-sajtovima i u aplikacijama drugih programera."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemate nijedno blokirano interesovanje"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikacije mogu da procenjuju interesovanja i privremeno ih čuvaju pomoću Android-a. Kasnije druga aplikacija može da vam prikaže oglas na osnovu tih interesovanja.\n\nAko blokirate aplikaciju, ona više neće procenjivati interesovanja. Neće biti dodata na ovu listu aplikacija ponovo ako je ne odblokirate. Interesovanja koja je aplikacija već procenila biće izbrisana, ali ćete možda i dalje videti neke povezane oglase."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplikacije koje ste blokirali"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Resetujte interesovanja koja su procenile aplikacije"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Zaštićeno okruženje privatnosti"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Beta verzija Android privatnosti za oglase pruža nove funkcije koje aplikacije mogu da koriste da bi vam prikazivale oglase koji će vam se možda dopasti. Ove tehnologije ne koriste identifikatore uređaja.\n\nAplikacije mogu da procenjuju vrste oglasa koji će vas možda zanimati i čuvaju interesovanja privremeno na uređaju. To omogućava aplikacijama da vam prikazuju relevantne oglase, bez praćenja vaših aktivnosti na veb-sajtovima i u aplikacijama drugih programera."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nemate nijednu blokiranu aplikaciju"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemate nijedno blokirano interesovanje"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Otkaži"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Želite da isključite Zaštićeno okruženje privatnosti?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ako se predomislite ili želite da saznate više o Android privatnosti sa oglasima beta, idite u podešavanja privatnosti"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Isključi"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Želite da blokirate temu <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Ovo interesovanje će biti blokirano i neće ponovo biti dodato na listu ako ga vi lično ponovo ne dodate. I dalje mogu da vam se prikazuju neki srodni oglasi."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokiraj"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Tema <xliff:g id="TOPIC">%1$s</xliff:g> je odblokirana"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android može ponovo da doda ovo interesovanje na listu, ali se ono možda neće odmah pojaviti"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Potvrdi"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Želite da resetujete sva interesovanja?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Lista će se obrisati i ubuduće će se procenjivati nova interesovanja. I dalje mogu da vam se prikazuju neki oglasi povezani sa obrisanim interesovanjima."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Resetuj"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Želite da blokirate aplikaciju <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ova aplikacija neće procenjivati interesovanja za Zaštićeno okruženje privatnosti i neće biti ponovo dodata na listu.\n\nInteresovanja koja je ova aplikacija već procenila biće izbrisana, ali ćete možda i dalje videti neke srodne oglase."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> je odblokirana"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Ova aplikacija može ponovo da proceni interesovanja za vas, ali se možda neće odmah pojaviti na listi. Možda će proći neko vreme dok ne budete videli srodne oglase."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Želite da resetujete interesovanja koja su generisale aplikacije?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interesovanja koja su procenile aplikacije na listi će se izbrisati iz Zaštićenog okruženja privatnosti, a aplikacije će ubuduće procenjivati nova interesovanja. I dalje mogu da vam se prikazuju neki srodni oglasi."</string>
<string name="topic10001" msgid="1636806320891333775">"Umetnost i zabava"</string>
<string name="topic10002" msgid="1226367977754287428">"Gluma i pozorište"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime i manga"</string>
diff --git a/adservices/apk/res/values-be/strings.xml b/adservices/apk/res/values-be/strings.xml
index 15b68458c..0aa995d72 100644
--- a/adservices/apk/res/values-be/strings.xml
+++ b/adservices/apk/res/values-be/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Як прыняць удзел"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Уключэнне бэта-версіі дазволіць тэсціраваць у праграмах новыя, больш прыватныя спосабы паказваць вам рэкламу. Вы можаце ў любы час выключыць бэта-версію ў наладах прыватнасці."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Не, дзякуй"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Так, я хачу далучыцца"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Уключыць"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Разгарнуць"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Дзякуй за ўдзел"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Вы прымаеце ўдзел у тэсціраванні бэта-версіі праграмы \"Прыватнасць у рэкламе на прыладах Android\". На вашай прыладзе ўключаны пакет тэхналогій Privacy Sandbox.\n\nВы можаце ў любы час атрымаць дадатковую інфармацыю або выключыць бэта-версію ў наладах прыватнасці."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Вы адмовіліся ад удзелу"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Пакуль няма інтарэсаў"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Праграма \"Прыватнасць у рэкламе на прыладах Android\" (бэта-версія) забяспечвае ўжыванне ў праграмах новых функцый для паказу вам рэкламы. Гэтыя тэхналогіі не выкарыстоўваюць ідэнтыфікатары прылады.\n\nAndroid будзе аналізаваць, якія тыпы рэкламы могуць быць для вас цікавымі, і часова захоўваць інфармацыю пра вашы інтарэсы на вашай прыладзе. Гэта дазволіць праграмам паказваць вам рэлевантную рэкламу, не адсочваючы вашы дзеянні на вэб-сайтах і ў праграмах іншых распрацоўшчыкаў."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"У вас няма заблакіраваных інтарэсаў"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Праграмы могуць аналізаваць інфармацыю пра вашы інтарэсы і часова захоўваць яе на прыладзе Android. Пазней іншая праграма, кіруючыся гэтымі данымі, можа паказаць вам адпаведную рэкламу.\n\nКалі заблакіраваць праграму, яна не зможа аналізаваць даныя пра інтарэсы і не будзе дадавацца ў гэты спіс праграм, пакуль вы не разблакіруеце яе. Даныя пра інтарэсы, ужо прааналізаваныя праграмай, будуць выдалены, аднак вам па-ранейшаму можа паказвацца адпаведная рэклама."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Праграмы, якія вы заблакіравалі"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Скінуць даныя пра інтарэсы, захаваныя праграмамі"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Праграма \"Прыватнасць у рэкламе на прыладах Android\" (бэта-версія) забяспечвае ўжыванне ў праграмах новых функцый для паказу вам рэкламы. Гэтыя тэхналогіі не выкарыстоўваюць ідэнтыфікатары прылады.\n\nПраграмы будуць аналізаваць, якія тыпы рэкламы могуць быць для вас цікавымі, і часова захоўваць інфармацыю пра вашы інтарэсы на вашай прыладзе. Гэта дазволіць праграмам паказваць вам рэлевантную рэкламу, не адсочваючы вашы дзеянні на вэб-сайтах і ў праграмах іншых распрацоўшчыкаў."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"У вас няма заблакіраваных праграм"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"У вас няма заблакіраваных інтарэсаў"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Скасаваць"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Выключыць Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Калі вы перадумаеце ці захочаце даведацца больш пра бэта-версію праграмы \"Прыватнасць у рэкламе на прыладах Android\", перайдзіце ў налады прыватнасці"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Выключыць"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Заблакіраваць тэму \"<xliff:g id="TOPIC">%1$s</xliff:g>\"?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Гэты інтарэс будзе заблакіраваны і не з\'явіцца ў спісе, пакуль вы самі не дадасце яго. Аднак вам па-ранейшаму можа паказвацца адпаведная рэклама."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Заблакіраваць"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Праграма \"<xliff:g id="TOPIC">%1$s</xliff:g>\" разблакіравана"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android можа зноў дадаць гэты інтарэс у ваш спіс, аднак ён не з\'явіцца там адразу ж"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Скінуць усе вашы інтарэсы?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Ваш спіс будзе ачышчаны, і інтарэсы пачнуць аналізавацца нанава. Аднак вам па-ранейшаму можа паказвацца рэклама, звязаная з выдаленымі інтарэсамі."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Скінуць"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Заблакіраваць праграму \"<xliff:g id="APP">%1$s</xliff:g>\"?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Гэта праграма не будзе ствараць інтарэсы для Privacy Sandbox і не з\'явіцца ў спісе, пакуль вы яе не разблакіруеце.\n\nІнтарэсы, ужо створаныя гэтай праграмай, будуць выдалены, аднак вам па-ранейшаму можа паказвацца адпаведная рэклама."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Праграма \"<xliff:g id="APP">%1$s</xliff:g>\" разблакіравана"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Гэта праграма зноў можа ствараць вашы інтарэсы, аднак яны не з\'явяцца ў спісе адразу ж. Адпаведная рэклама пачне вам паказвацца толькі праз некаторы час."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Скінуць інтарэсы, створаныя праграмамі?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Даныя пра інтарэсы, захаваныя праграмамі з вашага спіса, будуць выдалены з Privacy Sandbox, і праграмы пачнуць аналізаваць інтарэсы нанава. Аднак вам па-ранейшаму можа паказвацца адпаведная рэклама."</string>
<string name="topic10001" msgid="1636806320891333775">"Мастацтва і забавы"</string>
<string name="topic10002" msgid="1226367977754287428">"Акцёрскае майстэрства і тэатр"</string>
<string name="topic10003" msgid="6949890838957814881">"Анімэ і манга"</string>
diff --git a/adservices/apk/res/values-bg/strings.xml b/adservices/apk/res/values-bg/strings.xml
index 8766361c7..33880af38 100644
--- a/adservices/apk/res/values-bg/strings.xml
+++ b/adservices/apk/res/values-bg/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Как да участвате"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ако включите бета-версията, разрешавате на приложенията да изпробват тези нови по-поверителни начини за показване на реклами. Имате възможност да изключите бета-версията по всяко време от настройките си за поверителност."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Не, благодаря"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Да, ще се присъединя"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Включване"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Още"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Благодарим ви за участието"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Участвате в бета-версията на Android за поверителността при рекламите Технологията Privacy Sandbox е включена за устройството ви.\n\nМожете да научите повече или да изключите бета-версията по всяко време от настройките си за поверителност."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Избрахте да не участвате"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"В момента няма интереси, които да бъдат показани"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Бета-версията на Android за поверителността при рекламите осигурява нови функции, които приложенията могат да ползват, за да ви показват реклами, които е възможно да харесате. Тези технологии не използват идентификатори на устройството.\n\nAndroid може да определя типовете реклами, които е възможно да ви заинтересуват, и временно да ги запазва на устройството ви. Това дава възможност на приложенията да ви показват подходящи реклами, без да проследяват активността ви в уебсайтовете и приложенията от други програмисти."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Няма блокирани интереси"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Приложенията могат да определят интересите ви и временно да ги запазят на устройството ви с Android. Възможно е по-късно друго приложение да ви покаже реклама въз основа на тези интереси.\n\nАко блокирате дадено приложение, то повече няма да определя интереси и няма да бъде добавено отново към списъка с приложения, докато не го отблокирате. Определените от него интереси ще бъдат изтрити, но е възможно да продължите да виждате свързани с тях реклами."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Приложения, които сте блокирали"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Нулиране на интересите, определени от приложения"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Бета-версията на Android за поверителността при рекламите осигурява нови функции, които приложенията могат да ползват, за да ви показват реклами, които е възможно да харесате. Тези технологии не използват идентификатори на устройството.\n\nПриложенията могат да определят типовете реклами, които може да ви заинтересуват, и временно да запазят съответните интереси на устройството ви. Това дава възможност на приложенията да ви показват подходящи реклами, без да проследяват активността ви в уебсайтовете и приложенията от други програмисти."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Няма блокирани приложения"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Няма блокирани интереси"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Отказ"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Искате ли да изключите Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ако промените решението си или искате да научите повече за бета-версията на Android за поверителността при рекламите, отворете настройките за поверителност"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Изключване"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Да се блокира ли <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Този интерес ще бъде блокиран и няма да бъде включен отново в списъка ви, освен ако не го добавите ръчно. Възможно е да продължите да виждате свързани с него реклами."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Блокиране"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Темата <xliff:g id="TOPIC">%1$s</xliff:g> е отблокирана"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Този интерес може да бъде добавен отново към списъка ви, но е възможно да не се покаже веднага"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Да се нулират ли всичките ви интереси?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Списъкът ви ще бъде изчистен и от сега нататък ще се определят новите интереси. Пак можете да виждате някои реклами, свързани с изчистените интереси."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Нулиране"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Да се блокира ли <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Това приложение няма да определя интереси за Privacy Sandbox и няма да бъде добавено отново към списъка ви, освен ако не го отблокирате.\n\nОпределените от приложението интереси ще бъдат изтрити, но е възможно да продължите да виждате свързани с тях реклами."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Приложението <xliff:g id="APP">%1$s</xliff:g> е отблокирано"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Това приложение може отново да определя интересите ви, но е възможно да не се покаже веднага в списъка ви. Може да мине известно време, докато започнете да виждате свързани реклами."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Да се нулират ли интересите, генерирани от приложенията?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Интересите, определени от приложенията в списъка ви, ще бъдат изтрити от Privacy Sandbox и от сега нататък приложенията ще определят новите интереси. Възможно е да продължите да виждате някои свързани реклами."</string>
<string name="topic10001" msgid="1636806320891333775">"Изкуства и развлечения"</string>
<string name="topic10002" msgid="1226367977754287428">"Актьорско майсторство и театър"</string>
<string name="topic10003" msgid="6949890838957814881">"Аниме и манга"</string>
diff --git a/adservices/apk/res/values-bn/strings.xml b/adservices/apk/res/values-bn/strings.xml
index 80c9a9ace..68a68b73c 100644
--- a/adservices/apk/res/values-bn/strings.xml
+++ b/adservices/apk/res/values-bn/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"কীভাবে অংশ নিতে হবে"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"বিটা চালু করার মাধ্যমে, অ্যাপগুলি আপনাকে বিজ্ঞাপন দেখাতে এইসব নতুন, আরও ব্যক্তিগত উপায় পরীক্ষা করার অনুমতি দেয়। আপনার গোপনীয়তা সেটিংসে যেকোনও সময় বিটা বন্ধ করতে পারবেন।"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"না থাক"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"হ্যাঁ, আমি যোগ দেব"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"চালু করুন"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"আরও দেখুন"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"অংশগ্রহণ করার জন্য আপনাকে ধন্যবাদ"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"আপনি Android-এর বিজ্ঞাপন সম্পর্কিত গোপনীয়তা বিটার অংশ। আপনার ডিভাইসের জন্য প্রাইভেসি স্যান্ডবক্সটি চালু করা আছে।\n\nআপনি আরও জানতে পারেন বা আপনার গোপনীয়তা সেটিংস থেকে যেকোনও সময় বিটা বন্ধ করতে পারবেন।"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"আপনি যোগ না দেওয়ার সিদ্ধান্ত নিয়েছেন"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"এই মুহূর্তে দেখানোর মতো কোনও আগ্রহ নেই"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"প্রাইভেসি স্যান্ডবক্স"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android-এর বিজ্ঞাপন সম্পর্কিত গোপনীয়তা বিটা নতুন ফিচার প্রদান করে যা, আপনার পছন্দ হতে পারে এমন বিজ্ঞাপন আপনাকে দেখানোর জন্য অ্যাপ ব্যবহার করতে পারে। এইসব প্রযুক্তি ডিভাইস শনাক্তকারী ব্যবহার করে না।\n\nAndroid অনুমান করতে পারে যে কী ধরনের বিজ্ঞাপনে আপনি আগ্রহী হতে পারেন, এবং আপনার ডিভাইসে সাময়িকভাবে এইসব বিজ্ঞাপন সেভ করতে পারে। এটি অন্যান্য ডেভেলপারদের ওয়েবসাইট ও অ্যাপ জুড়ে আপনার অ্যাক্টিভিটি ট্র্যাক না করেই, এই সম্পর্কিত বিজ্ঞাপন দেখানোর জন্য অ্যাপকে অনুমতি দেয়।"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"আপনার কোনও ব্লক করা আগ্রহ নেই"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"অ্যাপ আপনার আগ্রহ অনুমান করতে পারে এবং Android-এ সাময়িকভাবে এগুলি সেভ করতে পারে। এরপরে, এইসব বিজ্ঞাপনের উপর নির্ভর করে অ্যাপ বিজ্ঞাপন দেখাতে পারে।\n\nআপনি কোনও অ্যাপ ব্লক করলে, এটি আর কোনও আগ্রহ অনুমান করবে না। আপনি আনব্লক না করা পর্যন্ত অ্যাপের এই তালিকায় এটি আর যোগ করা হবে না। ইতিমধ্যেই অ্যাপের মাধ্যমে অনুমান করা আগ্রহ সম্পর্কিত ডেটা মুছে ফেলা হবে, তবে আপনি হয়ত এই সম্পর্কিত বিজ্ঞাপন এখনও দেখতে পাবেন।"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"আপনার ব্লক করা অ্যাপ"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"অ্যাপের মাধ্যমে অনুমান করা আগ্রহ রিসেট করুন"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"প্রাইভেসি স্যান্ডবক্স"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android-এর বিজ্ঞাপন সম্পর্কিত গোপনীয়তা বিটা নতুন ফিচার প্রদান করে যা, আপনার পছন্দ হতে পারে এমন বিজ্ঞাপন আপনাকে দেখানোর জন্য অ্যাপ ব্যবহার করতে পারে। এইসব প্রযুক্তি ডিভাইস শনাক্তকারী ব্যবহার করে না।\n\nঅ্যাপ অনুমান করতে পারে যে কী ধরনের বিজ্ঞাপনে আপনি আগ্রহী হতে পারেন, এবং আপনার ডিভাইসে সাময়িকভাবে এইসব বিজ্ঞাপন সেভ করতে পারবে। এটি অন্যান্য ডেভেলপারদের ওয়েবসাইট ও অ্যাপ জুড়ে আপনার অ্যাক্টিভিটি ট্র্যাক না করেই, এই সম্পর্কিত বিজ্ঞাপন দেখানোর জন্য অ্যাপকে অনুমতি দেয়।"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"আপনার কাছে কোনও ব্লক করা অ্যাপ নেই"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"আপনার কোনও ব্লক করা আগ্রহ নেই"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"বাতিল করুন"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"প্রাইভেসি স্যান্ডবক্স বন্ধ করে দেবেন?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"আপনি মত পরিবর্তন করে Android-এর বিজ্ঞাপন গোপনীয়তা বেটা সম্পর্কে আরও জানতে চাইলে, আপনার \'গোপনীয়তা সেটিংস\' বিকল্পে যান"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"বন্ধ করুন"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> ব্লক করবেন?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"এই আগ্রহ ব্লক করে দেওয়া হবে এবং আপনি নতুন করে যোগ না করা পর্যন্ত, আর আপনার তালিকায় যোগ করা হবে না। এরপরেও, এই সম্পর্কিত কিছু বিজ্ঞাপন আপনি হয়ত এখনও দেখতে পাবেন।"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ব্লক করুন"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> আনব্লক করা আছে"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android আপনার আগ্রহের এই বিষয়টিকে আবার যোগ করতে পারে তবে, এটি সাথে সাথে তালিকায় উপস্থিত নাও হতে পারে"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ঠিক আছে"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"আপনার সব আগ্রহ রিসেট করবেন?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"আপনার তালিকা মুছে দেওয়া হবে এবং এখন থেকে নতুন আগ্রহ অনুমান করা হবে। এরপরেও, আপনার মুছে দেওয়া আগ্রহের সাথে সম্পর্কিত কিছু বিজ্ঞাপন দেখতে পেতে পারেন।"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"রিসেট করুন"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> ব্লক করবেন?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"এই অ্যাপ প্রাইভেসি স্যান্ডবক্সের জন্য আগ্রহ কতটা তা অনুমান করবে না, আপনি এটি আনব্লক না করা পর্যন্ত আরেকবার আপনার তালিকায় এটি যোগ করা হবে না।\n\nআগে থেকেই এই অ্যাপের করা আগ্রহের অনুমান মুছে দেওয়া হবে, তবুও আপনি কিছু সংশ্লিষ্ট বিজ্ঞাপন দেখতে পাবেন।"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> আনব্লক করা আছে"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"এই অ্যাপ আবার আপনার আগ্রহ অনুমান করতে পারে, তবে এগুলি সাথে সাথে আপনার তালিকায় উপলভ্য নাও হতে পারে। আপনি কিছুক্ষণ পরে একই ধরনের বিজ্ঞাপন দেখতে পেতে পারেন।"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"অ্যাপের জেনারেট করা আগ্রহ রিসেট করবেন?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"আপনার তালিকার অ্যাপগুলির মাধ্যমে অনুমান করা আগ্রহ প্রাইভেসি স্যান্ডবক্স থেকে মুছে দেওয়া হবে, এখন থেকে অ্যাপ নতুন আগ্রহের অনুমান করবে। এরপরেও, এই সম্পর্কিত কিছু বিজ্ঞাপন আপনি হয়তো দেখতে পাবেন।"</string>
<string name="topic10001" msgid="1636806320891333775">"শিল্পকলা ও বিনোদন"</string>
<string name="topic10002" msgid="1226367977754287428">"অভিনয় ও থিয়েটার"</string>
<string name="topic10003" msgid="6949890838957814881">"অ্যানিমে এবং মাঙ্গা"</string>
diff --git a/adservices/apk/res/values-bs/strings.xml b/adservices/apk/res/values-bs/strings.xml
index fe2a10b0e..c7ad1f75c 100644
--- a/adservices/apk/res/values-bs/strings.xml
+++ b/adservices/apk/res/values-bs/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Kako učestvovati"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Uključivanje beta verzije omogućava aplikacijama da testiraju ove nove, privatnije načine za prikazivanje oglasa. Možete isključiti beta verziju bilo kada u postavkama privatnosti."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ne, hvala"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Da, pridružit ću se"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Uključi"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Više"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Hvala na učešću"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Koristite Androidovu beta verziju privatnosti oglasa. Okruženje zaštićene privatnosti je uključeno za vaš uređaj.\n\nMožete saznati više ili isključiti beta verziju bilo kada u postavkama privatnosti."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Odabrali ste da ne učestvujete"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Trenutno nema interesovanja za prikaz"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Okruženje zaštićene privatnosti"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androidova beta verzija privatnosti oglasa pruža nove funkcije koje aplikacije mogu koristiti za prikazivanje oglasa koji bi vam se mogli svidjeti. Ove tehnologije ne koriste identifikatore uređaja.\n\nAndroid može procijeniti vrste oglasa koje bi vam mogle biti zanimljive i privremeno sačuvati ta interesovanja na vašem uređaju. Ovo omogućava aplikacijama da vam prikazuju relevantne oglase, ali bez praćenja vaše aktivnosti na web lokacijama i u aplikacijama drugih programera."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemate nijedno blokirano interesovanje"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikacije mogu procijeniti vaša interesovanja i privremeno ih sačuvati pomoću Androida. Druga aplikacija vam kasnije može prikazati oglas na osnovu tih interesovanja.\n\nAko blokirate aplikaciju, neće više procjenjivati interesovanja. Neće se ponovo dodati na listu aplikacija osim ako je deblokirate. Interesovanja koja je aplikacija već procijenila će se izbrisati, ali vam se i dalje mogu prikazivati neki srodni oglasi."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplikacije koje ste blokirali"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Poništi interesovanja koja su procijenile aplikacije"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Okruženje zaštićene privatnosti"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androidova beta verzija privatnosti oglasa pruža nove funkcije koje aplikacije mogu koristiti za prikazivanje oglasa koji bi vam se mogli svidjeti. Ove tehnologije ne koriste identifikatore uređaja.\n\nAplikacije mogu procijeniti vrste oglasa koji vas mogu zanimati i privremeno sačuvati ta interesovanja na vašem uređaju. Ovo omogućava aplikacijama da vam prikazuju relevantne oglase, ali bez praćenja vaše aktivnosti na web lokacijama i u aplikacijama drugih programera."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nemate blokirane aplikacije"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemate nijedno blokirano interesovanje"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Otkaži"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Isključiti Okruženje zaštićene privatnosti?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ako se predomislite ili želite saznati više o Androidovoj beta verziji privatnosti oglasa, idite u postavke privatnosti"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Isključi"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Blokirati temu <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Ovo interesovanje će se blokirati i neće se ponovo dodati na vašu listu, osim ako ga ponovo dodate. I dalje vam se mogu prikazivati srodni oglasi."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokiraj"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Tema <xliff:g id="TOPIC">%1$s</xliff:g> je deblokirana"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android može ponovo dodati ovo interesovanje na listu, ali je moguće da se neće odmah prikazati"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Uredu"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Poništiti sva interesovanja?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Lista će biti obrisana i ubuduće se će procjenjivati nova interesovanja. I dalje vam se mogu prikazivati oglasi povezani s obrisanim interesovanjima."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Poništi"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Blokirati aplikaciju <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ova aplikacija neće procjenjivati interesovanja za Okruženje zaštićene privatnosti i neće se ponovo dodati na vašu listu, osim ako je deblokirate.\n\nInteresovanja koja je aplikacija već procijenila će se izbrisati, ali vam se i dalje mogu prikazivati srodni oglasi."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> je deblokirana"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Aplikacija može ponovo procjenjivati vaša interesovanja, ali se možda neće odmah pojaviti na vašoj listi. Može proći neko vrijeme dok vam se ne prikažu srodni oglasi."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Poništiti interesovanja koja su generirale aplikacije?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interesovanja koja su procijenile aplikacije na vašoj listi bit će izbrisana iz Okruženja zaštićene privatnosti, a aplikacije će procjenjivati buduće nove interese. I dalje vam se mogu prikazivati srodni oglasi."</string>
<string name="topic10001" msgid="1636806320891333775">"Umjetnost i zabava"</string>
<string name="topic10002" msgid="1226367977754287428">"Gluma i teatar"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime i manga"</string>
diff --git a/adservices/apk/res/values-ca/strings.xml b/adservices/apk/res/values-ca/strings.xml
index b8837a22e..225987882 100644
--- a/adservices/apk/res/values-ca/strings.xml
+++ b/adservices/apk/res/values-ca/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Com s\'hi pot participar"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Activar la versió beta permet a les aplicacions provar aquestes noves maneres més privades de mostrar-te anuncis. Pots desactivar la versió beta en qualsevol moment a la configuració de privadesa."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"No, gràcies"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Sí, vull unir-m\'hi"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Activa"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Més"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Gràcies per participar"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Participes en la versió beta de privadesa d\'anuncis d\'Android. S\'ha activat Privacy Sandbox per al teu dispositiu.\n\nPots obtenir més informació o desactivar la versió beta en qualsevol moment a la configuració de privadesa."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Has triat no participar-hi"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Ara mateix, no hi ha cap interès per mostrar"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"La versió beta de la privadesa d\'anuncis d\'Android ofereix noves funcions que les aplicacions poden utilitzar per mostrar-te anuncis que et poden agradar. Aquestes tecnologies no fan servir identificadors de dispositiu.\n\nAndroid pot estimar els tipus d\'anuncis que et poden interessar i desar-los temporalment al teu dispositiu. Això permet que les aplicacions et mostrin anuncis rellevants sense fer un seguiment de la teva activitat en llocs web i aplicacions d\'altres desenvolupadors."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"No tens cap interès bloquejat"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Les aplicacions poden estimar els teus interessos i desar-los temporalment a Android. Més endavant, una altra aplicació pot mostrar-te un anunci basant-se en aquests interessos.\n\nSi bloqueges una aplicació, no estimarà més interessos. No es tornarà a afegir a aquesta llista d\'aplicacions tret que la desbloquegis. Els interessos que l\'aplicació ja hagi estimat se suprimiran, però pot ser que encara vegis alguns anuncis relacionats."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplicacions que has bloquejat"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Restableix els interessos estimats per les aplicacions"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"La versió beta de la privadesa d\'anuncis d\'Android ofereix noves funcions que les aplicacions poden utilitzar per mostrar-te anuncis que et poden agradar. Aquestes tecnologies no fan servir identificadors de dispositiu.\n\nLes aplicacions poden estimar els tipus d\'anuncis que et poden interessar i desar aquests interessos temporalment al teu dispositiu. Això permet que les aplicacions et mostrin anuncis rellevants sense fer un seguiment de la teva activitat en llocs web i aplicacions d\'altres desenvolupadors."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"No tens cap aplicació bloquejada"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"No tens cap interès bloquejat"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancel·la"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Vols desactivar Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Si canvies d\'opinió o vols obtenir més informació sobre la versió beta de la privadesa d\'anuncis d\'Android, ves a la configuració de privadesa"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Desactiva"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Vols bloquejar <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"L\'interès es bloquejarà i no es tornarà a afegir a la teva llista, tret que el tornis a afegir. Pot ser que continuïs veient alguns anuncis relacionats."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloqueja"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> s\'ha desbloquejat"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"És possible que Android torni a afegir aquest interès a la teva llista, però pot ser que no aparegui immediatament"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"D\'acord"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Vols restablir tots els teus interessos?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"La llista s\'esborrarà i s\'estimaran nous interessos a partir d\'aquest moment. Pot ser que continuïs veient alguns anuncis relacionats amb els interessos esborrats."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Restableix"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Vols bloquejar <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Aquesta aplicació no estimarà els interessos per a Privacy Sandbox i no es tornaran a afegir a la teva llista, tret que la desbloquegis.\n\nEls interessos que l\'aplicació ja hagi estimat se suprimiran, però és possible que encara vegis alguns anuncis relacionats."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> s\'ha desbloquejat"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Aquesta aplicació pot tornar a estimar els teus interessos, però pot ser que no aparegui a la teva llista immediatament. Pot ser que tardis una estona a veure anuncis relacionats."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Vols restablir els interessos generats per les aplicacions?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Els interessos estimats per les aplicacions de la llista se suprimiran de Privacy Sandbox, i les aplicacions estimaran nous interessos a partir d\'aquest moment. Pot ser que continuïs veient alguns anuncis relacionats."</string>
<string name="topic10001" msgid="1636806320891333775">"Art i entreteniment"</string>
<string name="topic10002" msgid="1226367977754287428">"Interpretació i teatre"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime i manga"</string>
diff --git a/adservices/apk/res/values-cs/strings.xml b/adservices/apk/res/values-cs/strings.xml
index 5a4780f3c..32641f06b 100644
--- a/adservices/apk/res/values-cs/strings.xml
+++ b/adservices/apk/res/values-cs/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Jak se zapojit"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Zapnutím beta verze umožníte aplikacím testovat tyto nové způsoby zobrazování reklam zajišťující vyšší ochranu soukromí. V nastavení ochrany soukromí můžete testování beta verze kdykoli vypnout."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ne, díky"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ano, zapojím se"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Zapnout"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Více"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Děkujeme vám za účast."</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Zapojili jste se do beta verze ochrany soukromí u reklam na Androidu. Technologie Privacy Sandbox je ve vašem zařízení zapnutá.\n\nV nastavení ochrany soukromí si můžete přečíst další informace nebo testování beta verze vypnout."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Rozhodli jste se nezúčastnit se"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Momentálně tu nejsou žádné zájmy"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Beta verze ochrany soukromí u reklam na Androidu přináší nové funkce, pomocí nichž vám mohou aplikace zobrazovat reklamy, které se vám budou líbit. Tyto technologie nevyužívají identifikátory zařízení.\n\nAndroid může odhadovat, jaké druhy reklam by vás mohly zajímat, a tyto zájmy dočasně ukládat do zařízení. Aplikace vám tak budou moci zobrazovat relevantní reklamy, aniž by sledovaly vaši aktivitu na webech a v aplikacích ostatních vývojářů."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemáte žádné zablokované zájmy"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikace mohou odhadovat vaše zájmy a dočasně je ukládat do systému Android. Později vám jiná aplikace na základě těchto zájmů může zobrazit reklamu.\n\nPokud aplikaci zablokujete, nebude odhadovat žádné další zájmy. Dokud ji neodblokujete, nebude do tohoto seznamu aplikací znovu přidána. Zájmy, které již aplikace odhadla, budou smazány, ale stále se vám mohou zobrazovat některé související reklamy."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplikace, které jste zablokovali"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Resetovat zájmy odhadnuté aplikacemi"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Beta verze ochrany soukromí u reklam na Androidu přináší nové funkce, pomocí nichž vám mohou aplikace zobrazovat reklamy, které se vám budou líbit. Tyto technologie nevyužívají identifikátory zařízení.\n\nAplikace mohou odhadovat, jaké druhy reklam by vás mohly zajímat, a tyto zájmy dočasně ukládat do zařízení. Aplikace vám tak budou moci zobrazovat relevantní reklamy, aniž by sledovaly vaši aktivitu na webech a v aplikacích ostatních vývojářů."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nemáte žádné zablokované aplikace"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemáte žádné zablokované zájmy"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Zrušit"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Vypnout Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Pokud změníte názor nebo budete chtít zjistit další informace o beta verzi ochrany soukromí v reklamách na Androidu, přejděte do nastavení ochrany soukromí"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Vypnout"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Zablokovat téma <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Tento zájem bude zablokován a do vašeho seznamu bude přidán jen v případě, že ho tam znovu přidáte. Nadále se ale mohou zobrazovat související reklamy."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Zablokovat"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Téma <xliff:g id="TOPIC">%1$s</xliff:g> je odblokováno"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Systém Android může tento zájem opět přidat na váš seznam, ale možná se na něm neobjeví ihned"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Resetovat všechny vaše zájmy?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Seznam bude vymazán a od teď budou odhadovány nové zájmy. Nadále se ale mohou zobrazovat reklamy související s vymazanými zájmy."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Resetovat"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Zablokovat aplikaci <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Tato aplikace nebude odhadovat zájmy pro účely technologie Privacy Sandbox a do vašeho seznamu bude přidána jen v případě, že ji odblokujete.\n\nZájmy, které již aplikace odhadla, budou smazány, ale stále se vám mohou zobrazovat některé související reklamy."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Aplikace <xliff:g id="APP">%1$s</xliff:g> je odblokována"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Tato aplikace pro vás může opět odhadovat zájmy, ale na seznamu se možná ihned neobjeví. Než se začnou zobrazovat související reklamy, může to chvíli trvat."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Resetovat zájmy vygenerované aplikacemi?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Zájmy odhadované aplikacemi na seznamu budou z Privacy Sandboxu vymazány a aplikace budou od teď odhadovat nové zájmy. Nadále se ale mohou zobrazovat související reklamy."</string>
<string name="topic10001" msgid="1636806320891333775">"Umění a zábava"</string>
<string name="topic10002" msgid="1226367977754287428">"Herectví a divadlo"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime a manga"</string>
diff --git a/adservices/apk/res/values-da/strings.xml b/adservices/apk/res/values-da/strings.xml
index 0fac23144..a561065b7 100644
--- a/adservices/apk/res/values-da/strings.xml
+++ b/adservices/apk/res/values-da/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Sådan kan du deltage"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Hvis du aktiverer betaversionen, giver du apps mulighed for at teste disse nye og mere private måder at vise dig annoncer på. Du kan altid deaktivere betaversionen i privatlivsindstillingerne."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nej tak"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ja, jeg vil gerne tilmelde mig"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Slå til"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Mere"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Tak, fordi du deltager"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Du er en del af Androids betaprogram for beskyttelse af personlige oplysninger i forbindelse med annoncer. Privacy Sandbox er aktiveret på din enhed.\n\nDu kan få flere oplysninger eller deaktivere betaversionen i privatlivsindstillingerne."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Du har valgt ikke at deltage"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Der er ingen interesser at vise lige nu"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androids beskyttelse af personlige oplysninger i forbindelse med annoncer (betaversion) giver nye funktioner, som apps kan bruge til at vise dig annoncer, der muligvis er relevante for dig. Disse teknologier anvender ikke enheds-id\'er.\n\nAndroid kan anslå, hvilke typer annoncer du muligvis er interesseret i, og gemme disse oplysninger midlertidigt på din enhed. Dette giver apps mulighed for at vise dig relevante annoncer, uden at spore din aktivitet på websites og i apps fra andre udviklere."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Du har ingen blokerede interesser"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apps kan anslå, hvilke interesser du muligvis har, og ved hjælp af Android gemme disse oplysninger. En anden app kan senere vise dig en annonce baseret på disse oplysninger.\n\nHvis du blokerer en app, anslår den ikke længere, hvilke interesser du muligvis har. Den føjes ikke til listen over apps igen, medmindre du fjerner blokeringen af den. Interesser, der allerede er anslået af appen, slettes, men du får muligvis stadig vist nogle relaterede annoncer."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps, du har blokeret"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Nulstil interesser, der er anslået af apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androids beskyttelse af personlige oplysninger i forbindelse med annoncer (betaversion) giver nye funktioner, som apps kan bruge til at vise dig annoncer, der muligvis er relevante for dig. Disse teknologier anvender ikke enheds-id\'er.\n\nApps kan anslå, hvilke typer annoncer du muligvis er interesseret i, og gemme disse oplysninger midlertidigt på din enhed. Dette giver apps mulighed for at vise dig relevante annoncer, uden at spore din aktivitet på websites og i apps fra andre udviklere."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Du har ingen blokerede apps"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Du har ingen blokerede interesser"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Annuller"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Vil du deaktivere Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Hvis du ombestemmer dig eller gerne vil have flere oplysninger om Androids betaversion af beskyttelse af personlige oplysninger i forbindelse med annoncering, skal du gå til dine privatlivsindstillinger."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Deaktiver"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Vil du blokere <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Denne interesse blokeres og føjes ikke til din liste igen, medmindre du tilføjer den igen. Der vises muligvis stadig nogle relaterede annoncer."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloker"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Blokeringen af <xliff:g id="TOPIC">%1$s</xliff:g> er fjernet"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android kan føje denne interesse til din liste igen, men den vises muligvis ikke med det samme"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Vil du nulstille alle dine interesser?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Din liste ryddes, og der vil blive anslået nye interesser fremadrettet. Der vises muligvis stadig annoncer, der er relateret til dine ryddede interesser."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Nulstil"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Vil du blokere <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Denne app estimerer ikke interesser for Privacy Sandbox, og den føjes ikke til din liste igen, medmindre du fjerner blokeringen af den.\n\nInteresser, som allerede er blevet estimeret af denne app, slettes, men du får muligvis stadig vist nogle relaterede annoncer."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Blokeringen af <xliff:g id="APP">%1$s</xliff:g> er fjernet"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Denne app kan estimere interesser for dig igen, men de vises muligvis ikke på din liste med det samme. Der kan gå noget tid, før du får vist relaterede annoncer."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Vil du nulstille interesser, der er genereret af apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"De interesser, der er anslået af apps på din liste, vil blive slettet fra Privacy Sandbox, og dine apps vil anslå nye interesser fremadrettet Der vises muligvis stadig nogle relaterede annoncer."</string>
<string name="topic10001" msgid="1636806320891333775">"Kunst og underholdning"</string>
<string name="topic10002" msgid="1226367977754287428">"Skuespil og teater"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime og manga"</string>
diff --git a/adservices/apk/res/values-de/strings.xml b/adservices/apk/res/values-de/strings.xml
index ec7e38929..ff660fd2a 100644
--- a/adservices/apk/res/values-de/strings.xml
+++ b/adservices/apk/res/values-de/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"So nimmst du am Betaprogramm teil"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Wenn du die Betaversion aktivierst, können Apps diese neuen privateren Möglichkeiten zum Anzeigen von Werbung testen. Die Betaversion lässt sich jederzeit in deinen Datenschutzeinstellungen deaktivieren."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nein danke"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ja, ich nehme teil"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Aktivieren"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Mehr"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Danke für deine Teilnahme"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Du nimmst am Betaprogramm für Android-Datenschutz bei Werbung teil. Die Privacy Sandbox ist für dein Gerät aktiviert.\n\nIn den Datenschutzeinstellungen kannst du jederzeit die Betaversion deaktivieren oder mehr erfahren."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Du möchtest nicht teilnehmen"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Momentan sind keine Interessen vorhanden"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Das Betaprogramm für Android-Datenschutz bei Werbung bietet neue Funktionen, mit denen Apps dir Werbung präsentieren können, die für dich möglicherweise interessant ist. Diese Technologien nutzen keine Geräte-IDs.\n\nAndroid kann schätzen, welche Art von Werbung dich möglicherweise interessiert, und diese Interessen vorübergehend auf deinem Gerät speichern. So können Apps dir relevante Werbung präsentieren, ohne dabei deine Aktivitäten auf Websites und in Apps anderer Entwickler zu erfassen."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Du hast keine blockierten Interessen"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apps können deine Interessen schätzen und sie vorübergehend unter Android speichern. Danach kann dir eine andere App auf Grundlage dieser Interessen eine Werbeanzeige präsentieren.\n\nWenn du eine App blockierst, schätzt sie keine Interessen mehr. Sie wird der Liste der Apps erst wieder hinzugefügt, wenn du die Blockierung aufhebst. Bereits von der App geschätzte Interessen werden gelöscht. Es kann aber sein, dass du weiterhin entsprechende Werbung siehst."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps, die ich blockiert habe"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Von Apps geschätzte Interessen zurücksetzen"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Das Betaprogramm für Android-Datenschutz bei Werbung bietet neue Funktionen, mit denen Apps dir Werbung präsentieren können, die für dich möglicherweise interessant ist. Diese Technologien nutzen keine Geräte-IDs.\n\nApps können schätzen, welche Art von Werbung dich möglicherweise interessiert, und diese Interessen vorübergehend auf deinem Gerät speichern. So können Apps dir relevante Werbung präsentieren, ohne dabei deine Aktivitäten auf Websites und in Apps anderer Entwickler zu erfassen."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Du hast keine blockierten Apps"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Du hast keine blockierten Interessen"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Abbrechen"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Privacy Sandbox deaktivieren?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Falls du es dir anders überlegst oder mehr über das Betaprogramm für Android-Datenschutz bei Werbung erfahren möchtest, rufe deine Datenschutzeinstellungen auf"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Deaktivieren"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> blockieren?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Dieses Interesse wird blockiert und nicht noch einmal zu deiner Liste hinzugefügt – es sei denn, du hebst die Blockierung auf. Unter Umständen siehst du einige zugehörige Werbeanzeigen jedoch weiterhin."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blockieren"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> ist nicht mehr blockiert"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android kann dieses Interesse wieder zu deiner Liste hinzufügen, unter Umständen erscheint es aber nicht sofort auf deiner Liste"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Alle Interessen zurücksetzen?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Deine Liste wird gelöscht und in Zukunft werden neue Interessen geschätzt. Unter Umständen siehst du einige zu gelöschten Interessen gehörige Werbeanzeigen jedoch weiterhin."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Zurücksetzen"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> blockieren?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Diese App schätzt keine Interessen für die Privacy Sandbox mehr und sie wird nicht noch einmal zu deiner Liste hinzugefügt – es sei denn, du hebst die Blockierung auf.\n\nBereits von der App geschätzte Interessen werden gelöscht. Es kann aber sein, dass du weiterhin entsprechende Werbung siehst."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> ist nicht mehr blockiert"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Diese App kann wieder deine Interessen schätzen. Unter Umständen erscheinen sie aber nicht sofort auf deiner Liste. Es kann eine Weile dauern, bis du entsprechende Werbung siehst."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Von Apps generierte Interessen zurücksetzen?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Die von den Apps auf deiner Liste geschätzten Interessen werden aus der Privacy Sandbox gelöscht. In Zukunft schätzen die Apps neue Interessen. Unter Umständen siehst du einige zugehörige Werbeanzeigen jedoch weiterhin."</string>
<string name="topic10001" msgid="1636806320891333775">"Kunst und Unterhaltung"</string>
<string name="topic10002" msgid="1226367977754287428">"Schauspielkunst und Theater"</string>
<string name="topic10003" msgid="6949890838957814881">"Manga und Anime"</string>
diff --git a/adservices/apk/res/values-el/strings.xml b/adservices/apk/res/values-el/strings.xml
index 42152fe06..ccaf4047d 100644
--- a/adservices/apk/res/values-el/strings.xml
+++ b/adservices/apk/res/values-el/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Τρόπος συμμετοχής"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Η ενεργοποίηση του προγράμματος beta επιτρέπει στις εφαρμογές να δοκιμάσουν αυτούς τους νέους πιο ιδιωτικούς τρόπους για την εμφάνιση διαφημίσεων. Μπορείτε να απενεργοποιήσετε το πρόγραμμα beta οποιαδήποτε στιγμή στις ρυθμίσεις απορρήτου."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Όχι, ευχαριστώ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ναι, θα λάβω μέρος"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Ενεργοποίηση"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Περισσότερα"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Σας ευχαριστούμε για τη συμμετοχή σας"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Συμμετέχετε στο πρόγραμμα beta απορρήτου διαφημίσεων του Android. Το Πλαίσιο ιδιωτικότητας είναι ενεργοποιημένο για τη συσκευή σας.\n\nΜπορείτε να μάθετε περισσότερα ή να απενεργοποιήσετε το πρόγραμμα beta οποιαδήποτε στιγμή στις ρυθμίσεις απορρήτου."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Επιλέξατε να μην συμμετάσχετε"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Δεν υπάρχουν ενδιαφέρονται για εμφάνιση αυτήν τη στιγμή"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Πλαίσιο ιδιωτικότητας"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Το πρόγραμμα beta απορρήτου διαφημίσεων του Android παρέχει νέες λειτουργίες τις οποίες μπορούν να χρησιμοποιούν οι εφαρμογές για να εμφανίζουν διαφημίσεις που μπορεί να σας αρέσουν. Αυτές οι τεχνολογίες δεν χρησιμοποιούν αναγνωριστικά συσκευών.\n\nΤο Android μπορεί να εκτιμήσει τα είδη διαφημίσεων που μπορεί να σας ενδιαφέρουν και να αποθηκεύσει αυτά τα ενδιαφέροντα προσωρινά στη συσκευή σας. Αυτό επιτρέπει στις εφαρμογές να εμφανίζουν διαφημίσεις που είναι συναφείς για εσάς, χωρίς να παρακολουθούν τη δραστηριότητά σας σε ιστοτόπους και εφαρμογές από άλλους προγραμματιστές."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Δεν έχετε αποκλεισμένα ενδιαφέροντα"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Οι εφαρμογές μπορούν να εκτιμήσουν τα ενδιαφέροντά σας και να τα αποθηκεύσουν προσωρινά με το Android. Αργότερα, μια διαφορετική εφαρμογή μπορεί να εμφανίσει μια διαφήμιση με βάση αυτά τα ενδιαφέροντα.\n\nΑν αποκλείσετε μια εφαρμογή, θα πάψει να εκτιμά άλλα ενδιαφέροντα. Δεν θα προστεθεί ξανά σε αυτήν τη λίστα εφαρμογών, παρά μόνο αν καταργήσετε τον αποκλεισμό της. Τα ενδιαφέροντα που έχουν ήδη εκτιμηθεί από την εφαρμογή θα διαγραφούν, αλλά ίσως συνεχίσετε να βλέπετε μερικές σχετικές διαφημίσεις."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Εφαρμογές που έχετε αποκλείσει"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Επαναφορά ενδιαφερόντων που έχουν εκτιμηθεί από εφαρμογές"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Πλαίσιο ιδιωτικότητας"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Το πρόγραμμα beta απορρήτου διαφημίσεων του Android παρέχει νέες λειτουργίες τις οποίες μπορούν να χρησιμοποιούν οι εφαρμογές για να εμφανίζουν διαφημίσεις που μπορεί να σας αρέσουν. Αυτές οι τεχνολογίες δεν χρησιμοποιούν αναγνωριστικά συσκευών.\n\nΟι εφαρμογές μπορούν να εκτιμήσουν τα είδη διαφημίσεων που μπορεί να σας ενδιαφέρουν και να αποθηκεύσουν αυτά τα ενδιαφέροντα προσωρινά στη συσκευή σας. Αυτό επιτρέπει στις εφαρμογές να εμφανίζουν διαφημίσεις που είναι συναφείς για εσάς, χωρίς να παρακολουθούν τη δραστηριότητά σας σε ιστοτόπους και εφαρμογές από άλλους προγραμματιστές."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Δεν έχετε αποκλεισμένες εφαρμογές"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Δεν έχετε αποκλεισμένα ενδιαφέροντα"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Ακύρωση"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Να απενεργοποιηθεί το Πλαίσιο ιδιωτικότητας;"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Εάν αλλάξετε γνώμη ή θέλετε να μάθετε περισσότερα σχετικά με την έκδοση beta απορρήτου διαφημίσεων του Android, μεταβείτε στις ρυθμίσεις απορρήτου."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Απενεργοποίηση"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Να αποκλειστεί το θέμα <xliff:g id="TOPIC">%1$s</xliff:g>;"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Το συγκεκριμένο ενδιαφέρον θα αποκλειστεί και δεν θα προστεθεί ξανά στη λίστα, εκτός και αν το προσθέσετε πάλι. Ενδέχεται να συνεχίσετε να βλέπετε μερικές σχετικές διαφημίσεις."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Αποκλεισμός"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Ο αποκλεισμός του θέματος <xliff:g id="TOPIC">%1$s</xliff:g> καταργήθηκε"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Το Android ενδέχεται να προσθέσει ξανά αυτό το ενδιαφέρον στη λίστα, αλλά μπορεί να μην εμφανιστεί αμέσως."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Να γίνει επαναφορά όλων των ενδιαφερόντων;"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Η λίστα σας θα διαγραφεί και τα νέα ενδιαφέροντα θα εκτιμώνται στο μέλλον. Ενδέχεται να εξακολουθείτε να βλέπετε ορισμένες διαφημίσεις που σχετίζονται με διαγραμμένα ενδιαφέροντα."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Επαναφορά"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Να αποκλειστεί η εφαρμογή <xliff:g id="APP">%1$s</xliff:g>;"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Η συγκεκριμένη εφαρμογή δεν θα κάνει εκτιμήσεις για ενδιαφέροντα για το Πλαίσιο ιδιωτικότητας και δεν θα προστεθεί ξανά στη λίστα, εκτός και αν καταργήσετε τον αποκλεισμό της.\n\nΤα ενδιαφέροντα για τα οποία έχει ήδη κάνει εκτιμήσεις αυτή η εφαρμογή θα διαγραφούν, αλλά μπορεί να εξακολουθήσετε να βλέπετε ορισμένες σχετικές διαφημίσεις."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Ο αποκλεισμός της εφαρμογής <xliff:g id="APP">%1$s</xliff:g> καταργήθηκε"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Η συγκεκριμένη εφαρμογή θα μπορεί να κάνει ξανά εκτιμήσεις για ενδιαφέροντα, αλλά ενδέχεται να μην εμφανιστούν αμέσως στη λίστα. Ενδέχεται να χρειαστεί κάποιος χρόνος για να δείτε σχετικές διαφημίσεις."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Να γίνει επαναφορά των ενδιαφερόντων που δημιουργούνται από εφαρμογές;"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Τα ενδιαφέροντα που εκτιμώνται από τις εφαρμογές στη λίστα σας θα διαγραφούν από το Πλαίσιο ιδιωτικότητας και οι εφαρμογές θα εκτιμούν νέα ενδιαφέροντα στο μέλλον. Ενδέχεται να συνεχίσετε να βλέπετε μερικές σχετικές διαφημίσεις."</string>
<string name="topic10001" msgid="1636806320891333775">"Τέχνες και ψυχαγωγία"</string>
<string name="topic10002" msgid="1226367977754287428">"Ηθοποιία και θέατρο"</string>
<string name="topic10003" msgid="6949890838957814881">"Ανιμέ και Manga"</string>
diff --git a/adservices/apk/res/values-en-rAU/strings.xml b/adservices/apk/res/values-en-rAU/strings.xml
index 02d75dcd5..941bc4aef 100644
--- a/adservices/apk/res/values-en-rAU/strings.xml
+++ b/adservices/apk/res/values-en-rAU/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"How to participate"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Turning on the beta allows apps to test these new, more private ways to show you ads. You can turn off the beta at any time in your privacy settings."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"No, thanks"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Yes, I’ll join"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Turn on"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"More"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Thank you for participating"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"You’re part of Android’s ads privacy beta. The Privacy Sandbox is turned on for your device.\n\nYou can learn more or turn off the beta at any time in your privacy settings."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"You’ve chosen not to participate"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"No interests to show right now"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android’s ads privacy beta provides new features that apps can use to show you ads that you might like. These technologies don’t use device identifiers.\n\nAndroid can estimate the kind of ads that you might be interested in and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"You have no blocked interests"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apps can estimate your interests and temporarily save these with Android. Later, a different app can show you an ad based on these interests.\n\nIf you block an app, it won’t estimate any more interests. It won’t be added to this list of apps again unless you unblock it. Interests already estimated by the app will be deleted, but you might still see some related ads."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps that you’ve blocked"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Reset interests estimated by apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android’s ads privacy beta provides new features that apps can use to show you ads that you might like. These technologies don’t use device identifiers.\n\nApps can estimate the kind of ads that you might be interested in and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"You have no blocked apps"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"You have no blocked interests"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancel"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Turn off Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"If you change your mind or want to learn more about Android’s ads privacy beta, go to your privacy settings"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Turn off"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Block <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"This interest will be blocked and won’t be added to your list again unless you add it back. You may still see some related ads."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Block"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> is unblocked"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android may add this interest to your list again, but it might not appear immediately"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Reset all of your interests?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Your list will be cleared and new interests will be estimated going forward. You may still see some ads related to cleared interests."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Reset"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Block <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"This app won’t estimate interests for the Privacy Sandbox and won’t be added to your list again, unless you unblock it.\n\nInterests already estimated by this app will be deleted, but you might still see some related ads."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> is unblocked"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"This app can estimate interests for you again, but it might not appear on your list immediately. It might take a while for you to see related ads."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Reset interests generated by apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interests estimated by the apps on your list will be deleted from the Privacy Sandbox, and the apps will estimate new interests going forward. You may still see some related ads."</string>
<string name="topic10001" msgid="1636806320891333775">"Arts &amp; entertainment"</string>
<string name="topic10002" msgid="1226367977754287428">"Acting &amp; theatre"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime &amp; manga"</string>
diff --git a/adservices/apk/res/values-en-rCA/strings.xml b/adservices/apk/res/values-en-rCA/strings.xml
index 02d75dcd5..941bc4aef 100644
--- a/adservices/apk/res/values-en-rCA/strings.xml
+++ b/adservices/apk/res/values-en-rCA/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"How to participate"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Turning on the beta allows apps to test these new, more private ways to show you ads. You can turn off the beta at any time in your privacy settings."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"No, thanks"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Yes, I’ll join"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Turn on"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"More"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Thank you for participating"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"You’re part of Android’s ads privacy beta. The Privacy Sandbox is turned on for your device.\n\nYou can learn more or turn off the beta at any time in your privacy settings."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"You’ve chosen not to participate"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"No interests to show right now"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android’s ads privacy beta provides new features that apps can use to show you ads that you might like. These technologies don’t use device identifiers.\n\nAndroid can estimate the kind of ads that you might be interested in and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"You have no blocked interests"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apps can estimate your interests and temporarily save these with Android. Later, a different app can show you an ad based on these interests.\n\nIf you block an app, it won’t estimate any more interests. It won’t be added to this list of apps again unless you unblock it. Interests already estimated by the app will be deleted, but you might still see some related ads."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps that you’ve blocked"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Reset interests estimated by apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android’s ads privacy beta provides new features that apps can use to show you ads that you might like. These technologies don’t use device identifiers.\n\nApps can estimate the kind of ads that you might be interested in and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"You have no blocked apps"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"You have no blocked interests"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancel"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Turn off Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"If you change your mind or want to learn more about Android’s ads privacy beta, go to your privacy settings"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Turn off"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Block <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"This interest will be blocked and won’t be added to your list again unless you add it back. You may still see some related ads."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Block"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> is unblocked"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android may add this interest to your list again, but it might not appear immediately"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Reset all of your interests?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Your list will be cleared and new interests will be estimated going forward. You may still see some ads related to cleared interests."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Reset"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Block <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"This app won’t estimate interests for the Privacy Sandbox and won’t be added to your list again, unless you unblock it.\n\nInterests already estimated by this app will be deleted, but you might still see some related ads."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> is unblocked"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"This app can estimate interests for you again, but it might not appear on your list immediately. It might take a while for you to see related ads."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Reset interests generated by apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interests estimated by the apps on your list will be deleted from the Privacy Sandbox, and the apps will estimate new interests going forward. You may still see some related ads."</string>
<string name="topic10001" msgid="1636806320891333775">"Arts &amp; entertainment"</string>
<string name="topic10002" msgid="1226367977754287428">"Acting &amp; theatre"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime &amp; manga"</string>
diff --git a/adservices/apk/res/values-en-rGB/strings.xml b/adservices/apk/res/values-en-rGB/strings.xml
index 02d75dcd5..941bc4aef 100644
--- a/adservices/apk/res/values-en-rGB/strings.xml
+++ b/adservices/apk/res/values-en-rGB/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"How to participate"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Turning on the beta allows apps to test these new, more private ways to show you ads. You can turn off the beta at any time in your privacy settings."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"No, thanks"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Yes, I’ll join"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Turn on"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"More"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Thank you for participating"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"You’re part of Android’s ads privacy beta. The Privacy Sandbox is turned on for your device.\n\nYou can learn more or turn off the beta at any time in your privacy settings."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"You’ve chosen not to participate"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"No interests to show right now"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android’s ads privacy beta provides new features that apps can use to show you ads that you might like. These technologies don’t use device identifiers.\n\nAndroid can estimate the kind of ads that you might be interested in and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"You have no blocked interests"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apps can estimate your interests and temporarily save these with Android. Later, a different app can show you an ad based on these interests.\n\nIf you block an app, it won’t estimate any more interests. It won’t be added to this list of apps again unless you unblock it. Interests already estimated by the app will be deleted, but you might still see some related ads."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps that you’ve blocked"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Reset interests estimated by apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android’s ads privacy beta provides new features that apps can use to show you ads that you might like. These technologies don’t use device identifiers.\n\nApps can estimate the kind of ads that you might be interested in and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"You have no blocked apps"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"You have no blocked interests"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancel"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Turn off Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"If you change your mind or want to learn more about Android’s ads privacy beta, go to your privacy settings"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Turn off"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Block <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"This interest will be blocked and won’t be added to your list again unless you add it back. You may still see some related ads."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Block"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> is unblocked"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android may add this interest to your list again, but it might not appear immediately"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Reset all of your interests?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Your list will be cleared and new interests will be estimated going forward. You may still see some ads related to cleared interests."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Reset"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Block <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"This app won’t estimate interests for the Privacy Sandbox and won’t be added to your list again, unless you unblock it.\n\nInterests already estimated by this app will be deleted, but you might still see some related ads."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> is unblocked"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"This app can estimate interests for you again, but it might not appear on your list immediately. It might take a while for you to see related ads."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Reset interests generated by apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interests estimated by the apps on your list will be deleted from the Privacy Sandbox, and the apps will estimate new interests going forward. You may still see some related ads."</string>
<string name="topic10001" msgid="1636806320891333775">"Arts &amp; entertainment"</string>
<string name="topic10002" msgid="1226367977754287428">"Acting &amp; theatre"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime &amp; manga"</string>
diff --git a/adservices/apk/res/values-en-rIN/strings.xml b/adservices/apk/res/values-en-rIN/strings.xml
index 02d75dcd5..941bc4aef 100644
--- a/adservices/apk/res/values-en-rIN/strings.xml
+++ b/adservices/apk/res/values-en-rIN/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"How to participate"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Turning on the beta allows apps to test these new, more private ways to show you ads. You can turn off the beta at any time in your privacy settings."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"No, thanks"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Yes, I’ll join"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Turn on"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"More"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Thank you for participating"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"You’re part of Android’s ads privacy beta. The Privacy Sandbox is turned on for your device.\n\nYou can learn more or turn off the beta at any time in your privacy settings."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"You’ve chosen not to participate"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"No interests to show right now"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android’s ads privacy beta provides new features that apps can use to show you ads that you might like. These technologies don’t use device identifiers.\n\nAndroid can estimate the kind of ads that you might be interested in and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"You have no blocked interests"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apps can estimate your interests and temporarily save these with Android. Later, a different app can show you an ad based on these interests.\n\nIf you block an app, it won’t estimate any more interests. It won’t be added to this list of apps again unless you unblock it. Interests already estimated by the app will be deleted, but you might still see some related ads."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps that you’ve blocked"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Reset interests estimated by apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android’s ads privacy beta provides new features that apps can use to show you ads that you might like. These technologies don’t use device identifiers.\n\nApps can estimate the kind of ads that you might be interested in and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"You have no blocked apps"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"You have no blocked interests"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancel"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Turn off Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"If you change your mind or want to learn more about Android’s ads privacy beta, go to your privacy settings"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Turn off"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Block <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"This interest will be blocked and won’t be added to your list again unless you add it back. You may still see some related ads."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Block"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> is unblocked"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android may add this interest to your list again, but it might not appear immediately"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Reset all of your interests?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Your list will be cleared and new interests will be estimated going forward. You may still see some ads related to cleared interests."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Reset"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Block <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"This app won’t estimate interests for the Privacy Sandbox and won’t be added to your list again, unless you unblock it.\n\nInterests already estimated by this app will be deleted, but you might still see some related ads."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> is unblocked"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"This app can estimate interests for you again, but it might not appear on your list immediately. It might take a while for you to see related ads."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Reset interests generated by apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interests estimated by the apps on your list will be deleted from the Privacy Sandbox, and the apps will estimate new interests going forward. You may still see some related ads."</string>
<string name="topic10001" msgid="1636806320891333775">"Arts &amp; entertainment"</string>
<string name="topic10002" msgid="1226367977754287428">"Acting &amp; theatre"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime &amp; manga"</string>
diff --git a/adservices/apk/res/values-en-rXC/strings.xml b/adservices/apk/res/values-en-rXC/strings.xml
index 1ea69d34a..aa7c4dee8 100644
--- a/adservices/apk/res/values-en-rXC/strings.xml
+++ b/adservices/apk/res/values-en-rXC/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎How to participate‎‏‎‎‏‎"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‏‏‎Turning on the beta allows apps to test these new, more private ways to show you ads. You can turn off the beta any time in your privacy settings.‎‏‎‎‏‎"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎No thanks‎‏‎‎‏‎"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎Yes, I’ll join‎‏‎‎‏‎"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎Turn On‎‏‎‎‏‎"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎More‎‏‎‎‏‎"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎Thank you for participating‎‏‎‎‏‎"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‎You’re part of Android’s ads privacy beta. The Privacy Sandbox is turned on for your device.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎You can learn more or turn off the beta any time in your privacy settings.‎‏‎‎‏‎"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‎‎‎‎You’ve chosen not to participate‎‏‎‎‏‎"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‎‎No interests to show right now‎‏‎‎‏‎"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎Privacy Sandbox‎‏‎‎‏‎"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎Android’s ads privacy beta provides new features that apps can use to show you ads you might like. These technologies don’t use device identifiers.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Android can estimate the kinds of ads you might be interested in, and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers.‎‏‎‎‏‎"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎You have no blocked interests‎‏‎‎‏‎"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎Apps can estimate your interests and temporarily save these with Android. Later, a different app can show you an ad based on these interests.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎If you block an app, it won’t estimate any more interests. It won’t be added to this list of apps again unless you unblock it. Interests already estimated by the app will be deleted, but you might still see some related ads.‎‏‎‎‏‎"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎Apps you’ve blocked‎‏‎‎‏‎"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‎Reset interests estimated by apps‎‏‎‎‏‎"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎Privacy Sandbox‎‏‎‎‏‎"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‎‎‎Android’s ads privacy beta provides new features that apps can use to show you ads you might like. These technologies don’t use device identifiers.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Apps can estimate the kinds of ads you might be interested in, and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers.‎‏‎‎‏‎"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎You have no blocked apps‎‏‎‎‏‎"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎You have no blocked interests‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎Cancel‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎Turn off Privacy Sandbox?‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎If you change your mind or want to learn more about Android’s ads privacy beta, go to your privacy settings‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‏‎‎Turn off‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‏‎‎Block ‎‏‎‎‏‏‎<xliff:g id="TOPIC">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎This interest will be blocked and won’t be added to your list again, unless you add it back. You may still see some related ads.‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎Block‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="TOPIC">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is unblocked‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‎Android may add this interest to your list again, but it might not appear immediately‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‎‏‎OK‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‎Reset all of your interests?‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‎‎Your list will be cleared and new interests will be estimated going forward. You may still see some ads related to cleared interests.‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‎‎‏‎Reset‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎Block ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎This app won’t estimate interests for the Privacy Sandbox and won’t be added to your list again, unless you unblock it.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Interests already estimated by this app will be deleted, but you might still see some related ads.‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is unblocked‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‏‎This app can estimate interests for you again, but it might not appear on your list immediately. It might take a while for you to see related ads.‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‎‎‎‏‎Reset interests generated by apps?‎‏‎‎‏‎"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎Interests estimated by the apps on your list will be deleted from the Privacy Sandbox, and the apps will estimate new interests going forward. You may still see some related ads.‎‏‎‎‏‎"</string>
<string name="topic10001" msgid="1636806320891333775">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‎Arts &amp; Entertainment‎‏‎‎‏‎"</string>
<string name="topic10002" msgid="1226367977754287428">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‎Acting &amp; Theater‎‏‎‎‏‎"</string>
<string name="topic10003" msgid="6949890838957814881">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎Anime &amp; Manga‎‏‎‎‏‎"</string>
diff --git a/adservices/apk/res/values-es-rUS/strings.xml b/adservices/apk/res/values-es-rUS/strings.xml
index 44cab11e0..8d63a0f51 100644
--- a/adservices/apk/res/values-es-rUS/strings.xml
+++ b/adservices/apk/res/values-es-rUS/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Cómo participar"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Activar la versión beta permite a las apps probar estas nuevas formas más privadas de mostrarte anuncios. Puedes desactivar la versión beta en cualquier momento desde la configuración de privacidad."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"No, gracias"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Sí, me uniré"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Activar"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Más"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Gracias por participar"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Puedes usar la versión beta de privacidad de los anuncios de Android. Privacy Sandbox está activado en tu dispositivo.\n\nPuedes obtener más información o desactivar la versión beta en cualquier momento, en tu configuración de privacidad."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Elegiste no participar"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"No hay intereses para mostrar en este momento"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"La versión beta de privacidad de los anuncios de Android ofrece nuevas funciones que las apps pueden usar para mostrarte anuncios que podrían interesarte. Estas tecnologías no utilizan identificadores de dispositivos.\n\nAndroid puede estimar los tipos de anuncios que podrían interesarte y guardar esos intereses temporalmente en tu dispositivo. Esto permite que las apps te muestren anuncios relevantes, sin rastrear tu actividad en sitios web ni apps de otros desarrolladores."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"No tienes intereses bloqueados"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Las apps pueden estimar tus intereses y guardarlos temporalmente con Android. Más tarde, otra app puede mostrarte un anuncio basado en esos intereses.\n\nSi bloqueas una app, esta no estimará más intereses. No se volverá a agregar a esta lista de apps a menos que la desbloquees. Sin embargo, si bien se borrarán los intereses ya estimados por ella, es posible que sigas viendo algunos anuncios relacionados."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps que bloqueaste"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Restablecer los intereses estimados por apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"La versión beta de privacidad de los anuncios de Android ofrece nuevas funciones que las apps pueden usar para mostrarte anuncios que podrían interesarte. Estas tecnologías no utilizan identificadores de dispositivos.\n\nLas apps pueden estimar los tipos de anuncios que podrían interesarte y guardar esos intereses temporalmente en tu dispositivo. Esto permite que las apps te muestren anuncios relevantes, sin rastrear tu actividad en sitios web ni apps de otros desarrolladores."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"No tienes ninguna app bloqueada"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"No tienes intereses bloqueados"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancelar"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"¿Quieres desactivar Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Si cambias de opinión o quieres obtener más información sobre la privacidad de los anuncios beta de Android, ve a la Configuración de privacidad"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Desactivar"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"¿Quieres bloquear a <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Se bloqueará este interés y no se agregará nuevamente a tu lista, a menos que decidas volver a agregarlo. Es posible que aún veas anuncios relacionados."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloquear"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Se desbloqueó <xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android podría agregar este interés a tu lista de nuevo, pero es posible que no aparezca de inmediato"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Aceptar"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"¿Quieres restablecer todos tus intereses?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Se borrará tu lista y, de ahora en más, se estimarán nuevos intereses. Es posible que aún veas anuncios relacionados a los intereses que se borraron."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Restablecer"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"¿Quieres bloquear a <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Esta app no calculará interesas para Privacy Sandbox ni se agregará nuevamente a tu lista, a menos que la desbloquees.\n\nSe borrarán los intereses que esta app ya haya calculado. Sin embargo, es posible que sigas viendo algunos anuncios relacionados."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Se desbloqueó <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Esta app puede volver a calcular intereses para tí, pero estos podrían no aparecer en tu lista de inmediato. Es posible que lleve un tiempo ver los anuncios relacionados."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"¿Quieres restablecer los intereses que generaron las apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Se eliminarán de Privacy Sandbox los intereses estimados por las apps en tu lista. De ahora en más, las apps estimarán nuevos intereses. Es posible que aún veas anuncios relacionados."</string>
<string name="topic10001" msgid="1636806320891333775">"Arte y entretenimiento"</string>
<string name="topic10002" msgid="1226367977754287428">"Actuación y teatro"</string>
<string name="topic10003" msgid="6949890838957814881">"Animé y manga"</string>
diff --git a/adservices/apk/res/values-es/strings.xml b/adservices/apk/res/values-es/strings.xml
index d48bc7b5f..a83418d06 100644
--- a/adservices/apk/res/values-es/strings.xml
+++ b/adservices/apk/res/values-es/strings.xml
@@ -37,19 +37,20 @@
<string name="permlab_accessAdServicesState" msgid="482058883975394398">"Acceder a la API del estado de habilitación de AdService"</string>
<string name="permdesc_accessAdServicesState" msgid="1165015604618567202">"Permite que una aplicación acceda a la API del estado de habilitación de AdService."</string>
<string name="app_label" msgid="5985320129629013968">"Sistema Android"</string>
- <string name="notificationUI_notification_title_eu" msgid="6328781145332100489">"Unirse a la beta de privacidad de anuncios de Android"</string>
+ <string name="notificationUI_notification_title_eu" msgid="6328781145332100489">"Unirse a la beta de privacidad en la publicidad de Android"</string>
<string name="notificationUI_notification_content_eu" msgid="4119222762583017840">"Android está buscando formas más privadas en las que las aplicaciones te muestren anuncios"</string>
<string name="notificationUI_notification_cta_eu" msgid="1294908454704864207">"Más información"</string>
- <string name="notificationUI_header_title_eu" msgid="4955298263079910371">"Beta de privacidad de anuncios de Android"</string>
+ <string name="notificationUI_header_title_eu" msgid="4955298263079910371">"Beta de privacidad en la publicidad de Android"</string>
<string name="notificationUI_container1_title_eu" msgid="8315199862119660570">"Novedades"</string>
<string name="notificationUI_container1_body_text_eu" msgid="1700290522706948241">"Las nuevas funciones de privacidad de Privacy Sandbox permiten que las aplicaciones te muestren anuncios relevantes, pero restringen lo que esas aplicaciones pueden llegar a saber sobre tus actividades en sitios web y aplicaciones de otros desarrolladores."</string>
<string name="notificationUI_container1_control_text_eu" msgid="3158258148321255153">"Cómo funciona"</string>
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Cómo participar"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Al activar la beta, las aplicaciones pueden probar estas nuevas formas más privadas de mostrarte anuncios. Puedes desactivar la beta cuando quieras en la configuración de privacidad."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"No, gracias"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Sí, quiero unirme"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Activar"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Más"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Gracias por tu participación"</string>
- <string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Formas parte de la beta de privacidad de anuncios de Android. Privacy Sandbox está activado en tu dispositivo.\n\nPuedes obtener más información o desactivar la beta cuando quieras en la configuración de privacidad."</string>
+ <string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Formas parte de la beta de privacidad en la publicidad de Android. Privacy Sandbox está activado en tu dispositivo.\n\nPuedes obtener más información o desactivar la beta cuando quieras en la configuración de privacidad."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Has decidido no participar"</string>
<string name="notificationUI_confirmation_decline_subtitle" msgid="4194388733937808440">"Gracias por tu respuesta. Privacy Sandbox está desactivado en tu dispositivo.\n\nSi cambias de opinión o quieres obtener más información, ve a la configuración de privacidad."</string>
<string name="notificationUI_confirmation_left_control_button_text" msgid="7459283556129950513">"Configuración de privacidad"</string>
@@ -65,7 +66,7 @@
<string name="notificationUI_how_it_works_expanded_text5" msgid="7257714040472055781">"• Intereses estimados por aplicaciones"</string>
<string name="notificationUI_how_it_works_expanded_text6" msgid="2949292580123453023">"Las aplicaciones pueden estimar tus intereses y guardarlos temporalmente con Android. Por ejemplo, si usas una aplicación para comprar zapatillas de deporte, podría estimar que tienes interés en \"maratones\".\n\nDespués, basándose en este interés, otra aplicación diferente puede mostrarte un anuncio relacionado con maratones.\n\nDesde la configuración de privacidad, puedes gestionar la lista de aplicaciones que tienen intereses guardados."</string>
<string name="settingsUI_main_view_title" msgid="1663691082473230496">"Privacy Sandbox"</string>
- <string name="settingsUI_main_view_subtitle" msgid="2059496555507319371">"Participa en la beta de privacidad de anuncios de Android"</string>
+ <string name="settingsUI_main_view_subtitle" msgid="2059496555507319371">"Participa en la beta de privacidad en la publicidad de Android"</string>
<string name="settingsUI_privacy_sandbox_beta_switch_title" msgid="5264782269528129670">"Privacy Sandbox"</string>
<string name="settingsUI_topics_title" msgid="5996189869721397937">"Intereses estimados por Android"</string>
<string name="settingsUI_apps_title" msgid="2772289096915264175">"Aplicaciones que estiman intereses"</string>
@@ -78,17 +79,34 @@
<string name="settingsUI_reset_topics_title" msgid="6917960006583871987">"Restablecer todos los intereses"</string>
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"No hay intereses que mostrar en este momento"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
- <string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"La beta de privacidad de anuncios de Android proporciona nuevas funciones que las aplicaciones pueden usar para mostrarte anuncios que podrían interesarte. Estas tecnologías no usan identificadores de dispositivo.\n\nAndroid puede estimar el tipo de anuncios que podrían interesarte y guardar los intereses temporalmente en tu dispositivo. Esto permite que las aplicaciones te muestren anuncios relevantes sin monitorizar tu actividad en sitios web ni aplicaciones de otros desarrolladores."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"La beta de privacidad en la publicidad de Android proporciona nuevas funciones que las aplicaciones pueden usar para mostrarte anuncios que podrían interesarte. Estas tecnologías no usan identificadores de dispositivo.\n\nAndroid puede estimar el tipo de anuncios que podrían interesarte y guardar los intereses temporalmente en tu dispositivo. Esto permite que las aplicaciones te muestren anuncios relevantes sin monitorizar tu actividad en sitios web ni aplicaciones de otros desarrolladores."</string>
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"No tienes intereses bloqueados"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Las aplicaciones pueden estimar tus intereses y guardarlos temporalmente con Android. Después, otra aplicación puede mostrarte un anuncio basado en esos intereses.\n\nSi bloqueas una aplicación, dejará de estimar intereses. No se añadirá a la lista de aplicaciones de nuevo a menos que la desbloquees. Los intereses que ya había estimado la aplicación se eliminarán, pero aún podrías seguir viendo anuncios relacionados."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplicaciones que has bloqueado"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Borrar intereses estimados por aplicaciones"</string>
<string name="settingsUI_apps_view_no_apps_text" msgid="7460005016322426137">"No hay aplicaciones que estén generando intereses sobre ti en este momento"</string>
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
- <string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"La beta de privacidad de anuncios de Android proporciona nuevas funciones que las aplicaciones pueden usar para mostrarte anuncios que podrían interesarte. Estas tecnologías no usan identificadores de dispositivo.\n\nLas aplicaciones pueden estimar el tipo de anuncios que podrían interesarte y guardar los intereses temporalmente en tu dispositivo. Esto permite que las aplicaciones te muestren anuncios relevantes sin monitorizar tu actividad en sitios web ni aplicaciones de otros desarrolladores."</string>
+ <string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"La beta de privacidad en la publicidad de Android proporciona nuevas funciones que las aplicaciones pueden usar para mostrarte anuncios que podrían interesarte. Estas tecnologías no usan identificadores de dispositivo.\n\nLas aplicaciones pueden estimar el tipo de anuncios que podrían interesarte y guardar los intereses temporalmente en tu dispositivo. Esto permite que las aplicaciones te muestren anuncios relevantes sin monitorizar tu actividad en sitios web ni aplicaciones de otros desarrolladores."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"No tienes ninguna aplicación bloqueada"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"No tienes intereses bloqueados"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancelar"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"¿Desactivar Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Si cambias de opinión o quieres obtener más información sobre la beta de privacidad en la publicidad de Android, ve a la configuración de privacidad"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Desactivar"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"¿Bloquear <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Este interés se bloqueará y no se volverá a añadir a tu lista a menos que lo vuelvas a añadir. Puedes seguir viendo algunos anuncios relacionados."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloquear"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> está desbloqueado"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android puede volver a añadir este interés a tu lista, pero puede que no aparezca inmediatamente"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Aceptar"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"¿Borrar todos tus intereses?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Tu lista se borrará y de ahora en adelante se estimarán nuevos intereses. Puede que sigas viendo anuncios relacionados con los intereses eliminados."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Borrar"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"¿Bloquear <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Esta aplicación no estimará intereses para Privacy Sandbox y no se volverá a añadir a tu lista a menos que la desbloquees.\n\nLos intereses estimados por esta aplicación se eliminarán, pero podrías seguir viendo algunos anuncios relacionados."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> está desbloqueada"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Esta aplicación puede volver a estimar tus intereses, pero puede que no aparezcan en tu lista inmediatamente. Puede que tengas que esperar un poco para ver anuncios relacionados."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"¿Borrar los intereses generados por aplicaciones?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Los intereses estimados por las aplicaciones de tu lista se eliminarán de Privacy Sandbox, y las aplicaciones estimarán nuevos intereses de ahora en adelante. Puede que sigas viendo anuncios relacionados."</string>
<string name="topic10001" msgid="1636806320891333775">"Arte y entretenimiento"</string>
<string name="topic10002" msgid="1226367977754287428">"Actuación y teatro"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime y manga"</string>
@@ -237,7 +255,7 @@
<string name="topic10146" msgid="8202145175139144216">"Periféricos informáticos"</string>
<string name="topic10147" msgid="4687964705548995437">"Impresoras"</string>
<string name="topic10148" msgid="8861628145647459323">"Seguridad informática"</string>
- <string name="topic10149" msgid="1719988773455991739">"Antivirus y software malicioso"</string>
+ <string name="topic10149" msgid="1719988773455991739">"Antivirus y malware"</string>
<string name="topic10150" msgid="1482205464872465298">"Seguridad de redes"</string>
<string name="topic10151" msgid="1772262905623388968">"Electrónica de consumo"</string>
<string name="topic10152" msgid="651152740021127086">"Cámaras y videocámaras"</string>
diff --git a/adservices/apk/res/values-et/strings.xml b/adservices/apk/res/values-et/strings.xml
index 64a7f3d34..f685849cb 100644
--- a/adservices/apk/res/values-et/strings.xml
+++ b/adservices/apk/res/values-et/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Osalemine"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Kui lülitate beetaprogrammi sisse, saavad rakendused proovida neid uusi ja privaatsemaid rakenduste näitamise viise. Saate beetaprogrammi igal ajal privaatsusseadete kaudu välja lülitada."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Tänan, ei"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Jah, liitun"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Lülita sisse"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Rohkem"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Täname osalemise eest!"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Osalete Androidi reklaamide privaatsuse beetaprogrammis. Privaatsuse liivakast on teie seadme jaoks sisse lülitatud.\n\nLisateavet leiate ja beetaprogrammi saate igal ajal välja lülitada privaatsusseadete kaudu."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Otsustasite mitte osaleda"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Praegu ei ole kuvamiseks ühtegi huvi"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privaatsuse liivakast"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androidi reklaamide privaatsuse beetaprogramm pakub uusi funktsioone, mida rakendused saavad kasutada selleks, et näidata teile reklaame, mis teile meeldida võivad. Need tehnoloogiad ei kasuta seadmete identifikaatoreid.\n\nAndroid saab prognoosida, millised reklaamid teile huvi pakkuda võivad, ja salvestada need huvid ajutiselt teie seadmesse. See võimaldab rakendustel teile asjakohaseid reklaame näidata ilma teie tegevust muude arendajate veebisaitidel või rakendustes jälgimata."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Teil ei ole blokeeritud huvisid"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Rakendused saavad teie huvisid prognoosida ja need ajutiselt Androidi salvestada. Hiljem saab muu rakendus teile nende huvide alusel reklaami näidata.\n\nKui blokeerite rakenduse, ei prognoosi see enam teie huvisid. Seda ei lisata rakenduste loendisse uuesti enne, kui selle deblokeerite. Rakenduse prognoositud huvid kustutatakse, ent võite siiski näha mõningaid seotud reklaame."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Teie blokeeritud rakendused"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Lähtesta rakenduste prognoositud huvid"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privaatsuse liivakast"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androidi reklaamide privaatsuse beetaprogramm pakub uusi funktsioone, mida rakendused saavad kasutada selleks, et näidata teile reklaame, mis teile meeldida võivad. Need tehnoloogiad ei kasuta seadmete identifikaatoreid.\n\nRakendused saavad prognoosida, millised reklaamid teile huvi pakkuda võivad, ja salvestada need huvid ajutiselt teie seadmesse. See võimaldab rakendustel teile asjakohaseid reklaame näidata ilma teie tegevust muude arendajate veebisaitidel või rakendustes jälgimata."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Teil pole blokeeritud rakendusi"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Teil ei ole blokeeritud huvisid"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Tühista"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Kas lülitada privaatsuse liivakast välja"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Kui muudate meelt või soovite lisateavet Androidi reklaamide privaatsuse beetaversiooni kohta, avage privaatsusseaded"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Lülita välja"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Kas blokeerida <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"See huvi blokeeritakse ja seda ei lisata enam teie loendisse, kui te seda ise tagasi ei lisa. Võite siiski näha mõnda sellega seotud reklaami."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokeeri"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> ei ole blokeeritud"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android võib selle huvi uuesti teie loendisse lisada, kuid seda ei pruugita kohe kuvada"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Kas lähtestada kõik teie huvid?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Teie loend tühjendatakse ja edaspidi prognoositakse uusi huvisid. Võite siiski näha eemaldatud huvidega seotud reklaame."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Lähtesta"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Kas blokeerida <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"See rakendus ei prognoosi privaatsuse liivakasti jaoks huvisid ja seda ei lisata enam teie loendisse, kui te seda ei deblokeeri.\n\nHuvid, mille see rakendus on juba prognoosinud, kustutatakse, kuid võite siiski näha mõnda nendega seotud reklaami."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> ei ole blokeeritud"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"See rakendus saab teie jaoks uuesti huvisid prognoosida, kuid neid ei pruugita kohe loendis kuvada. Seotud reklaamide kuvamiseks võib aega kuluda."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Kas lähtestada rakenduste loodud huvide loend?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Teie loendis olevate rakenduste prognoositud huvid kustutatakse privaatsuse liivakastist ja rakendused prognoosivad edaspidi uusi huvisid. Võite siiski näha mõnda seotud reklaami."</string>
<string name="topic10001" msgid="1636806320891333775">"Kunst ja meelelahutus"</string>
<string name="topic10002" msgid="1226367977754287428">"Näitlemine ja teater"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime ja manga"</string>
diff --git a/adservices/apk/res/values-eu/strings.xml b/adservices/apk/res/values-eu/strings.xml
index 0266948c4..de9d58af3 100644
--- a/adservices/apk/res/values-eu/strings.xml
+++ b/adservices/apk/res/values-eu/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Beta-bertsioa probatzeko argibideak"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Beta-bertsioa aktibatuz gero, iragarkiak erakusteko modu berriak eta pribatuagoak probatzeko baimena izango dute aplikazioek. Beta-bertsioa desaktibatzeko, joan pribatutasun-ezarpenetara."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ez, eskerrik asko"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Bai, probatu nahi dut"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Aktibatu"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Gehiago"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Eskerrik asko parte hartzeagatik"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Android-eko iragarkien pribatutasunaren beta-bertsioa probatzen ari zara. Privacy Sandbox-a aktibatuta dago gailuan.\n\nInformazio gehiago lortu edo beta-bertsioa probatzeari utzi nahi izanez gero, joan pribatutasun-ezarpenetara."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Ez parte hartzea aukeratu duzu"</string>
@@ -58,17 +59,17 @@
<string name="notificationUI_container2_body_text" msgid="758560159693325060">"Aktibatuta dago Privacy Sandbox-a gailuan; beraz, aplikazioek iragarkiak erakusteko modu berriak eta pribatuagoak proba ditzakete. Beta-bertsioa desaktibatzeko, joan pribatutasun-ezarpenetara."</string>
<string name="notificationUI_left_control_button_text" msgid="6591519663037500665">"Kudeatu pribatutasun-ezarpenak"</string>
<string name="notificationUI_right_control_button_text" msgid="4418849554981401536">"Ados"</string>
- <string name="notificationUI_how_it_works_expanded_text1" msgid="5567641119079971239">"Eginbide berriak eskaintzen ditu Android-en Privacy Sandbox-ak; eginbide horiei esker, aplikazioek agian gustatuko zaizkizun iragarkiak erakutsiko dizkizute. Teknologia horiek ez dute erabiltzen gailuaren identifikatzailerik.\n\nAgian interesatuko zaizkizun iragarki motak igar ditzakete Android-ek eta aplikazioek, eta interesak gailuan aldi baterako gorde. Hala, aplikazioek agian gustatuko zaizkizun iragarkiak erakutsiko dizkizute beste garatzaile batzuen webgune eta aplikazioetan egindako jardueren jarraipena egin gabe."</string>
+ <string name="notificationUI_how_it_works_expanded_text1" msgid="5567641119079971239">"Eginbide berriak eskaintzen ditu Android-en Privacy Sandbox-ak; eginbide horiei esker, aplikazioek agian gustatuko zaizkizun iragarkiak erakutsiko dizkizute. Teknologia horiek ez dute erabiltzen gailuaren identifikatzailerik.\n\nAgian interesatuko zaizkizun iragarki motak estima ditzakete Android-ek eta aplikazioek, eta interesak gailuan aldi baterako gorde. Hala, aplikazioek agian gustatuko zaizkizun iragarkiak erakutsiko dizkizute beste garatzaile batzuen webgune eta aplikazioetan egindako jardueren jarraipena egin gabe."</string>
<string name="notificationUI_how_it_works_expanded_text2" msgid="6676443093692450581">"Privacy Sandbox-arekin iragarkiak pertsonalizatzeko aukera"</string>
<string name="notificationUI_how_it_works_expanded_text3" msgid="603165966360369018">"•  Android-ek igarritako interesak"</string>
<string name="notificationUI_how_it_works_expanded_text4" msgid="3910573920883580609">"Aldizka, erabiltzen dituzun aplikazioetan oinarrituta igartzen ditu zure interes nagusiak Android-ek; adibidez, \"kirolak\" edo \"bidaiak\".\n\nOndoren, aplikazio batek interes horiek erabiltzeko eska diezaioke Android-i, iragarki egokiagoak erakusteko.\n\nOraingo interesen zerrenda ikusteko eta gustuko ez dituzunak blokeatzeko, joan pribatutasun-ezarpenetara."</string>
<string name="notificationUI_how_it_works_expanded_text5" msgid="7257714040472055781">"•  Aplikazioek igarritako interesak"</string>
- <string name="notificationUI_how_it_works_expanded_text6" msgid="2949292580123453023">"Aplikazioek zure interesak igar ditzakete, eta Android-en aldi baterako gorde. Adibidez, baliteke korrika egiteko oinetakoak eroste aldera erabiltzen duzun aplikazio batek \"maratoiak\" interes gisa igartzea.\n\nGeroago, interes horretan oinarrituta, baliteke beste aplikazio batek maratoiekin erlazionatutako iragarki bat erakustea.\n\nInteresak gorde dituzten aplikazioen zerrenda kudeatzeko, joan pribatutasun-ezarpenetara."</string>
+ <string name="notificationUI_how_it_works_expanded_text6" msgid="2949292580123453023">"Aplikazioek zure interesak estima ditzakete, eta Android-en aldi baterako gorde. Adibidez, baliteke korrika egiteko oinetakoak eroste aldera erabiltzen duzun aplikazio batek \"maratoiak\" interes gisa estimatzea.\n\nGeroago, interes horretan oinarrituta, baliteke beste aplikazio batek maratoiekin erlazionatutako iragarki bat erakustea.\n\nInteresak gorde dituzten aplikazioen zerrenda kudeatzeko, joan pribatutasun-ezarpenetara."</string>
<string name="settingsUI_main_view_title" msgid="1663691082473230496">"Privacy Sandbox-a"</string>
<string name="settingsUI_main_view_subtitle" msgid="2059496555507319371">"Probatu Android-eko iragarkien pribatutasunaren beta-bertsioa"</string>
<string name="settingsUI_privacy_sandbox_beta_switch_title" msgid="5264782269528129670">"Privacy Sandbox-a"</string>
<string name="settingsUI_topics_title" msgid="5996189869721397937">"Android-ek igarritako interesak"</string>
- <string name="settingsUI_apps_title" msgid="2772289096915264175">"Interesak igartzen dituzten aplikazioak"</string>
+ <string name="settingsUI_apps_title" msgid="2772289096915264175">"Interesak estimatzen dituzten aplikazioak"</string>
<string name="settingsUI_main_view_info_text8" msgid="2262800588014737545">"Iragarkien neurketa"</string>
<string name="settingsUI_main_view_info_text9" msgid="5431131706102562540">"Aplikazioek Privacy Sandbox-a erabil dezakete iragarkien eraginkortasuna neurtzeko. Horretarako, Android-en iragarkien eta aplikazioen arteko interakzioei buruzko datuak gorde ditzakete iragarleek aldi baterako. Mugatuta dago gorde dezaketen datu kopurua, eta aldizka ezabatuko dira datu horiek.\n\nDatuok ezabatzeko, desaktibatu Privacy Sandbox-a."</string>
<string name="settingsUI_topics_view_subtitle" msgid="6608135144119617292">"Aldizka, erabiltzen dituzun aplikazioetan oinarrituta igartzen ditu zure interes nagusiak Android-ek. Aplikazioek interes horiek erabiltzeko eska diezaiokete Android-i, iragarki egokiagoak erakusteko.\n\nInteres bat blokeatzen baduzu, ez da berriz gehituko zerrendan baldin eta interes hori desblokeatzen ez baduzu. Hala ere, baliteke harekin erlazionatutako iragarki batzuk ikusten jarraitzea."</string>
@@ -78,17 +79,34 @@
<string name="settingsUI_reset_topics_title" msgid="6917960006583871987">"Berrezarri interes guztiak"</string>
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Une honetan ez dago interesik erakusteko"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox-a"</string>
- <string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Eginbide berriak eskaintzen ditu Android-eko iragarkien pribatutasunaren beta-bertsioak; eginbide horiei esker, aplikazioek agian gustatuko zaizkizun iragarkiak erakutsiko dizkizute. Teknologia horiek ez dute erabiltzen gailuaren identifikatzailerik.\n\nAgian interesatuko zaizkizun iragarki motak igar ditzake Android-ek, eta interesak gailuan aldi baterako gorde. Hala, aplikazioek iragarki egokiak erakutsiko dizkizute beste garatzaile batzuen webgune eta aplikazioetan egindako jardueren jarraipena egin gabe."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
- <string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikazioek zure interesak igar ditzakete, eta Android-en aldi baterako gorde. Geroago, interes horietan oinarrituta, baliteke beste aplikazio batek iragarki bat erakustea.\n\nAplikazio bat blokeatzen baduzu, ez du interes gehiagorik igarriko. Aplikazioa ez da berriz gehituko aplikazioen zerrenda honetan baldin eta aplikazio hori desblokeatzen ez baduzu. Aplikazioak igarritako interesak ezabatuko dira; hala ere, baliteke haiekin erlazionatutako iragarki batzuk ikusten jarraitzea."</string>
+ <string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Eginbide berriak eskaintzen ditu Android-eko iragarkien pribatutasunaren beta-bertsioak; eginbide horiei esker, aplikazioek agian gustatuko zaizkizun iragarkiak erakutsiko dizkizute. Teknologia horiek ez dute erabiltzen gailuaren identifikatzailerik.\n\nAgian interesatuko zaizkizun iragarki motak estima ditzake Android-ek, eta interesak gailuan aldi baterako gorde. Hala, aplikazioek iragarki egokiak erakutsiko dizkizute beste garatzaile batzuen webgune eta aplikazioetan egindako jardueren jarraipena egin gabe."</string>
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Ez daukazu blokeatutako interesik"</string>
+ <string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikazioek zure interesak estima ditzakete, eta Android-en aldi baterako gorde. Geroago, interes horietan oinarrituta, baliteke beste aplikazio batek iragarki bat erakustea.\n\nAplikazio bat blokeatzen baduzu, ez du interes gehiagorik estimatuko. Aplikazioa ez da berriz gehituko aplikazioen zerrenda honetan baldin eta aplikazio hori desblokeatzen ez baduzu. Aplikazioak estimatutako interesak ezabatuko dira; hala ere, baliteke haiekin erlazionatutako iragarki batzuk ikusten jarraitzea."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Blokeatu dituzun aplikazioak"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Berrezarri aplikazioek igarritako interesak"</string>
<string name="settingsUI_apps_view_no_apps_text" msgid="7460005016322426137">"Une honetan ez dago interesak iradokitzen dituen aplikaziorik"</string>
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox-a"</string>
- <string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Eginbide berriak eskaintzen ditu Android-eko iragarkien pribatutasunaren beta-bertsioak; eginbide horiei esker, aplikazioek agian gustatuko zaizkizun iragarkiak erakutsiko dizkizute. Teknologia horiek ez dute erabiltzen gailuaren identifikatzailerik.\n\nAgian interesatuko zaizkizun iragarki motak igar ditzakete aplikazioek, eta interesak gailuan aldi baterako gorde. Hala, aplikazioek iragarki egokiak erakutsiko dizkizute beste garatzaile batzuen webgune eta aplikazioetan egindako jardueren jarraipena egin gabe."</string>
+ <string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Eginbide berriak eskaintzen ditu Android-eko iragarkien pribatutasunaren beta-bertsioak; eginbide horiei esker, aplikazioek agian gustatuko zaizkizun iragarkiak erakutsiko dizkizute. Teknologia horiek ez dute erabiltzen gailuaren identifikatzailerik.\n\nAgian interesatuko zaizkizun iragarki motak estima ditzakete aplikazioek, eta interesak gailuan aldi baterako gorde. Hala, aplikazioek iragarki egokiak erakutsiko dizkizute beste garatzaile batzuen webgune eta aplikazioetan egindako jardueren jarraipena egin gabe."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Ez daukazu blokeatutako aplikaziorik"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Ez daukazu blokeatutako interesik"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Utzi"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Privacy Sandbox-a desaktibatu nahi duzu?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Iritziz aldatzen bazara edo Android-eko iragarkien pribatutasunaren beta-bertsioari buruzko informazio gehiago lortu nahi baduzu, joan pribatutasun-ezarpenetara"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Desaktibatu"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> blokeatu nahi duzu?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Interes hau blokeatu egingo da eta ez da gehituko berriro zerrendan, zeuk gehitzen ez baduzu. Hala ere, baliteke harekin erlazionatutako iragarki batzuk ikusten jarraitzea."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokeatu"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Desblokeatu da <xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android-ek zure interesa berriro gehi dezake zerrendan, baina baliteke berehala ez agertzea"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Ados"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Interes guztiak berrezarri nahi dituzu?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Zerrenda garbitu egingo da, eta une horretatik aurrera beste interes batzuk igarriko dira. Hala ere, baliteke garbitutako interesekin erlazionatutako iragarki batzuk ikusten jarraitzea."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Berrezarri"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> blokeatu nahi duzu?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Aplikazio honek ez ditu estimatuko Privacy Sandbox-erako interesak, eta ez da gehituko berriro zerrendan, desblokeatzen ez baduzu.\n\nAplikazioak dagoeneko estimatu dituen interesak ezabatu egingo dira, baina baliteke haiekin erlazionatutako iragarki batzuk ikusten jarraitzea."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Desblokeatu da <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Aplikazio honek berriro estima ditzake interesak, baina baliteke berehala ez agertzea zerrendan. Baliteke tarte bat behar izatea erlazionatutako iragarkiak ikusten hasteko."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Aplikazioek sortutako interesak berrezarri nahi dituzu?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Zerrendako aplikazioek igarritako interesak Privacy Sandbox-etik ezabatuko dira, eta une horretatik aurrera aplikazioek beste interes batzuk igarriko dituzte. Hala ere, baliteke erlazionatutako iragarki batzuk ikusten jarraitzea."</string>
<string name="topic10001" msgid="1636806320891333775">"Artea eta aisia"</string>
<string name="topic10002" msgid="1226367977754287428">"Interpretazioa eta antzerkia"</string>
<string name="topic10003" msgid="6949890838957814881">"Animea eta manga"</string>
@@ -147,7 +165,7 @@
<string name="topic10056" msgid="3066058636154255073">"Opera"</string>
<string name="topic10057" msgid="387445514677222034">"Telebista-gidak eta erreferentziak"</string>
<string name="topic10058" msgid="9149178644022969766">"Telebista-sareak eta kanalak"</string>
- <string name="topic10059" msgid="6300562669238320524">"Telebistako saioak"</string>
+ <string name="topic10059" msgid="6300562669238320524">"Saioak eta programak"</string>
<string name="topic10060" msgid="3901256055064835246">"Telebistako komediak"</string>
<string name="topic10061" msgid="8343924749123326062">"Dokumentalak eta ez-fikziozko saioak"</string>
<string name="topic10062" msgid="9183355218976951211">"Telenobelak"</string>
@@ -369,7 +387,7 @@
<string name="topic10278" msgid="873251714192341707">"Mezu elektronikoak eta mezularitza"</string>
<string name="topic10279" msgid="6514296374139842600">"Posta elektronikoa"</string>
<string name="topic10280" msgid="7976787187115018267">"Testu-mezuak eta istanteko mezularitza"</string>
- <string name="topic10281" msgid="4013341162811169339">"Ahots- eta bideo-txatak"</string>
+ <string name="topic10281" msgid="4013341162811169339">"Ahots- eta bideodeiak"</string>
<string name="topic10282" msgid="7810462611906257564">"Interneteko zerbitzu-hornitzaileak"</string>
<string name="topic10283" msgid="3958287780718485172">"Telefono-zerbitzuen hornitzaileak"</string>
<string name="topic10284" msgid="2415044270290467935">"Mugikorretarako tonuak eta gaiak"</string>
diff --git a/adservices/apk/res/values-fa/strings.xml b/adservices/apk/res/values-fa/strings.xml
index 0003c6498..635bdfa56 100644
--- a/adservices/apk/res/values-fa/strings.xml
+++ b/adservices/apk/res/values-fa/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"نحوه مشارکت"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"روشن کردن نسخه بتا به برنامه‌ها اجازه می‌دهد این روش‌های خصوصی‌تر جدید را برای نمایش آگهی به شما آزمایش کنند. هرزمان خواستید می‌توانید نسخه بتا را در تنظیمات حریم خصوصی خاموش کنید."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"نه متشکرم"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"بله، می‌پیوندم"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"روشن کردن"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"بیشتر"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"از مشارکت شما سپاس‌گزاریم"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"‏شما عضوی از نسخه بتای حریم خصوصی آگهی‌های Android هستید. «جعبه ایمنی حریم خصوصی» برای دستگاه شما روشن شده است.\n\nهرزمان بخواهید می‌توانید در تنظیمات حریم خصوصی اطلاعات بیشتری کسب کنید یا نسخه بتا را خاموش کنید."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"انتخاب کرده‌اید که شرکت نکنید"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"در‌حال‌حاضر هیچ علاقه‌ای برای نمایش وجود ندارد"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"جعبه ایمنی حریم خصوصی"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"‏نسخه بتای حریم خصوصی آگهی‌های Android ویژگی‌های جدیدی را ارائه می‌کند که برنامه‌ها بااستفاده از آن‌ها می‌توانند آگهی‌هایی به شما نشان دهند که شاید بپسندید. این فناوری‌ها از شناسه‌های دستگاه استفاده نمی‌کنند.\n\nAndroid می‌تواند انواع آگهی‌هایی را که ممکن است به آن‌ها علاقه داشته باشید ارزیابی کند و این علایق را به‌طور موقت در دستگاهتان ذخیره کند. این ویژگی به برنامه‌ها امکان می‌دهد بدون ردیابی فعالیت شما در وب‌سایت‌ها و برنامه‌های توسعه‌دهندگان دیگر، آگهی‌های مرتبط به شما نشان دهند."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"علاقه مسدودشده‌ای ندارید"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"‏برنامه‌ها می‌توانند علایق شما را ارزیابی کنند و آن‌ها را به‌طور موقت در Android ذخیره کنند. بعداً، برنامه دیگری می‌تواند براساس این علایق به شما آگهی نشان دهد.\n\nاگر برنامه‌ای را مسدود کنید، این برنامه دیگر علایق را ارزیابی نخواهد کرد. تا زمانی‌که آن را لغو انسداد نکنید دیگر به این فهرست برنامه اضافه نخواهد شد. علایقی که برنامه قبلاً ارزیابی کرده است حذف خواهد شد، اما ممکن است همچنان آگهی‌هایی مرتبط با آن‌ها ببینید."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"برنامه‌هایی که مسدود کرده‌اید"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"بازنشانی علایق ارزیابی‌شده توسط برنامه‌ها"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"جعبه ایمنی حریم خصوصی"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"‏نسخه بتای حریم خصوصی آگهی‌های Android ویژگی‌های جدیدی را ارائه می‌کند که برنامه‌ها بااستفاده از آن‌ها می‌توانند آگهی‌هایی به شما نشان دهند که شاید بپسندید. این فناوری‌ها از شناسه‌های دستگاه استفاده نمی‌کنند.\n\nبرنامه‌ها می‌توانند انواع آگهی‌هایی را که ممکن است به آن‌ها علاقه داشته باشید ارزیابی کنند و این علایق را به‌طور موقت در دستگاهتان ذخیره کنند. این ویژگی به برنامه‌ها امکان می‌دهد بدون ردیابی فعالیت شما در وب‌سایت‌ها و برنامه‌های توسعه‌دهندگان دیگر، آگهی‌های مرتبط به شما نشان دهند."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"هیچ برنامه مسدودشده‌ای ندارید"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"علاقه مسدودشده‌ای ندارید"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"لغو"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"«جعبه ایمنی حریم خصوصی» خاموش شود؟"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"‏اگر نظرتان عوض شد یا خواستید درباره نسخه بتای حریم خصوصی آگهی‌های Android اطلاعات بیشتری کسب کنید، به تنظیمات حریم خصوصی‌تان بروید"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"خاموش کردن"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> مسدود شود؟"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"این علاقه مسدود خواهد شد و دیگر به فهرستتان اضافه نخواهد شد، مگراینکه آن را دوباره اضافه کنید. ممکن است همچنان تعدادی آگهی مرتبط ببینید."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"مسدود کردن"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> لغو انسداد شد"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"‏Android ممکن است این علاقه را دوباره به فهرستتان اضافه کند، اما ممکن است فوراً نمایش داده نشود"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"تأیید"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"همه علایقتان بازنشانی شود؟"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"فهرستتان پاک خواهد شد و ازاین‌به‌بعد علایق جدید برآورد خواهد شد. ممکن است همچنان تعدادی آگهی مرتبط با علایق پاک‌شده مشاهده کنید."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"بازنشانی"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> مسدود شود؟"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"این برنامه علایق را برای «جعبه ایمنی حریم خصوصی» تخمین نمی‌زند و دیگر به فهرستتان اضافه نخواهد شد، مگراینکه آن را لغو انسداد کنید.\n\nعلایقی که قبلاً توسط این برنامه تخمین زده شده است حذف خواهد شد، اما ممکن است همچنان تعدادی آگهی مرتبط ببینید."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> لغو انسداد شد"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"این برنامه می‌تواند دوباره علایقتان را تخمین بزند، اما ممکن است فوراً در فهرستتان نمایش داده نشود. ممکن است مدتی طول بکشد تا آگهی‌های مرتبط را ببینید."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"علایق ایجادشده توسط برنامه‌ها بازنشانی شود؟"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"علایق برآوردشده توسط برنامه‌های موجود در فهرستتان از «جعبه ایمنی حریم خصوصی» حذف خواهد شد و برنامه‌ها ازاین‌به‌بعد علایق جدید را برآورد خواهند کرد. ممکن است همچنان تعدادی آگهی مرتبط ببینید."</string>
<string name="topic10001" msgid="1636806320891333775">"هنر و سرگرمی"</string>
<string name="topic10002" msgid="1226367977754287428">"بازیگری و تئاتر"</string>
<string name="topic10003" msgid="6949890838957814881">"انیمه و مانگا"</string>
diff --git a/adservices/apk/res/values-fi/strings.xml b/adservices/apk/res/values-fi/strings.xml
index 8c1dfa1cc..1f77d511c 100644
--- a/adservices/apk/res/values-fi/strings.xml
+++ b/adservices/apk/res/values-fi/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Osallistuminen"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Kun betaohjelma on päällä, sovellukset voivat kokeilla näitä uusia, aiempaa yksityisempiä tapoja näyttää sinulle mainoksia. Voit laittaa betaohjelman pois päältä milloin tahansa yksityisyysasetuksista."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ei kiitos"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Kyllä, haluan liittyä"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Laita päälle"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Lisää"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Kiitos osallistumisesta"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Osallistut Androidin mainosyksityisyyden betaohjelmaan. Privacy Sandbox on päällä laitteellasi.\n\nVoit lukea lisää betaohjelmasta tai laittaa sen pois päältä milloin tahansa yksityisyysasetuksista."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Päätit olla osallistumatta"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Näytettäviä kiinnostuksen kohteita ei juuri nyt ole"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androidin mainosyksityisyyden betaohjelma tarjoaa uusia ominaisuuksia, joiden avulla sovellukset voivat näyttää mainoksia, joista saatat pitää. Nämä teknologiat eivät käytä laitetunnisteita.\n\nAndroid voi arvioida, millaisista mainoksista voisit olla kiinnostunut, ja tallentaa kiinnostuksen kohteet väliaikaisesti laitteellesi. Näin sovellukset voivat näyttää osuvia mainoksia seuraamatta toimintaasi verkkosivustoilla ja muiden kehittäjien sovelluksissa."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Sinulla ei ole estettyjä kiinnostuksen kohteita"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Sovellukset voivat arvioida kiinnostuksen kohteitasi ja tallentaa ne väliaikaisesti Androidiin. Myöhemmin toinen sovellus voi näyttää sinulle kiinnostuksen kohteisiisi perustuvia mainoksia.\n\nJos estät sovelluksen, se ei enää arvioi kiinnostuksen kohteitasi. Sovellusta ei lisätä sovelluslistalle, ennen kuin kumoat eston. Sovelluksen aiemmin arvioimat kiinnostuksen kohteet poistetaan, muitta saatat yhä nähdä joitain siihen liittyviä mainoksia."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Estämäsi sovellukset"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Nollaa sovellusten arvioimat kiinnostuksen kohteet"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androidin mainosyksityisyyden betaohjelma tarjoaa uusia ominaisuuksia, joiden avulla sovellukset voivat näyttää mainoksia, joista saatat pitää. Nämä teknologiat eivät käytä laitetunnisteita.\n\nSovellukset voivat arvioida, millaisista mainoksista voisit olla kiinnostunut, ja tallentaa kiinnostuksen kohteet väliaikaisesti laitteellesi. Näin sovellukset voivat näyttää osuvia mainoksia seuraamatta toimintaasi verkkosivustoilla ja muiden kehittäjien sovelluksissa."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Sinulla ei ole estettyjä sovelluksia"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Sinulla ei ole estettyjä kiinnostuksen kohteita"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Peru"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Laitetaanko Privacy Sandbox pois päältä?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Jos muutat mielesi tai haluat lukea lisää Androidin mainosyksityisyyden betaohjelmasta, avaa yksityisyysasetukset"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Laita pois päältä"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Estetäänkö <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Kiinnostuksen kohde estetään, eikä sitä lisätä listallesi, ellet lisää sitä uudelleen. Saatat yhä nähdä joitakin aiheeseen liittyviä mainoksia."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Estä"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> on sallittu"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android voi lisätä tämän kiinnostuksen kohteen taas listallesi, mutta sitä ei ehkä näy heti"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Nollataanko kaikki kiinnostuksen kohteet?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Listasi tyhjennetään, ja jatkossa arvioidaan uudet kiinnostuksen kohteet. Saatat yhä nähdä joitakin poistettuihin kiinnostuksen kohteisiin liittyviä mainoksia."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Nollaa"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Estetäänkö <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Tämä sovellus ei arvioi kiinnostuksen kohteita Privacy Sandboxia varten, eikä sitä lisätä listallesi, ellet kumoa estoa.\n\nSovelluksen aiemmin arvioimat kiinnostuksen kohteet poistetaan, mutta saatat yhä nähdä joitakin aiheeseen liittyviä mainoksia."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> on sallittu"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Sovellus voi taas arvioida kiinnostuksen kohteitasi, mutta niitä ei ehkä näy heti. Voi mennä jonkin aikaa, ennen kuin aiheeseen liittyviä mainoksia näkyy."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Nollataanko sovellusten luomat kiinnostuksen kohteet?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Sovellusten arvioimat kiinnostuksen kohteet poistetaan Privacy Sandboxista, ja sovellukset arvioivat jatkossa uudet kiinnostuksen kohteet. Saatat yhä nähdä joitakin aiempiin aiheisiin liittyviä mainoksia."</string>
<string name="topic10001" msgid="1636806320891333775">"Taide ja viihde"</string>
<string name="topic10002" msgid="1226367977754287428">"Näytteleminen ja teatteri"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime ja manga"</string>
diff --git a/adservices/apk/res/values-fr-rCA/strings.xml b/adservices/apk/res/values-fr-rCA/strings.xml
index 43ddb7287..db2b427dd 100644
--- a/adservices/apk/res/values-fr-rCA/strings.xml
+++ b/adservices/apk/res/values-fr-rCA/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Marche à suivre pour participer"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"L\'activation de la version bêta permet aux applications de tester ces nouvelles façons plus privées de vous montrer des annonces. Vous pouvez désactiver la version bêta à tout moment dans vos paramètres de confidentialité."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Non merci"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Oui, j\'accepte"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Activer"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Plus"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Merci de votre participation"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Vous participez à la version bêta des fonctionnalités de confidentialité relatives aux annonces sur Android. Le bac à sable de confidentialité est activé sur votre appareil.\n\nVous pouvez en savoir plus ou désactiver la version bêta à tout moment dans vos paramètres de confidentialité."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Vous avez choisi de ne pas participer"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Aucun centre d\'intérêt à afficher pour le moment"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Bac à sable de confidentialité"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"La version bêta de la confidentialité des annonces d\'Android offre de nouvelles fonctionnalités que les applications peuvent utiliser pour vous montrer des annonces susceptibles de vous plaire. Ces technologies n\'utilisent pas d\'identifiants d\'appareils.\n\nAndroid peut évaluer les types d\'annonces qui pourraient vous intéresser et sauvegarder temporairement vos centres d\'intérêt sur votre appareil. Cela permet aux applications de vous montrer des annonces pertinentes, sans suivre votre activité sur les sites Web et les applications conçues par d\'autres développeurs."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Vous n\'avez aucun centre d\'intérêt bloqué"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Les applications peuvent évaluer vos centres d\'intérêt et les sauvegarder temporairement sur Android. Il se peut par la suite qu\'une autre application vous montre une annonce liée à ces centres d\'intérêt.\n\nSi vous bloquez une application, elle n\'évaluera plus aucun de vos centres d\'intérêt. Elle ne sera plus ajoutée à cette liste d\'applications tant que vous ne la débloquerez pas. Les centres d\'intérêt préalablement évalués par l\'application seront supprimés, mais vous pourriez encore voir certaines annonces associées à ceux-ci."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Applications que vous avez bloquées"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Réinitialiser les centres d\'intérêt évalués par les applications"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Bac à sable de confidentialité"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"La version bêta de la confidentialité des annonces d\'Android offre de nouvelles fonctionnalités que les applications peuvent utiliser pour vous montrer des annonces susceptibles de vous plaire. Ces technologies n\'utilisent pas d\'identifiants d\'appareils.\n\nVos applications peuvent évaluer les types d\'annonces qui pourraient vous intéresser et sauvegarder temporairement vos centres d\'intérêt sur votre appareil. Cela permet aux applications de vous montrer des annonces pertinentes, sans suivre votre activité sur les sites Web et les applications conçues par d\'autres développeurs."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Vous n\'avez aucune application bloquée"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Vous n\'avez aucun centre d\'intérêt bloqué"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Annuler"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Désactiver le Bac à sable de confidentialité?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Si vous changez d\'avis ou voulez en savoir plus sur la version bêta des fonctionnalités de confidentialité relatives aux annonces sur Android, accédez à vos paramètres de confidentialité."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Désactiver"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Bloquer <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Ce centre d\'intérêt sera bloqué et ne sera plus ajouté à votre liste, à moins que vous l\'ajoutiez vous-même à nouveau. Il est toutefois possible que vous voyiez encore certaines annonces associées."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloquer"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Le centre d\'intérêt <xliff:g id="TOPIC">%1$s</xliff:g> est débloqué"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Il est possible qu\'Android ajoute à nouveau ce centre d\'intérêt à votre liste, mais il n\'y apparaîtra peut-être pas immédiatement"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Réinitialiser tous vos centres d\'intérêt?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Votre liste sera effacée, et de nouveaux centres d\'intérêt seront évalués à l\'avenir. Toutefois, vous pourriez encore voir certaines annonces liées aux centres d\'intérêt effacés."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Réinitialiser"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Bloquer <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Cette application n\'évaluera pas les centres d\'intérêt pour le Bac à sable de confidentialité et ne sera plus ajoutée à votre liste, à moins que vous la débloquiez.\n\nLes centres d\'intérêt déjà évalués par cette application seront supprimés, mais il est possible que vous voyiez encore certaines annonces associées."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"L\'application <xliff:g id="APP">%1$s</xliff:g> est débloquée"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Cette application peut à nouveau évaluer vos centres d\'intérêt, mais il est possible qu\'elle n\'apparaisse pas immédiatement sur votre liste. Il faudra sans doute du temps avant que vous puissiez voir des annonces associées."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Réinitialiser les centres d\'intérêt générés par les applications?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Les centres d\'intérêt évalués par les applications qui figurent sur votre liste seront supprimés du bac à sable de confidentialité, et les applications évalueront de nouveaux centres d\'intérêt à l\'avenir. Il est toutefois possible que vous voyiez encore certaines annonces associées."</string>
<string name="topic10001" msgid="1636806320891333775">"Arts et divertissements"</string>
<string name="topic10002" msgid="1226367977754287428">"Théâtre"</string>
<string name="topic10003" msgid="6949890838957814881">"Films d\'animation et mangas"</string>
@@ -425,7 +443,7 @@
<string name="topic10334" msgid="8671262583721706509">"Famille et relations"</string>
<string name="topic10335" msgid="1526523762163185819">"Généalogie"</string>
<string name="topic10336" msgid="4551233571090493177">"Mariage"</string>
- <string name="topic10337" msgid="8436172065188964882">"Parents"</string>
+ <string name="topic10337" msgid="8436172065188964882">"Éducation des enfants"</string>
<string name="topic10338" msgid="8896269734682970">"Adoption"</string>
<string name="topic10339" msgid="781900237993621300">"Bébés et jeunes enfants"</string>
<string name="topic10340" msgid="6538182603360796294">"Sécurité des enfants sur Internet"</string>
diff --git a/adservices/apk/res/values-fr/strings.xml b/adservices/apk/res/values-fr/strings.xml
index 0c23c306f..dba68ccbd 100644
--- a/adservices/apk/res/values-fr/strings.xml
+++ b/adservices/apk/res/values-fr/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Modalités de participation"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Si vous activez la version bêta, les applis pourront tester de nouvelles méthodes plus respectueuses de la confidentialité pour vous proposer des annonces. Vous pouvez désactiver la version bêta à tout moment dans vos paramètres de confidentialité."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Non, merci"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Oui, je participe"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Activer"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Plus"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Merci de votre participation"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Vous testez la confidentialité des annonces Android en bêta. Privacy Sandbox est activé pour votre appareil.\n\nPour en savoir plus ou désactiver la version bêta, ouvrez à tout moment les paramètres de confidentialité."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Vous avez choisi de ne pas participer"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Aucun centre d\'intérêt à afficher pour l\'instant"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"La version bêta de la configuration des annonces Android offre de nouvelles fonctionnalités permettant aux applis de vous présenter des annonces susceptibles de vous plaire. Ces technologies n\'utilisent pas les identifiants des appareils.\n\nAndroid peut déterminer les types d\'annonces susceptibles de vous intéresser et enregistrer provisoirement ces centres d\'intérêt sur votre appareil. Les applis peuvent ainsi vous présenter des annonces pertinentes, sans surveiller vos activités sur les sites Web et les applis d\'autres développeurs."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Vous n\'avez aucun centre d\'intérêt bloqué"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Les applis peuvent déterminer vos centres d\'intérêt et les enregistrer provisoirement avec Android. Ensuite, une autre appli peut vous présenter une annonce en fonction de ces centres d\'intérêt.\n\nSi vous bloquez une appli, elle ne déterminera plus de centre d\'intérêt. Elle ne sera plus ajoutée à la liste des applis tant que vous ne l\'aurez pas débloquée. Les centres d\'intérêt qu\'elle a déjà générés seront aussi supprimés, mais vous pourrez quand même voir certaines annonces associées."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Applis que vous avez bloquées"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Réinitialiser les centres d\'intérêt déterminés par les applis"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"La version bêta de la configuration des annonces Android offre de nouvelles fonctionnalités permettant aux applis de vous présenter des annonces susceptibles de vous plaire. Ces technologies n\'utilisent aucun identifiant d\'appareil.\n\nLes applis peuvent déterminer les types d\'annonces susceptibles de vous intéresser et enregistrer provisoirement ces centres d\'intérêt sur votre appareil. Les applis peuvent ainsi vous présenter des annonces pertinentes, sans surveiller vos activités sur les sites Web et les applis d\'autres développeurs."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Vous n\'avez aucune appli bloquée"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Vous n\'avez aucun centre d\'intérêt bloqué"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Annuler"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Désactiver Privacy Sandbox ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Si vous changez d\'avis ou si vous voulez en savoir plus sur la confidentialité des annonces d\'Android (bêta), accédez à vos paramètres de confidentialité"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Désactiver"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Bloquer <xliff:g id="TOPIC">%1$s</xliff:g> ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Ce centre d\'intérêt sera bloqué et ne sera pas ajouté à nouveau à votre liste, sauf si vous l\'ajoutez vous-même. Toutefois, vous continuerez peut-être à voir des annonces qui s\'y rapportent."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloquer"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> est débloqué"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android est susceptible d\'ajouter à nouveau ce centre d\'intérêt à votre liste, mais il est possible qu\'il n\'apparaisse pas immédiatement"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Réinitialiser tous vos centres d\'intérêt ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Votre liste sera effacée, puis de nouveaux centres d\'intérêt seront estimés au fil du temps. Toutefois, vous continuerez peut-être à voir des annonces qui se rapportent aux centres d\'intérêt supprimés."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Réinitialiser"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Bloquer <xliff:g id="APP">%1$s</xliff:g> ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Cette appli ne déterminera pas les centres d\'intérêt pour Privacy Sandbox et ne sera pas ajoutée à nouveau à votre liste, sauf si vous la débloquez.\n\nLes centres d\'intérêt déjà déterminés par cette appli seront supprimés, mais vous continuerez peut-être à voir des annonces qui s\'y rapportent."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> est débloqué"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Cette appli pourra déterminer à nouveau vos centres d\'intérêt, mais il est possible qu\'ils n\'apparaissent pas immédiatement dans votre liste. Un délai peut être constaté avant que vous ne voyiez des annonces qui s\'y rapportent."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Réinitialiser les centres d\'intérêt générés par les applis ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Les centres d\'intérêt estimés par les applis de votre liste seront supprimés de Privacy Sandbox et les applis estimeront de nouveaux centres d\'intérêt à partir de maintenant. Toutefois, vous continuerez peut-être à voir des annonces qui s\'y rapportent."</string>
<string name="topic10001" msgid="1636806320891333775">"Arts et divertissements"</string>
<string name="topic10002" msgid="1226367977754287428">"Art dramatique"</string>
<string name="topic10003" msgid="6949890838957814881">"Films d\'animation et manga"</string>
diff --git a/adservices/apk/res/values-gl/strings.xml b/adservices/apk/res/values-gl/strings.xml
index bbce438e6..ff2786162 100644
--- a/adservices/apk/res/values-gl/strings.xml
+++ b/adservices/apk/res/values-gl/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Como participar?"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ao activar a versión beta, as aplicacións poden probar estas novas formas máis privadas de mostrarche anuncios. Podes desactivar a versión beta cando queiras na configuración de privacidade."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Non, grazas"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Si, quero unirme"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Activar"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Máis"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Grazas por participar"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Estás participando na versión beta de privacidade nos anuncios de Android. Privacy Sandbox está activado no teu dispositivo.\n\nPodes obter máis información ou desactivar a versión beta cando queiras desde a configuración de privacidade."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Decidiches non participar"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Neste momento non hai ningún interese para mostrar"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"A versión beta de privacidade nos anuncios de Android ofrece novas funcións que poden utilizar as aplicacións para mostrarche anuncios que poderían gustarche. Estas tecnoloxías non utilizan identificadores de dispositivos.\n\nAndroid pode determinar os tipos de anuncios que poderían interesarche e gardar eses intereses de maneira temporal no teu dispositivo. Deste xeito, as aplicacións poden mostrarche anuncios relevantes sen facer un seguimento da túa actividade nos sitios web e nas aplicacións doutros programadores."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Non tes ningún interese bloqueado"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"As aplicacións poden determinar cales son os teus intereses e gardalos de forma temporal en Android. Máis adiante, calquera delas pode mostrarche anuncios baseados neses intereses.\n\nSe bloqueas unha aplicación, deixará de suxerir cales son os teus intereses. Non se engadirá de novo a esta lista de aplicacións a menos que a desbloquees. Eliminaranse os intereses que xa determinase esa aplicación, aínda que é posible que sigas vendo algún anuncio relacionado."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplicacións que bloqueaches"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Restablecer intereses que determinan as aplicacións"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"A versión beta de privacidade nos anuncios de Android ofrece novas funcións que poden utilizar as aplicacións para mostrarche anuncios que poderían gustarche. Estas tecnoloxías non utilizan identificadores de dispositivos.\n\nAs aplicacións poden determinar os tipos de anuncios que poderían interesarche e gardar eses intereses de maneira temporal no teu dispositivo. Deste xeito, as aplicacións poden mostrarche anuncios relevantes sen facer un seguimento da túa actividade nos sitios web e nas aplicacións doutros programadores."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Non tes ningunha aplicación bloqueada"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Non tes ningún interese bloqueado"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancelar"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Queres desactivar Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Se cambias de opinión ou queres obter máis información sobre a versión beta de privacidade nos anuncios de Android, vai á configuración de privacidade"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Desactivar"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Queres bloquear <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Este interese bloquearase e non volverá a aparecer na túa lista, a non ser que o engadas de novo. É posible que sigas vendo anuncios relacionados."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloquear"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Desbloqueouse <xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"É posible que Android engada este interese á túa lista outra vez, pero podería non aparecer de inmediato"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Aceptar"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Queres restablecer todos os teus intereses?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Borrarase a túa lista e calcularanse novos intereses a partir de agora. É posible que sigas vendo anuncios relacionados cos intereses que se eliminaron."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Restablecer"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Queres bloquear <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Esta aplicación non determinará intereses para Privacy Sandbox nin se volverá engadir á túa lista, a non ser que a desbloquees.\n\nOs intereses que xa determinase esta aplicación eliminaranse, pero poderías seguir vendo anuncios relacionados."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Desbloqueouse <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Esta aplicación pode volver determinar intereses por ti, pero poderían non aparecer na túa lista de inmediato. Poderías tardar algo en ver anuncios relacionados."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Queres restablecer os intereses xerados polas aplicacións?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Os intereses estimados polas aplicacións da túa lista eliminaranse de Privacy Sandbox e as aplicacións calcularán outros novos a partir de agora. É posible que sigas vendo anuncios relacionados."</string>
<string name="topic10001" msgid="1636806320891333775">"Arte e entretemento"</string>
<string name="topic10002" msgid="1226367977754287428">"Representacións e teatro"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime e manga"</string>
diff --git a/adservices/apk/res/values-gu/strings.xml b/adservices/apk/res/values-gu/strings.xml
index c4ce0e092..23d770b0c 100644
--- a/adservices/apk/res/values-gu/strings.xml
+++ b/adservices/apk/res/values-gu/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"સહભાગી બનવાની રીત"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"બીટા વર્ઝન ચાલુ કરવાથી, ઍપને જાહેરાતો બતાવવા માટે, આ નવી વધુ ખાનગી રીતોનું પરીક્ષણ કરવાની મંજૂરી મળે છે. તમે તમારા પ્રાઇવસી સેટિંગમાં જઈને કોઈપણ સમયે બીટા વર્ઝન બંધ કરી શકો છો."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"ના, આભાર"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"હા, હું જોડાઈશ"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ચાલુ કરો"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"વધુ"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"સહભાગિતા બદલ આપનો આભાર"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"તમે Androidની જાહેરાતો સંબંધિત પ્રાઇવસીનું બીટા વર્ઝનનો ભાગ છો. તમારા ડિવાઇસ માટે પ્રાઇવસી સૅન્ડબૉક્સ ચાલુ કરવામાં આવ્યું છે.\n\nતમે તમારા પ્રાઇવસી સેટિંગમાં જઈને કોઈપણ સમયે વધુ જાણી શકો છો અથવા બીટા વર્ઝન બંધ કરી શકો છો."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"તમે ભાગ ન લેવાનું પસંદ કર્યું"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"હમણાં બતાવવા માટે કોઈ રુચિ નથી"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"પ્રાઇવસી સૅન્ડબૉક્સ"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androidનું જાહેરાતો સંબંધિત પ્રાઇવસીનું બીટા વર્ઝન નવી સુવિધાઓ પ્રદાન કરે છે, જેનો ઉપયોગ ઍપ તમને ગમી શકે તેવી જાહેરાતો બતાવવા માટે કરી શકે છે. આ ટેક્નોલોજી ડિવાઇસ ઓળખકર્તાઓનો ઉપયોગ કરતી નથી.\n\nAndroid તમને કેવા પ્રકારની જાહેરાતોમાં રુચિ હોઈ શકે છે, તેનું અનુમાન લગાવી શકે છે અને તમારા ડિવાઇસ પર હંગામી રીતે આ રુચિઓને સાચવી શકે છે. આ અન્ય ડેવલપરની વેબસાઇટ અને ઍપ પરની તમારી પ્રવૃત્તિને ટ્રૅક કર્યા વિના, તમને સંબંધિત જાહેરાતો બતાવવા માટે ઍપને મંજૂરી આપે છે."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"બ્લૉક કરેલી કોઈ રુચિ તમે ધરાવતા નથી"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"ઍપ તમારી રુચિઓનું અનુમાન લગાવી શકે છે અને Android પર તેને હંગામી રીતે સાચવી શકે છે. પછીથી, કોઈ અલગ ઍપ તમને આ રુચિઓના આધારે, કોઈ જાહેરાત બતાવી શકે છે.\n\nજો તમે કોઈ ઍપને બ્લૉક કરો, તો હવેથી તે રુચિઓનું અનુમાન લગાવશે નહીં. જ્યાં સુધી તમે તેને અનબ્લૉક કરશો નહીં, ત્યાં સુધી તેને ઍપની આ સૂચિમાં ફરીથી ઉમેરવામાં આવશે નહીં. આ ઍપ દ્વારા અગાઉ અનુમાનિત તમામ રુચિઓ ડિલીટ કરવામાં આવશે, પરંતુ તમે કેટલીક સંબંધિત જાહેરાતો જોઈ શકો છો."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"તમે બ્લૉક કરેલી ઍપ"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ઍપ દ્વારા અનુમાનિત રુચિઓ રીસેટ કરો"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"પ્રાઇવસી સૅન્ડબૉક્સ"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androidનું જાહેરાતો સંબંધિત પ્રાઇવસીનું બીટા વર્ઝન નવી સુવિધાઓ પ્રદાન કરે છે, જેનો ઉપયોગ ઍપ તમને ગમી શકે તેવી જાહેરાતો બતાવવા માટે કરી શકે છે. આ ટેક્નોલોજી ડિવાઇસ ઓળખકર્તાઓનો ઉપયોગ કરતી નથી.\n\nઍપ તમને કેવા પ્રકારની જાહેરાતોમાં રુચિ હોઈ શકે છે, તેનું અનુમાન લગાવી શકે છે અને તમારા ડિવાઇસ પર હંગામી રીતે આ રુચિઓને સાચવી શકે છે. આ અન્ય ડેવલપરની વેબસાઇટ અને ઍપ પરની તમારી પ્રવૃત્તિને ટ્રૅક કર્યા વિના, તમને સંબંધિત જાહેરાતો બતાવવા માટે ઍપને મંજૂરી આપે છે."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"બ્લૉક કરેલી કોઈ ઍપ તમે ધરાવતા નથી"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"બ્લૉક કરેલી કોઈ રુચિ તમે ધરાવતા નથી"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"રદ કરો"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"પ્રાઇવસી સૅન્ડબૉક્સ બંધ કરીએ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"જો તમે તમારો વિચાર બદલો અથવા તમે Androidના જાહેરાતો સંબંધિત પ્રાઇવસીના બીટા વર્ઝન વિશે વધુ જાણવા માગતા હો, તો તમારા પ્રાઇવસી સેટિંગમાં જાઓ"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"બંધ કરો"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g>ને બ્લૉક કરીએ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"આ રુચિને બ્લૉક કરવામાં આવશે અને તમે જ્યાં સુધી તેને ફરી ઉમેરશો નહીં, ત્યાં સુધી તે ફરીથી તમારી સૂચીમાં ઉમેરવામાં આવશે નહીં. તમને હજી પણ અમુક સંબંધિત જાહેરાતો જોવા મળી શકે છે."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"બ્લૉક કરો"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> અનબ્લૉક કરેલી છે"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android આ રુચિને તમારી સૂચિમાં ફરી ઉમેરી દઈ શકે છે, પરંતુ તે તરત જ દેખાય નહીં એમ બને"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ઓકે"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"તમારી તમામ રુચિઓ રીસેટ કરીએ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"તમારી સૂચિ સાફ કરવામાં આવશે અને આગળ જતાં નવી રુચિઓનો અંદાજ લગાવવામાં આવશે. તમને હજી પણ સાફ કરેલી રુચિઓ સંબંધિત કેટલીક જાહેરાતો જોવા મળી શકે છે."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"રીસેટ કરો"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g>ને બ્લૉક કરીએ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"આ ઍપ પ્રાઇવસી સૅન્ડબૉક્સ માટે તમારી રુચિઓનું અનુમાન લગાવશે નહીં અને તમે જ્યાં સુધી તેને અનબ્લૉક કરશો નહીં, ત્યાં સુધી તેને ફરીથી તમારી સૂચીમાં ઉમેરવામાં આવશે નહીં.\n\nઆ ઍપ દ્વારા અગાઉ અનુમાન લગાવવામાં આવેલી હોય તેવી રુચિઓ ડિલીટ કરવામાં આવશે, છતાં પણ તમને એ રુચિઓ સંબંધિત અમુક જાહેરાતો દેખાઈ શકે છે."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> અનબ્લૉક કરેલી છે"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"આ ઍપ તમારા માટે ફરીથી રુચિઓનું અનુમાન લગાવી શકે છે, પરંતુ તે તરત જ તમારી સૂચિમાં દેખાય નહીં એમ બને. તમને તેના સંબંધિત જાહેરાતો દેખાવામાં થોડી વાર લાગી શકે છે."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"ઍપ દ્વારા જનરેટ થયેલી રુચિઓ રીસેટ કરીએ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"પ્રાઇવસી સૅન્ડબૉક્સમાંથી તમારી સૂચિ પરની ઍપ દ્વારા અંદાજિત રુચિઓ ડિલીટ કરવામાં આવશે અને આગળ જતાં ઍપ નવી રુચિઓનો અંદાજ લગાવશે. તમને હજી પણ કેટલીક સંબંધિત જાહેરાતો જોવા મળી શકે છે."</string>
<string name="topic10001" msgid="1636806320891333775">"આર્ટ અને મનોરંજન"</string>
<string name="topic10002" msgid="1226367977754287428">"અભિનય અને થિએટર"</string>
<string name="topic10003" msgid="6949890838957814881">"ઍનિમે અને માંગા"</string>
diff --git a/adservices/apk/res/values-hi/strings.xml b/adservices/apk/res/values-hi/strings.xml
index 07ec39db9..d8781c8a7 100644
--- a/adservices/apk/res/values-hi/strings.xml
+++ b/adservices/apk/res/values-hi/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"प्रोग्राम में शामिल होने का तरीका"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"बीटा प्रोग्राम को चालू करने का मतलब है कि आपके डिवाइस में मौजूद ऐप्लिकेशन, विज्ञापन दिखाने के लिए ज़्यादा निजता वाले इन नए तरीकों को आज़मा सकते हैं. निजता सेटिंग में जाकर, बीटा प्रोग्राम को कभी भी बंद किया जा सकता है."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"रहने दें"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"हां, मुझे शामिल होना है"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"चालू करें"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"ज़्यादा देखें"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"इसमें हिस्सा लेने के लिए धन्यवाद"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"अब आप Android प्राइवसी सैंडबॉक्स के बीटा प्रोग्राम का हिस्सा हैं. आपके डिवाइस के लिए, प्राइवसी सैंडबॉक्स का बीटा प्रोग्राम चालू है.\n\nबीटा प्रोग्राम को कभी भी बंद करने या इसके बारे में ज़्यादा जानने के लिए निजता सेटिंग में जाएं."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"आपने इस प्रोग्राम में शामिल न होने का विकल्प चुना है"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"इस समय दिखाने के लिए कोई भी दिलचस्पी नहीं है."</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"प्राइवसी सैंडबॉक्स"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android के विज्ञापनों से जुड़ी निजता नीति के बीटा प्रोग्राम से आपको नई सुविधाएं मिलती हैं. आपके डिवाइस में मौजूद ऐप्लिकेशन, इन सुविधाओं का इस्तेमाल करके आपकी पसंद के मुताबिक विज्ञापन दिखा सकते हैं. ये टेक्नोलॉजी, डिवाइस के आइडेंटिफ़ायर का इस्तेमाल नहीं करती हैं.\n\nAndroid, इस बात का अनुमान लगा सकता है कि आपको किस तरह के विज्ञापन पसंद आएंगे. साथ ही, यह आपके डिवाइस में कुछ समय के लिए आपकी दिलचस्पियों को सेव कर सकता है. इससे ऐप्लिकेशन को, आपके काम के विज्ञापन दिखाने की अनुमति मिलती है. ऐसा, दूसरे डेवलपर के ऐप्लिकेशन और वेबसाइटों पर आपकी गतिविधियों को ट्रैक किए बिना हो सकता है."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"आपकी कोई भी दिलचस्पी ब्लॉक नहीं की गई"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"ऐप्लिकेशन, आपकी दिलचस्पियों का अनुमान लगा सकते हैं. साथ ही, ये Android में कुछ समय के लिए आपकी दिलचस्पियों को सेव कर सकते हैं. इसके बाद, कोई दूसरा ऐप्लिकेशन, आपकी दिलचस्पी के आधार पर विज्ञापन दिखा सकता है.\n\nअगर कोई ऐप्लिकेशन ब्लॉक किया जाता है, तो वह आपकी दिलचस्पी का अनुमान नहीं लगा पाएगा. दिलचस्पी का अनुमान लगाने वाले ऐप्लिकेशन की सूची में किसी ऐप्लिकेशन को वापस तब तक नहीं जोड़ा जाएगा, जब तक आप उसे अनब्लॉक न कर दें. ऐप्लिकेशन ने आपकी जिन दिलचस्पियों का अनुमान लगया था उन्हें मिटा दिया जाएगा. हालांकि, आपको अब भी इनसे जुड़े कुछ विज्ञापन दिख सकते हैं."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"ऐसे ऐप्लिकेशन जिन्हें आपने ब्लॉक किया है"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ऐप्लिकेशन की अनुमान लगाई गई दिलचस्पियां रीसेट करें"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"प्राइवसी सैंडबॉक्स"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android के विज्ञापनों से जुड़ी निजता नीति के बीटा प्रोग्राम से आपको नई सुविधाएं मिलती हैं. आपके डिवाइस में मौजूद ऐप्लिकेशन, इन सुविधाओं का इस्तेमाल करके आपकी पसंद के मुताबिक विज्ञापन दिखा सकते हैं. ये टेक्नोलॉजी, डिवाइस के आइडेंटिफ़ायर का इस्तेमाल नहीं करती हैं.\n\nऐप्लिकेशन, इस बात का अनुमान लगा सकते हैं कि आपको किस तरह के विज्ञापन पसंद आएंगे. साथ ही, ये आपके डिवाइस में कुछ समय के लिए आपकी दिलचस्पियों को सेव कर सकते हैं. इससे ऐप्लिकेशन को, आपके काम के विज्ञापन दिखाने की अनुमति मिलती है. ऐसा, दूसरे डेवलपर के ऐप्लिकेशन और वेबसाइटों पर आपकी गतिविधियों को ट्रैक किए बिना हो सकता है."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"आपका कोई भी ऐप्लिकेशन ब्लॉक नहीं किया गया है"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"आपकी कोई भी दिलचस्पी ब्लॉक नहीं की गई"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"रद्द करें"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"क्या आपको प्राइवसी सैंडबॉक्स को बंद करना है?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"अगर आपको इसमें बदलाव करना है या Android पर विज्ञापनों से जुड़ी निजता नीति के बीटा वर्शन के बारे में ज़्यादा जानना है, तो निजता सेटिंग पर जाएं"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"बंद करें"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"क्या आपको <xliff:g id="TOPIC">%1$s</xliff:g> को ब्लॉक करना है?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"आपकी दिलचस्पी के इस विषय को ब्लॉक कर दिया जाएगा. साथ ही, इसे तब तक आपकी सूची में वापस नहीं जोड़ा जाएगा, जब तक इसे फिर से नहीं जोड़ा जाता. आपको अब भी अपनी दिलचस्पी से मिलते-जुलते विज्ञापन दिख सकते हैं."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ब्लॉक करें"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> को अनब्लॉक कर दिया गया है"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android, आपकी दिलचस्पी से जुड़े इस विषय को सूची में फिर से जोड़ सकता है. हालांकि, ऐसा हो सकता है कि यह सूची में तुरंत न दिखे"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ठीक है"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"क्या आपको अपनी दिलचस्पी के सभी विषय रीसेट करने हैं?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"आपकी दिलचस्पी से जुड़े विषय की सूची को मिटा दिया जाएगा. साथ ही, आने वाले समय के लिए, दिलचस्पी से जुड़े नए विषयों का अनुमान लगाया जाएगा. मिटाए जाने के बाद भी, आपको अपनी दिलचस्पी से मिलते-जुलते विज्ञापन दिख सकते हैं."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"रीसेट करें"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"क्या आपको <xliff:g id="APP">%1$s</xliff:g> को ब्लॉक करना है?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"यह ऐप्लिकेशन प्राइवसी सैंडबॉक्स के लिए, आपकी दिलचस्पियों का अनुमान नहीं लगाएगा और जब तक इसे अनब्लॉक नहीं किया जाता, तब तक इसे आपकी सूची में वापस नहीं जोड़ा जाएगा.\n\nऐप्लिकेशन ने आपकी जिन दिलचस्पियों का अनुमान लगाया था उन्हें मिटा दिया जाएगा. हालांकि, आपको अब भी इनसे जुड़े कुछ विज्ञापन दिख सकते हैं."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> को अनब्लॉक कर दिया गया है"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"यह ऐप्लिकेशन फिर से आपकी दिलचस्पियों का अनुमान लगा सकता है. हालांकि, ऐसा हो सकता है कि ये आपकी सूची में तुरंत न दिखें. इससे जुड़े विज्ञापन दिखने में कुछ समय लग सकता है."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"क्या आपको ऐप्लिकेशन की अनुमान लगाई गई दिलचस्पियां रीसेट करनी हैं?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"आपकी दिलचस्पी से जुड़े उन विषयों की सूची को प्राइवसी सैंडबॉक्स से हटा दिया जाएगा जिनका अनुमान लगाया गया था. साथ ही, ऐप्लिकेशन आने वाले समय के लिए, आपकी दिलचस्पी से जुड़े नए विषयों का अनुमान लगाएगा. आपको अब भी अपनी दिलचस्पी से मिलते-जुलते विज्ञापन दिख सकते हैं."</string>
<string name="topic10001" msgid="1636806320891333775">"कला और मनोरंजन"</string>
<string name="topic10002" msgid="1226367977754287428">"अभिनय और थिएटर"</string>
<string name="topic10003" msgid="6949890838957814881">"ऐनमे और मंगा"</string>
diff --git a/adservices/apk/res/values-hr/strings.xml b/adservices/apk/res/values-hr/strings.xml
index 817d54c0b..40b9d4c28 100644
--- a/adservices/apk/res/values-hr/strings.xml
+++ b/adservices/apk/res/values-hr/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Način sudjelovanja"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Uključivanje beta verzije aplikacijama omogućuje ispitivanje novih, privatnijih načina za prikazivanje oglasa. Beta verziju uvijek možete isključiti u postavkama privatnosti."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ne, hvala"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"U redu"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Uključi"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Više"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Hvala na sudjelovanju."</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Dio ste Androidove beta verzije privatnosti za oglase. Uključen je Privacy Sandbox za vaš uređaj.\n\nMožete saznati više ili isključiti beta verziju u postavkama privatnosti."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Odlučili ste da nećete sudjelovati"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Trenutačno nema nijednog interesa za prikaz"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androidova beta verzija privatnosti za oglase pruža nove značajke koje aplikacije mogu upotrijebiti za prikazivanje oglasa koji bi vas mogli zanimati. Ta tehnologija ne upotrebljava identifikatore uređaja.\n\nAndroid može procijeniti vrstu oglasa koji bi vas mogli zanimati i privremeno spremiti te interese na vaš uređaj. To aplikacijama omogućuje prikazivanje relevantnih oglasa, bez praćenja aktivnosti na web-lokacijama i u aplikacijama drugih razvojnih programera."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemate nijedan blokirani interes"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikacije mogu procijeniti vaše interese i privremeno ih spremiti s Androidom. Kasnije vam neka druga aplikacija može prikazati oglas na temelju tih interesa.\n\nAko blokirate aplikaciju, neće više procjenjivati interese. Neće se dodati na popis aplikacija dok je ne deblokirate. Interesi koje je aplikacija već procijenila će se izbrisati, no i dalje vam se mogu prikazivati neki povezani oglasi."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplikacije koje ste blokirali"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Poništi interese koje procjenjuju aplikacije"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androidova beta verzija privatnosti za oglase pruža nove značajke koje aplikacije mogu upotrijebiti za prikazivanje oglasa koji bi vas mogli zanimati. Ta tehnologija ne upotrebljava identifikatore uređaja.\n\nAplikacije mogu procijeniti vrstu oglasa koji bi vas mogli zanimati i privremeno spremiti te interese na vaš uređaj. To aplikacijama omogućuje prikazivanje relevantnih oglasa, bez praćenja aktivnosti na web-lokacijama i u aplikacijama drugih razvojnih programera."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nemate nijednu blokiranu aplikaciju"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemate nijedan blokirani interes"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Odustani"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Želite li isključiti Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ako se predomislite ili želite saznati više Androidovoj beta verziji privatnosti oglasa, idite na postavke privatnosti"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Isključi"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Želite li blokirati temu <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Taj će se interes blokirati i više se neće dodavati na vaš popis, osim ako ga dodate sami. I dalje vam se mogu prikazivati neki povezani oglasi."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokiraj"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Tema <xliff:g id="TOPIC">%1$s</xliff:g> je deblokirana"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android može ponovo dodati taj interes na vaš popis, no on se možda neće odmah pojaviti"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"U redu"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Želite li poništiti sve svoje interese?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Popis će se izbrisati i procjenjivat će se novi interesi. I dalje vam se mogu prikazivati neki oglasi povezani s izbrisanim interesima."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Poništi"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Želite li blokirati aplikaciju <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ta aplikacija neće procjenjivati interese za Privacy Sandbox i neće se više dodavati na vaš popis, osim ako je deblokirate.\n\nInteresi koje je aplikacija već procijenila izbrisat će se, no i dalje biste mogli vidjeti neke povezane oglase."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> je deblokirana"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Ta aplikacija može ponovo procijeniti vaše interese, no možda se neće odmah pojaviti na vašem popisu. Možda će biti potrebno neko vrijeme da se počnu prikazivati povezani oglasi."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Želite li poništiti interese koje su generirale aplikacije?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interesi koje procjenjuju aplikacije na vašem popisu izbrisat će se iz Privacy Sandboxa i aplikacije će od tog trenutka nadalje procjenjivati nove interese. I dalje vam se mogu prikazivati neki povezani oglasi."</string>
<string name="topic10001" msgid="1636806320891333775">"Umjetnost i zabava"</string>
<string name="topic10002" msgid="1226367977754287428">"Gluma i kazalište"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime i manga"</string>
diff --git a/adservices/apk/res/values-hu/strings.xml b/adservices/apk/res/values-hu/strings.xml
index 3d4f1f934..f8b19c9eb 100644
--- a/adservices/apk/res/values-hu/strings.xml
+++ b/adservices/apk/res/values-hu/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Részvételi szabályok"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"A bétaverzió bekapcsolásával az alkalmazások tesztelhetik a hirdetések megjelenítésének ezen új, privátabb módjait. A bétaverziót bármikor kikapcsolhatja az adatvédelmi beállítások között."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Most nem"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Igen, csatlakozom"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Bekapcsolás"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Továbbiak"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Köszönjük részvételét"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Ön részt vesz az Android hirdetésekhez kapcsolódó adatvédelmi bétaprogramjában. A Privacy Sandbox be van kapcsolva eszközén.\n\nTovábbi információt az adatvédelmi beállítások között talál, és itt kapcsolhatja ki a bétaverziót is."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Úgy döntött, hogy nem vesz részt a programban"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Jelenleg egyetlen érdeklődési kör sem jeleníthető meg"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Az Android hirdetési adatvédelmének bétaverziója olyan új funkciókat biztosít, melyek segítségével az alkalmazások Önnek esetlegesen tetsző hirdetéseket jeleníthetnek meg. Ezek a technológiák nem használnak eszközazonosítókat.\n\nAz Android becslést tehet az Önt esetlegesen érdeklő hirdetéstípusokra, az érdeklődési köröket pedig ideiglenesen az eszközön menti. Az alkalmazások ennek segítségével releváns hirdetéseket jeleníthetnek meg Önnek anélkül, hogy nyomon követnék a más fejlesztőktől származó webhelyeken és alkalmazásokban végzett tevékenységeit."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Önnek nincsenek letiltott érdeklődési körei"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Az alkalmazások becslést tehetnek az Ön érdeklődési köreire vonatkozóan, és ideiglenesen menthetik e becsléseket az Android rendszerre. Később egy másik alkalmazás hirdetéseket jeleníthet meg Önnek ezen érdeklődési körök alapján.\n\nHa letiltja valamelyik alkalmazást, akkor az a továbbiakban nem tesz becslést az érdeklődési körökre vonatkozóan. Nem kerül fel újból az alkalmazások ezen listájára, kivéve, ha Ön feloldja a letiltását. Az alkalmazás becslése szerinti érdeklődési körök törlődnek majd, de előfordulhat, hogy továbbra is megjelenik néhány kapcsolódó hirdetés."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Letiltott alkalmazások"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Az alkalmazások becslése szerinti érdeklődési körök visszaállítása"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Az Android hirdetési adatvédelmének bétaverziója olyan új funkciókat biztosít, melyek segítségével az alkalmazások Önnek esetlegesen tetsző hirdetéseket jeleníthetnek meg. Ezek a technológiák nem használnak eszközazonosítókat.\n\nAz alkalmazások becslést tehetnek az Önt esetlegesen érdeklő hirdetéstípusokra vonatkozóan, az érdeklődési köröket pedig ideiglenesen az eszközre mentik. Az alkalmazások ennek segítségével releváns hirdetéseket jeleníthetnek meg Önnek anélkül, hogy nyomon követnék a más fejlesztőktől származó webhelyeken és alkalmazásokban végzett tevékenységeit."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nincsenek letiltott alkalmazásai"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Önnek nincsenek letiltott érdeklődési körei"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Mégse"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Kikapcsolja a Privacy Sandbox szolgáltatást?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ha meggondolja magát, vagy részletesebben meg szeretné ismerni az Android hirdetésekhez kapcsolódó adatvédelmének bétaverzióját, lépjen az adatvédelmi beállításokhoz"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Kikapcsolás"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Letiltja ezt: <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Az érdeklődési kört letiltja a rendszer, és nem veszi fel újból a rendszer a listára, amíg Ön újra fel nem veszi. Továbbra is láthat néhány kapcsolódó hirdetést."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Letiltás"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> letiltása feloldva"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Az Android újból felveheti ezt az érdeklődési kört a listára, de előfordulhat, hogy nem jelenik meg azonnal"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Alaphelyzetbe állítja az összes érdeklődési kört?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Listája törölve lesz, és mostantól új érdeklődési körök lesznek kikövetkeztetve. Továbbra is láthat néhány olyan hirdetést, amely kapcsolódik a törölt érdeklődési körökhöz."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Alaphelyzet"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Letiltja ezt: <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Amíg fel nem oldja, az alkalmazás nem tesz becslést az érdeklődési körökre a Privacy Sandboxnál, és az érdeklődési köröket nem veszi fel újból a rendszer a listára.\n\nAz alkalmazás által korábban becsült érdeklődési körök törlődnek, de továbbra is láthat néhány kapcsolódó hirdetést."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> letiltása feloldva"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Az alkalmazás újból megbecsülheti az érdeklődési köröket, de előfordulhat, hogy nem jelennek meg azonnal a listán. Beletelhet némi időbe, amíg az összes kapcsolódó hirdetés megjelenik."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Alaphelyzetbe állítja az alkalmazások által generált érdeklődési köröket?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"A listáján szereplő alkalmazások szerint kikövetkeztetett érdeklődési körök törölve lesznek a Privacy Sandboxból, és mostantól az alkalmazások új érdeklődési körökeket fognak kikövetkeztetni. Továbbra is láthat néhány kapcsolódó hirdetést."</string>
<string name="topic10001" msgid="1636806320891333775">"Művészetek és szórakoztatás"</string>
<string name="topic10002" msgid="1226367977754287428">"Színészet és színház"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime és manga"</string>
diff --git a/adservices/apk/res/values-hy/strings.xml b/adservices/apk/res/values-hy/strings.xml
index 67191e159..c1e401f49 100644
--- a/adservices/apk/res/values-hy/strings.xml
+++ b/adservices/apk/res/values-hy/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Ինչպես մասնակցել"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Բետա փորձարկումը միացնելով՝ դուք կարող եք փորձարկել գովազդի ցուցադրման այս նոր, ավելի պաշտպանված եղանակները։ Դուք ցանկացած ժամանակ կարող եք անջատել բետա փորձարկումը գաղտնիության կարգավորումներում։"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ոչ, շնորհակալություն"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Այո, միանալ"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Միացնել"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Ավելին"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Շնորհակալություն մասնակցության համար"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Դուք մասնակցում եք Android-ում գովազդի գաղտնիության բետա փորձարկմանը։ Privacy Sandbox տեխնոլոգիաները միացված են ձեր սարքում։\n\nԲետա փորձարկման մասին ավելին իմանալու կամ դրա մասնակցությունից հրաժարվելու համար անցեք գաղտնիության կարգավորումներ։"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Դուք հրաժարվել եք ծրագրին մասնակցելուց"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Այս պահին հետաքրքրություններ չկան"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android-ում գովազդի գաղտնիության բետա փորձարկման ծրագիրը տրամադրում է նոր գործառույթներ, որոնց միջոցով հավելվածները կարող են ցույց տալ ձեզ գովազդներ, որոնք կարող են ձեզ դուր գալ։ Այս տեխնոլոգիաները չեն օգտագործում սարքերի նույնացուցիչները։\n\nAndroid-ը կարող է որոշել գովազդների տեսակները, որոնք կարող են հետաքրքրել ձեզ, և ժամանակավորապես պահել հետաքրքրությունները ձեր սարքում։ Սա թույլ է տալիս հավելվածներին ցուցադրել ձեզ համապատասխան գովազդ՝ առանց հետագծելու ձեր գործողությունները այլ մշակողների կայքերում ու հավելվածներում։"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Արգելափակված հետաքրքրություններ չկան"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Հավելվածները կարող են որոշել ձեր հետաքրքրությունները և ժամանակավորապես պահել դրանք Android-ում։ Հետագայում մեկ այլ հավելված կարող է ձեզ գովազդ ցուցադրել այս հետաքրքրությունների հիման վրա։\n\nԵթե դուք արգելափակեք որևէ հավելված, այն այլևս չի որոշի ձեր հետաքրքրությունները։ Քանի դեռ հավելվածն արգելափակված է, այն չի հայտնվի հավելվածների այս ցանկում։ Հավելվածի կողմից արդեն որոշված հետաքրքրությունները կջնջվեն, սակայն հնարավոր է, որ դուք դեռ տեսնեք այդ հետաքրքրության հետ կապված որոշ գովազդներ։"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Հավելվածներ, որոնք արգելափակել եք"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Զրոյացնել հավելվածների կողմից որոշված հետաքրքրությունները"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android-ում գովազդի գաղտնիության բետա փորձարկման ծրագիրը տրամադրում է նոր գործառույթներ, որոնց միջոցով հավելվածները կարող են ցույց տալ ձեզ գովազդներ, որոնք կարող են ձեզ դուր գալ։ Այս տեխնոլոգիաները չեն օգտագործում սարքերի նույնացուցիչները։\n\nՀավելվածները կարող են որոշել գովազդների տեսակները, որոնք կարող են հետաքրքրել ձեզ, և ժամանակավորապես պահել հետաքրքրությունները ձեր սարքում։ Սա թույլ է տալիս հավելվածներին ցուցադրել ձեզ համապատասխան գովազդ՝ առանց հետագծելու ձեր գործողությունները այլ մշակողների կայքերում ու հավելվածներում։"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Արգելափակված հավելվածներ չկան"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Արգելափակված հետաքրքրություններ չկան"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Չեղարկել"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Անջատե՞լ Privacy Sandbox-ը"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Եթե մտափոխվեք կամ ուզեք ավելին իմանալ Android-ում գովազդի գաղտնիության բետա փորձարկման մասին, անցեք գաղտնիության կարգավորումներ"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Անջատել"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Արգելափակե՞լ «<xliff:g id="TOPIC">%1$s</xliff:g>» թեման"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Այս հետաքրքրությունը կարգելափակվի և այլևս չի ավելացվի ձեր ցուցակում, եթե ինքներդ այն նորից չավելացնեք։ Հնարավոր է՝ դուք նախկինի պես տեսնեք այդ հետաքրքրության հետ կապված որոշ գովազդներ։"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Արգելափակել"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"«<xliff:g id="TOPIC">%1$s</xliff:g>» թեման արգելաբացված է"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android-ը կարող է կրկին ավելացնել այս հետաքրքրությունը ձեր ցուցակում, բայց այն կարող է անմիջապես չհայտնվել"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Եղավ"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Զրոյացնե՞լ ձեր բոլոր հետաքրքրությունները"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Ձեր ցուցակը կմաքրվի, և ժամանակի ընթացքում ցուցակում կավելացվեն նոր որոշված հետաքրքրություններ։ Հնարավոր է՝ դուք նախկինի պես տեսնեք ջնջված հետաքրքրություններին առնչվող որոշ գովազդներ։"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Զրոյացնել"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Արգելափակե՞լ <xliff:g id="APP">%1$s</xliff:g> հավելվածը"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Այս հավելվածը չի որոշի հետաքրքրությունները Privacy Sandbox-ի համար և այլևս չի ավելացվի ձեր ցուցակում, քանի դեռ չեք արգելահանել այն։\n\nԱյս հավելվածի կողմից արդեն որոշված հետաքրքրությունները կջնջվեն, սակայն հնարավոր է՝ դուք նախկինի պես տեսնեք դրանց հետ կապված որոշ գովազդներ։"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> հավելվածն արգելաբացված է"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Այս հավելվածը կարող է նորից որոշել ձեր հետաքրքրությունները, սակայն դրանք կարող են անմիջապես չհայտնվել ձեր ցուցակում։ Կարող է որոշ ժամանակ պահանջվել, որպեսզի տեսնեք այդ հետաքրքրությունների հետ կապված գովազդներ։"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Զրոյացնե՞լ հավելվածների կողմից ստեղծված հետաքրքրությունները"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Հավելվածների կողմից որոշված հետաքրքրությունները կջնջվեն Privacy Sandbox-ից, և հավելվածները կսկսեն նորից որոշել դրանք։ Հնարավոր է՝ դուք նախկինի պես տեսնեք հին հետաքրքրությունների հետ կապված որոշ գովազդներ։"</string>
<string name="topic10001" msgid="1636806320891333775">"Արվեստ և զվարճանք"</string>
<string name="topic10002" msgid="1226367977754287428">"Թատրոն և դրամատուրգիա"</string>
<string name="topic10003" msgid="6949890838957814881">"Անիմե և մանգա"</string>
@@ -239,7 +257,7 @@
<string name="topic10148" msgid="8861628145647459323">"Համակարգչային անվտանգություն"</string>
<string name="topic10149" msgid="1719988773455991739">"Հակավիրուսային ծրագրեր և վնասագրեր"</string>
<string name="topic10150" msgid="1482205464872465298">"Ցանցային անվտանգություն"</string>
- <string name="topic10151" msgid="1772262905623388968">"Կենցաղային տեխնիկա"</string>
+ <string name="topic10151" msgid="1772262905623388968">"Կենցաղային էլեկտրոնիկա"</string>
<string name="topic10152" msgid="651152740021127086">"Ֆոտոխցիկներ և տեսախցիկներ"</string>
<string name="topic10153" msgid="8741504768717646372">"GPS և նավիգացիա"</string>
<string name="topic10154" msgid="5382029798409496090">"Տան ավտոմատացում"</string>
diff --git a/adservices/apk/res/values-in/strings.xml b/adservices/apk/res/values-in/strings.xml
index b5874337d..9a52fd073 100644
--- a/adservices/apk/res/values-in/strings.xml
+++ b/adservices/apk/res/values-in/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Cara berpartisipasi"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Mengaktifkan program beta memungkinkan aplikasi menguji cara baru yang lebih pribadi ini untuk menampilkan iklan kepada Anda. Anda dapat menonaktifkan program beta kapan saja di setelan privasi."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Lain kali"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ya, saya akan bergabung"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Aktifkan"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Lainnya"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Terima kasih sudah berpartisipasi"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Anda berpartisipasi dalam privasi iklan Android beta. Privacy Sandbox diaktifkan untuk perangkat Anda.\n\nAnda dapat mempelajari lebih lanjut atau menonaktifkan program beta kapan saja di setelan privasi."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Anda telah memilih untuk tidak berpartisipasi"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Tidak ada minat untuk ditampilkan sekarang"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Privasi iklan Android beta memberikan fitur baru yang dapat digunakan aplikasi untuk menampilkan iklan yang mungkin Anda sukai. Teknologi ini tidak menggunakan ID perangkat.\n\nAndroid dapat memperkirakan jenis iklan yang mungkin menarik bagi Anda, dan menyimpan minat ini untuk sementara di perangkat Anda. Hal ini memungkinkan aplikasi menampilkan iklan yang relevan, tanpa melacak aktivitas Anda di seluruh situs dan aplikasi dari developer lainnya."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Anda tidak memiliki minat yang diblokir"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikasi dapat memperkirakan minat dan menyimpan minat ini untuk sementara dengan Android. Selanjutnya, aplikasi lain dapat menampilkan iklan kepada Anda berdasarkan minat ini.\n\nJika Anda memblokir suatu aplikasi, aplikasi tersebut tidak akan memperkirakan minat lagi. Aplikasi tidak akan ditambahkan ke daftar aplikasi ini lagi kecuali jika Anda berhenti memblokirnya. Minat yang telah diperkirakan oleh aplikasi akan dihapus, tetapi Anda mungkin masih melihat beberapa iklan terkait."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplikasi yang Anda blokir"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Reset minat yang diperkirakan oleh aplikasi"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Privasi iklan Android beta memberikan fitur baru yang dapat digunakan aplikasi untuk menampilkan iklan yang mungkin Anda sukai. Teknologi ini tidak menggunakan ID perangkat.\n\nAplikasi dapat memperkirakan jenis iklan yang mungkin menarik bagi Anda, dan menyimpan minat ini untuk sementara di perangkat Anda. Hal ini memungkinkan aplikasi menampilkan iklan yang relevan, tanpa melacak aktivitas Anda di seluruh situs dan aplikasi dari developer lainnya."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Anda tidak memiliki aplikasi yang diblokir"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Anda tidak memiliki minat yang diblokir"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Batal"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Nonaktifkan Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Jika Anda berubah pikiran atau ingin mempelajari privasi iklan Android beta lebih lanjut, buka setelan privasi"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Nonaktifkan"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Blokir <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Minat ini akan diblokir dan tidak akan ditambahkan ke daftar Anda lagi, kecuali jika Anda menambahkannya kembali. Anda mungkin masih melihat beberapa iklan terkait minat tersebut."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokir"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> berhenti diblokir"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android dapat menambahkan minat ini lagi ke daftar Anda, tetapi minat mungkin tidak langsung muncul"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Oke"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Reset semua minat?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Daftar Anda akan dihapus dan minat baru akan diperkirakan ke depannya. Anda mungkin masih melihat beberapa iklan terkait minat yang dihapus."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Reset"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Blokir <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Aplikasi ini tidak akan memperkirakan minat untuk Privacy Sandbox dan tidak akan ditambahkan ke daftar Anda lagi, kecuali jika Anda berhenti memblokirnya.\n\nMinat yang sudah diperkirakan oleh aplikasi ini akan dihapus, tetapi Anda mungkin masih melihat beberapa iklan terkait minat tersebut."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> berhenti diblokir"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Aplikasi ini dapat memperkirakan minat lagi untuk Anda, tetapi minat mungkin tidak langsung muncul di daftar. Mungkin perlu waktu agak lama untuk melihat iklan terkait minat tersebut."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Reset minat yang dibuat oleh aplikasi?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Minat yang diperkirakan oleh aplikasi dalam daftar Anda akan dihapus dari Privacy Sandbox, dan aplikasi akan memperkirakan minat baru ke depannya. Anda mungkin masih melihat beberapa iklan terkait minat tersebut."</string>
<string name="topic10001" msgid="1636806320891333775">"Seni &amp; Hiburan"</string>
<string name="topic10002" msgid="1226367977754287428">"Akting &amp; Teater"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime &amp; Manga"</string>
@@ -468,7 +486,7 @@
<string name="topic10377" msgid="7834619927563362042">"Robotika"</string>
<string name="topic10378" msgid="242496862821758873">"Belanja"</string>
<string name="topic10379" msgid="5505228335012447165">"Barang Antik &amp; Barang Koleksi"</string>
- <string name="topic10380" msgid="4989560205486143927">"Pakaian"</string>
+ <string name="topic10380" msgid="4989560205486143927">"Busana"</string>
<string name="topic10381" msgid="7585496746485093505">"Pakaian Anak"</string>
<string name="topic10382" msgid="5088060295126473451">"Kostum"</string>
<string name="topic10383" msgid="4947104586590538143">"Pakaian Pria"</string>
diff --git a/adservices/apk/res/values-is/strings.xml b/adservices/apk/res/values-is/strings.xml
index d46014d37..fb61e315e 100644
--- a/adservices/apk/res/values-is/strings.xml
+++ b/adservices/apk/res/values-is/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Svona tekurðu þátt"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ef kveikt er á betaútgáfunni geta forrit prófað þessar nýju, lokaðri leiðir til að birta þér auglýsingar. Þú getur slökkt á betaútgáfunni hvenær sem er í persónuverndarstillingunum."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nei, takk"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Já, ég vil taka þátt"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Kveikja"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Meira"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Takk fyrir að taka þátt"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Þú tekur þátt í betaútgáfu persónuverndar Android-auglýsinga. Kveikt er á Privacy Sandbox fyrir tækið þitt.\n\nÞú getur kynnt þér betaútgáfuna nánar eða slökkt á henni hvenær sem er í persónuverndarstillingunum þínum."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Þú valdir að taka ekki þátt"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Engin áhugasvið til að sýna sem stendur"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Betaútgáfa persónuverndar Android-auglýsinga býður upp á nýja eiginleika sem forrit geta notað til að birta þér auglýsingar sem gætu höfðað til þín. Þessi tækni notar ekki auðkenni tækja.\n\nAndroid getur áætlað hvernig auglýsingar gætu höfðað til þín og vistað áhugasviðin tímabundið í tækinu þínu. Þetta gerir forritum kleift að birta þér viðeigandi auglýsingar án þess að rekja virkni þína á vefsvæðum eða í forritum annarra þróunaraðila."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Þú hefur ekki sett nein áhugasvið á bannlista"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Forrit geta áætlað áhugasviðin þín og vistað þau tímabundið með Android. Síðar gæti annað forrit birt þér auglýsingu í samræmi við þessi áhugasvið.\n\nEf þú lokar á forrit getur það ekki áætlað áhugasviðin þín lengur. Því verður ekki bætt við forritalistann aftur nema þú takir það af bannlista. Áhugasviðunum sem forrit hefur nú þegar áætlað verður eytt en þú gætir áfram fengið einhverjar tengdar auglýsingar."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Forrit sem þú hefur lokað á"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Endurstilla áhugasvið sem forrit hafa áætlað"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Betaútgáfa persónuverndar Android-auglýsinga býður upp á nýja eiginleika sem forrit geta notað til að birta þér auglýsingar sem gætu höfðað til þín. Þessi tækni notar ekki auðkenni tækja.\n\nForrit geta áætlað hvernig auglýsingar gætu höfðað til þín og vistað áhugasviðin tímabundið í tækinu þínu. Þetta gerir forritum kleift að birta þér viðeigandi auglýsingar án þess að rekja virkni þína á vefsvæðum eða í forritum annarra þróunaraðila."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Þú hefur ekki sett nein forrit á bannlista"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Þú hefur ekki sett nein áhugasvið á bannlista"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Hætta við"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Slökkva á Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ef þú skiptir um skoðun eða vilt kynna þér betaútgáfu persónuverndar Android-auglýsinga nánar skaltu fara í persónuverndarstillingarnar þínar"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Slökkva"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Viltu setja <xliff:g id="TOPIC">%1$s</xliff:g> á bannlista?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Þetta áhugasvið verður sett á bannlista og verður ekki bætt við listann þinn aftur nema þú bætir því aftur við. Þú gætir áfram séð tengdar auglýsingar."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Setja á bannlista"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> er ekki lengur á bannlista"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android gæti bætt þessu áhugasviði aftur við listann en það birtist hugsanlega ekki strax"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Í lagi"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Endurstilla öll áhugasvið?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Listinn verður hreinsaður og ný áhugasvið verða áætluð í framhaldinu. Þú gætir áfram séð einhverjar auglýsingar sem tengjast áhugasviðunum sem voru hreinsuð."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Endurstilla"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Viltu setja <xliff:g id="APP">%1$s</xliff:g> á bannlista?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Þetta forrit mun ekki áætla áhugasviðin þín fyrir Privacy Sandbox og verður ekki bætt við listann þinn aftur nema þú takir það af bannlista.\n\nÁhugasviðum sem þetta forrit hefur áætlað verður eytt en þú gætir áfram séð tengdar auglýsingar."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> er ekki lengur á bannlista"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Þetta forrit getur áætlað áhugasviðin þín aftur en þau birtast hugsanlega ekki á listanum strax. Nokkur tími getur liðið þar til þú sérð tengdar auglýsingar."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Endurstilla áhugasvið sem forrit hafa áætlað?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Áhugasviðunum sem forritin á listanum áætla verður eytt úr Privacy Sandbox og forritin munu áætla ný áhugasvið í framhaldinu. Þú gætir áfram séð tengdar auglýsingar."</string>
<string name="topic10001" msgid="1636806320891333775">"Listir og afþreying"</string>
<string name="topic10002" msgid="1226367977754287428">"Leiklist og leikhús"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime og manga"</string>
diff --git a/adservices/apk/res/values-it/strings.xml b/adservices/apk/res/values-it/strings.xml
index 9be5f9f51..0fff2f490 100644
--- a/adservices/apk/res/values-it/strings.xml
+++ b/adservices/apk/res/values-it/strings.xml
@@ -47,12 +47,13 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Come partecipare"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Se attivi il programma beta consenti alle app di testare queste nuove modalità più private di mostrarti gli annunci. Puoi disattivare il programma beta in qualsiasi momento dalle impostazioni di privacy."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"No, grazie"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Sì, partecipo"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Attiva"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Altro"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Grazie per aver scelto di partecipare"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Fai parte del programma beta di Android sulla privacy per gli annunci. Privacy Sandbox è attivato per il tuo dispositivo.\n\nSe vuoi saperne di più o vuoi disattivare il programma beta, vai alle impostazioni della privacy in qualsiasi momento."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Hai deciso di non partecipare"</string>
<string name="notificationUI_confirmation_decline_subtitle" msgid="4194388733937808440">"Grazie per la tua risposta. Privacy Sandbox è disattivato per il tuo dispositivo.\n\nSe cambi idea o vuoi saperne di più, vai alle impostazioni della privacy."</string>
- <string name="notificationUI_confirmation_left_control_button_text" msgid="7459283556129950513">"Impostazioni della privacy"</string>
+ <string name="notificationUI_confirmation_left_control_button_text" msgid="7459283556129950513">"Impostazioni privacy"</string>
<string name="notificationUI_confirmation_right_control_button_text" msgid="1390803936115621269">"OK"</string>
<string name="notificationUI_container2_title" msgid="3844342008598264655">"Fai parte del programma beta"</string>
<string name="notificationUI_container2_body_text" msgid="758560159693325060">"Privacy Sandbox è attivato per il tuo dispositivo e le app possono testare queste nuove modalità più private di mostrarti gli annunci. Puoi disattivare il programma beta in qualsiasi momento dalle impostazioni di privacy."</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Al momento non ci sono interessi da mostrare"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Il programma beta di Android sulla privacy per gli annunci offre nuove funzionalità che le app possono utilizzare per mostrarti annunci che potrebbero piacerti. Queste tecnologie non usano identificatori dei dispositivi.\n\nAndroid può stimare i tipi di annunci che potrebbero interessarti e salvare temporaneamente questi interessi sul dispositivo. Ciò consente alle app di mostrarti annunci pertinenti, senza tracciare l\'attività su siti web e app di altri sviluppatori."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Non ci sono interessi bloccati"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Le app possono stimare i tuoi interessi e salvarli temporaneamente sul dispositivo Android. Successivamente, un\'altra app può mostrarti un annuncio basato su questi interessi.\n\nSe blocchi un\'app, questa non stimerà più i tuoi interessi. Non verrà più aggiunta a questo elenco di app a meno che non la sblocchi. Gli interessi già stimati dall\'app verranno eliminati, ma potresti comunque vedere alcuni annunci correlati."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"App che hai bloccato"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Reimposta gli interessi stimati dalle app"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Il programma beta di Android sulla privacy per gli annunci offre nuove funzionalità che le app possono utilizzare per mostrarti annunci che potrebbero piacerti. Queste tecnologie non usano identificatori dei dispositivi.\n\nLe app possono stimare i tipi di annunci che potrebbero interessarti e salvare temporaneamente questi interessi sul dispositivo. Ciò consente alle app di mostrarti annunci pertinenti, senza tracciare l\'attività su siti web e app di altri sviluppatori."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Non ci sono app bloccate"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Non ci sono interessi bloccati"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Annulla"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Vuoi disattivare Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Se cambi idea o se vuoi scoprire di più sul programma Privacy annunci di Android (beta), vai alle impostazioni della privacy"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Disattiva"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Vuoi bloccare <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Questo interesse verrà bloccato non verrà aggiunto nuovamente all\'elenco finché non lo aggiungerai di nuovo. Potresti comunque vedere alcuni annunci correlati."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blocca"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"L\'argomento <xliff:g id="TOPIC">%1$s</xliff:g> è sbloccato"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android potrebbe aggiungere di nuovo al tuo elenco questo interesse, che potrebbe non essere visualizzato subito"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Vuoi reimpostare tutti i tuoi interessi?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Il tuo elenco verrà cancellato e d\'ora in poi verranno stimati nuovi interessi. Potresti comunque vedere alcuni annunci correlati agli interessi cancellati."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Reimposta"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Vuoi bloccare <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Questa app non stimerà gli interessi per Privacy Sandbox e non verrà aggiunta nuovamente all\'elenco finché non la sblocchi.\n\nGli interessi già stimati dall\'app verranno eliminati, ma potresti comunque vedere alcuni annunci correlati."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"L\'app <xliff:g id="APP">%1$s</xliff:g> è sbloccata"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Questa app può stimare di nuovo gli interessi per te, ma gli interessi potrebbero non essere visualizzati subito nell\'elenco. Potrebbe occorrere un po\' di tempo affinché tu possa vedere gli annunci correlati."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Vuoi reimpostare gli interessi generati dalle app?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Gli interessi stimati dalle app nel tuo elenco verranno eliminati da Privacy Sandbox e le app stimeranno nuovi interessi d\'ora in poi. Potresti comunque vedere alcuni annunci correlati."</string>
<string name="topic10001" msgid="1636806320891333775">"Arte e spettacoli"</string>
<string name="topic10002" msgid="1226367977754287428">"Recitazione e teatro"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime e manga"</string>
diff --git a/adservices/apk/res/values-iw/strings.xml b/adservices/apk/res/values-iw/strings.xml
index 81e1e947c..e1a6c3883 100644
--- a/adservices/apk/res/values-iw/strings.xml
+++ b/adservices/apk/res/values-iw/strings.xml
@@ -51,7 +51,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"איך משתתפים"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"הפעלת תוכנית הבטא מאפשרת לאפליקציות לבדוק איך להציג לך מודעות בדרכים חדשות ופרטיות יותר. אפשר להשבית את תוכנית הבטא בכל שלב בהגדרות הפרטיות."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"לא תודה"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"כן, אני רוצה להצטרף"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"הפעלה"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"עוד"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"תודה על השתתפותך"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"‏הצטרפת לתוכנית הבטא של פרטיות במודעות ב-Android. ארגז החול לפרטיות מופעל במכשיר.\n\nאפשר לקרוא מידע נוסף או להשבית את תוכנית הבטא בכל שלב בהגדרות הפרטיות."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"בחרת שלא להשתתף"</string>
@@ -83,8 +84,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"אין כרגע תחומי עניין להצגה"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"ארגז חול לפרטיות"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"‏תוכנית הבטא של פרטיות במודעות ב-Android מספקת תכונות חדשות שמאפשרות לאפליקציות להציג לך מודעות שעשויות להיות רלוונטיות עבורך. בטכנולוגיות האלה לא נעשה שימוש במזהי המכשיר.\n\nמערכת Android יכולה להעריך אילו סוגי מודעות עשויים לעניין אותך, ולשמור את תחומי העניין האלה באופן זמני על המכשיר. באופן הזה, האפליקציות יכולות להציג לך מודעות רלוונטיות בלי לעקוב אחרי הפעילות שלך באתרים ובאפליקציות של מפתחים אחרים."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"לא חסמת אף תחום עניין"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"‏האפליקציות יכולות להעריך את תחומי העניין שלך ולשמור אותם באופן זמני באמצעות Android. בהמשך, אפליקציות אחרות יוכלו להציג לך מודעות על סמך תחומי העניין האלה.\n\nאם בחרת לחסום אפליקציה מסוימת, היא לא תוכל להעריך את תחומי העניין שלך יותר. האפליקציה הזו לא תתווסף שוב לרשימת האפליקציות אלא אם החסימה תבוטל. כל תחומי העניין שהאפליקציה כבר העריכה יימחקו, אך יכול להיות שעדיין יוצגו לך כמה מודעות קשורות."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"אפליקציות שחסמת"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"איפוס הנתונים שהוערכו על ידי האפליקציות"</string>
@@ -92,7 +92,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"ארגז חול לפרטיות"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"‏תוכנית הבטא של פרטיות במודעות ב-Android מספקת תכונות חדשות שמאפשרות לאפליקציות להציג לך מודעות שעשויות להיות רלוונטיות עבורך. בטכנולוגיות האלה לא נעשה שימוש במזהי המכשיר.\n\nהאפליקציות יכולות להעריך אילו סוגי מודעות עשויים לעניין אותך, ולשמור את תחומי העניין האלה באופן זמני על המכשיר. באופן הזה, האפליקציות יכולות להציג לך מודעות רלוונטיות בלי לעקוב אחרי הפעילות שלך באתרים ובאפליקציות של מפתחים אחרים."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"לא חסמת אף אפליקציה"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"לא חסמת אף תחום עניין"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"ביטול"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"להשבית את ארגז החול לפרטיות?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"‏אם שינית את דעתך או שברצונך לקבל מידע נוסף על תוכנית הבטא של \'פרטיות בפרסום\' ב-Android, אפשר להיכנס אל הגדרות הפרטיות"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"השבתה"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"לחסום את <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"תחום העניין הזה ייחסם ולא יתווסף שוב לרשימה שלך, אלא אם הוספת אותו בחזרה. ייתכן שעדיין יוצגו כמה מודעות קשורות."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"חסימה"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"בוטלה החסימה של <xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"‏ייתכן ש-Android יוסיף שוב את תחום העניין הזה לרשימה שלך, אך יכול להיות שהוא לא יופיע מיד"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"אישור"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"לאפס את כל תחומי העניין?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"הרשימה תימחק ותחומי עניין חדשים יוערכו בהמשך. ייתכן שעדיין יוצגו כמה מודעות שקשורות לתחומי עניין שנמחקו."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"איפוס"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"לחסום את <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"האפליקציה הזו לא תעריך את תחומי העניין לארגז החול לפרטיות ולא תתווסף שוב לרשימה שלך, אלא אם ביטלת את החסימה שלה.\n\nכל תחומי העניין שהאפליקציה כבר העריכה יימחקו, אך יכול להיות שעדיין יוצגו לך כמה מודעות קשורות."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"בוטלה החסימה של <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"האפליקציה הזו יכולה להעריך את תחומי העניין בשבילך שוב, אך יכול להיות שהם לא יופיעו ברשימה שלך מיד. הצגת כל המודעות הקשורות עשויה לקחת הרבה זמן."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"לאפס את תחומי העניין שנוצרו על ידי האפליקציות?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"תחומי עניין שהוערכו על ידי האפליקציות ברשימה שלך יימחקו מארגז החול לפרטיות, והאפליקציות יעריכו תחומי עניין חדשים בהמשך. ייתכן שעדיין יוצגו כמה מודעות קשורות."</string>
<string name="topic10001" msgid="1636806320891333775">"אומנות ובידור"</string>
<string name="topic10002" msgid="1226367977754287428">"משחק ותיאטרון"</string>
<string name="topic10003" msgid="6949890838957814881">"אנימה ומנגה"</string>
diff --git a/adservices/apk/res/values-ja/strings.xml b/adservices/apk/res/values-ja/strings.xml
index 4f2f62b37..c725d5cbd 100644
--- a/adservices/apk/res/values-ja/strings.xml
+++ b/adservices/apk/res/values-ja/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"参加方法"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"ベータ版を ON にすると、アプリは広告の表示でプライバシー保護を強化したこれらの新しい方法をテストできます。ベータ版はプライバシー設定でいつでも OFF にできます。"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"利用しない"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"参加する"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ON にする"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"その他"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"ご参加ありがとうございます"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Android の広告のプライバシー(ベータ版)にご参加いただきありがとうございます。プライバシー サンドボックスはこのデバイスで ON になっています。\n\nベータ版はプライバシー設定でいつでも OFF にできます。詳しくは、プライバシー設定をご覧ください。"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"参加を取り消しました"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"現在、興味 / 関心はありません"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"プライバシー サンドボックス"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android の広告のプライバシー(ベータ版)は、アプリがユーザーにおすすめの広告を表示する場合に役立つ新しい機能を提供しています。これらの技術でデバイス識別子が使用されることはありません。\n\nAndroid により、ユーザーが興味や関心を持ちそうな広告の種類が推定され、その興味 / 関心がデバイスに一時的に保存されます。これにより、アプリは、他のデベロッパーのウェブサイトやアプリでユーザーのアクティビティを追跡することなく、関連性の高い広告を表示できます。"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ブロック中の興味 / 関心はありません"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"アプリはユーザーの興味 / 関心を推定し、Android で一時的に保存することができます。後で、別のアプリがこれらの興味 / 関心に基づいて広告を表示することができます。\n\nブロックしたアプリは、今後、興味 / 関心を推定しなくなります。ブロックを解除しない限り、そのアプリがこのアプリのリストに再び追加されることはありません。ブロックしたアプリですでに推定された興味 / 関心は削除されますが、一部の関連する広告は引き続き表示されることがあります。"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"ブロックしたアプリ"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"アプリが推定した興味 / 関心をリセット"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"プライバシー サンドボックス"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android の広告のプライバシー(ベータ版)は、アプリがユーザーにおすすめの広告を表示する場合に役立つ新しい機能を提供しています。これらの技術でデバイス識別子が使用されることはありません。\n\nアプリにより、ユーザーが興味や関心を持ちそうな広告の種類が推定され、その興味 / 関心がデバイスに一時的に保存されます。これにより、アプリは、他のデベロッパーのウェブサイトやアプリでユーザーのアクティビティを追跡することなく、関連性の高い広告を表示できます。"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ブロック中のアプリはありません"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ブロック中の興味 / 関心はありません"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"キャンセル"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"プライバシー サンドボックスを OFF にしますか?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"改めて参加される場合や、Android の広告のプライバシーのベータ版について詳細をご覧になりたい場合は、プライバシー設定にアクセスしてください。"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"OFF にする"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"「<xliff:g id="TOPIC">%1$s</xliff:g>」をブロックしますか?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"この興味 / 関心はブロックされ、ご自身で再び追加しない限り、リストに再び追加されることはありません。なお、一部の関連する広告は引き続き表示されることがあります。"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ブロック"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"「<xliff:g id="TOPIC">%1$s</xliff:g>」のブロックを解除しました"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"この興味 / 関心は Android によって再びリストに追加されることがありますが、すぐには反映されない可能性があります"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"興味 / 関心をすべてリセットしますか?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"現在のリストは削除され、今後は新しい興味 / 関心が推定されるようになります。なお、削除された興味 / 関心に関連する広告が引き続き表示されることもあります。"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"リセット"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> をブロックしますか?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"このアプリは、ブロックを解除しない限り、プライバシー サンドボックスで興味 / 関心を推定することはありません。また、リストに再び追加されることもありません。\n\nこのアプリですでに推定された興味 / 関心は削除されますが、一部の関連する広告は引き続き表示されることがあります。"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> のブロックを解除しました"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"このアプリはあなたの興味 / 関心を再び推定できるようになりますが、すぐにはリストに反映されない可能性があります。関連する広告が表示されるまで時間がかかることがあります。"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"アプリで生成された興味 / 関心をリセットしますか?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"リスト上でアプリが推定した興味 / 関心はプライバシー サンドボックスから削除され、今後は新しい興味 / 関心をアプリが推定するようになります。なお、関連する広告が引き続き表示されることもあります。"</string>
<string name="topic10001" msgid="1636806320891333775">"アート、エンターテインメント"</string>
<string name="topic10002" msgid="1226367977754287428">"劇場、映画館"</string>
<string name="topic10003" msgid="6949890838957814881">"アニメ、漫画"</string>
diff --git a/adservices/apk/res/values-ka/strings.xml b/adservices/apk/res/values-ka/strings.xml
index 645976756..455ee2ef9 100644
--- a/adservices/apk/res/values-ka/strings.xml
+++ b/adservices/apk/res/values-ka/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"მონაწილეობის წესი"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"ბეტას ჩართვით აპებს შეუძლია თქვენთვის რეკლამების ჩვენების უფრო მეტი გზების გამოცდა. ბეტას გამორთვა ნებისმიერ დროს შეგიძლიათ კონფიდენციალურობის პარამეტრებში."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"არა, გმადლობთ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"დიახ, შევუერთდები"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ჩართვა"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"მეტი"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"გმადლობთ მონაწილეობისათვის"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"თქვენ Android რეკლამების კონფიდენციალურობის (ბეტა) წევრი ხართ. Privacy Sandbox ჩართულია თქვენს მოწყობილობაზე.\n\nშეიტყვეთ მეტი ან გამორთეთ ბეტა ნებისმიერ დროს კონფიდენციალურობის პარამეტრებიდან."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"თქვენ გადაწყვიტეთ, რომ არ მიიღოთ მონაწილეობა"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ამჟამად საჩვენებელი ინტერესები არ არის"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android-ის რეკლამების კონფიდენციალურობა (ბეტა) უზრუნველყოფს ისეთ ახალ ფუნქციებს, რომლებსაც აპები იყენებს თქვენთვის საინტერესო რეკლამების საჩვენებლად. ეს ტექნოლოგია არ იყენებს მოწყობილობის იდენტიფიკატორებს.\n\nAndroid აფასებს, როგორი რეკლამები გაინტერესებთ და ამ ინფორმაციას დროებით თქვენს მოწყობილობაში ინახავს. ეს აპებს საშუალებას აძლევს, გაჩვენოს თქვენთვის საინტერესო რეკლამები ვებსა და აპებში თქვენი აქტივობის თვალყურის დევნების გარეშე."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"დაბლოკილი ინტერესები არ გაქვთ"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"აპებს შეუძლია თქვენი ინტერესების შეფასება და დროებით მათი Android-ში შენახვა. მოგვიანებით, ამ ინტერესების საფუძველზე, სხვა აპი გაჩვენებთ რეკლამებს.\n\nთუ აპს დაბლოკავთ, ის თქვენს ინტერესებს ვეღარ შეაფასებს. ის აღარ დაემატა ამ სიას, სანამ არ განბლოკავთ. აპის მიერ უკვე შეფასებული ინტერესები ამოიშლება, მაგრამ მსგავსი რეკლამები, შესაძლოა, მაინც ნახოთ."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"თქვენ მიერ დაბლოკილი აპები"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"აპების მიერ შეფასებული ინტერესების გადაყენება"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android-ის რეკლამების კონფიდენციალურობა (ბეტა) უზრუნველყოფს ისეთ ახალ ფუნქციებს, რომლებსაც აპები იყენებს თქვენთვის საინტერესო რეკლამების საჩვენებლად. ეს ტექნოლოგია არ იყენებს მოწყობილობის იდენტიფიკატორებს.\n\nთქვენი აპები აფასებს, როგორი რეკლამები გაინტერესებთ და ამ ინფორმაციას დროებით თქვენს მოწყობილობაში ინახავს. ეს აპებს საშუალებას აძლევს, გაჩვენოს თქვენთვის საინტერესო რეკლამები ვებსა და აპებში თქვენი აქტივობის თვალყურის დევნების გარეშე."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"დაბლოკილი აპები არ გაქვთ"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"დაბლოკილი ინტერესები არ გაქვთ"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"გაუქმება"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"გსურთ Privacy Sandbox-ის გამორთვა?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"თუ გადაიფიქრეთ ან გსურთ გაიგოთ მეტი Android-ის რეკლამის კონფიდენციალურობის ბეტას შესახებ, გადადით თქვენი კონფიდენციალურობის პარამეტრებზე"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"გამორთვა"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"გსურთ, დაბლოკოთ <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ეს ინტერესი დაიბლოკება და აღარ დაემატება თქვენს სიას, თუ არ დაამატებთ მას. თქვენ შეიძლება ისევ ნახოთ მსგავსი რეკლამები."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"დაბლოკვა"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> განბლოკილია"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android-მა შეიძლება კვლავ დაამატოს ეს ინტერესი თქვენს სიაში, მაგრამ ის შეიძლება მაშინვე არ გამოჩნდეს"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"კარგი"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"გსურთ, რომ გადააყენოთ ყველა თქვენი ინტერესი?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"თქვენი სია გასუფთავდება და შეფასდება მომავალი ინტერესები. თქვენ კვლავ შეგიძლიათ ნახოთ გარკვეული რეკლამები, რომლებიც დაკავშირებულია გასუფთავებულ ინტერესებთან."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"გადაყენება"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"გსურთ, დაბლოკოთ <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ეს აპი არ შეაფასებს ინტერესებს Privacy Sandbox-ისთვის და აღარ დაემატება თქვენს სიას, თუ არ განბლოკავთ მას.\n\nამ აპის მიერ უკვე შეფასებული ინტერესები წაიშლება, მაგრამ თქვენ შესაძლოა კვლავ ნახოთ დაკავშირებული რეკლამები."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> განბლოკილია"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ამ აპს შეუძლია კვლავ შეაფასოს თქვენთვის ინტერესები, მაგრამ ის შესაძლოა დაუყოვნებლივ არ გამოჩნდეს თქვენს სიაში. დაკავშირებული რეკლამის ნახვას შესაძლოა გარკვეული დრო დასჭირდეს."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"გსურთ აპების მიერ გენერირებული ინტერესების გადაყენება?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"თქვენს სიაში არსებული აპების მიერ შეფასებული ინტერესები წაიშლება Privacy Sandbox-იდან და მომავალში აპები ახალ ინტერესებს შეაფასებს. თქვენ შეიძლება ისევ ნახოთ მსგავსი რეკლამები."</string>
<string name="topic10001" msgid="1636806320891333775">"ხელოვნება და გართობა"</string>
<string name="topic10002" msgid="1226367977754287428">"სამსახიობო ოსტატობა და თეატრი"</string>
<string name="topic10003" msgid="6949890838957814881">"ანიმე და მანგა"</string>
diff --git a/adservices/apk/res/values-kk/strings.xml b/adservices/apk/res/values-kk/strings.xml
index 997eec372..9785dcd67 100644
--- a/adservices/apk/res/values-kk/strings.xml
+++ b/adservices/apk/res/values-kk/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Қатысу жолы"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Бета нұсқасын қосқан кезде, қолданбалар жарнама көрсетудің осындай жаңа әрі жеке жолдарын сынай алады. Бета нұсқасын кез келген уақытта құпиялылық параметрлерінен өшіруге болады."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Жоқ, рақмет"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Иә, қосыламын"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Қосу"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Тағы"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Қатысқаныңызға рақмет!"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Сіз Android-тың жарнама құпиялылығына (бета нұсқасы) арналған бағдарламаның қатысушысыз. Құрылғыңызда Privacy Sandbox қосылған.\n\nТолық ақпарат алуға немесе бета нұсқасын кез келген уақытта құпиялылық параметрлерінде өшіруге болады."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Қатысудан бас тарттыңыз"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Дәл қазір көрсететін қызығушылық жоқ."</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android-тың жарнама құпиялылығы (бета нұсқасы) қолданбалар сізге ұнайды деген жарнамаларды көрсету үшін пайдалана алатын жаңа функцияларды ұсынады. Бұл технологиялар құрылғы идентификаторларын пайдаланбайды.\n\nAndroid сізді қызықтыруы мүмкін жарнама түрлерін бағалап, бұл қызығушылықтарды құрылғыңызда уақытша сақтай алады. Соның нәтижесінде қолданбалар басқа әзірлеушілердің веб-сайттары мен қолданбаларында жасаған әрекеттеріңізді бақыламай, сізге сәйкес жарнамаларды көрсете алады."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Сізде бөгелген қызығушылықтар жоқ"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Қолданбалар қызығушылықтарыңызды бағалап, оларды уақытша Android-та сақтай алады. Содан кейін осы қызығушылыққа қарай басқа қолданба сізге марафондарға қатысты жарнама көрсете алады.\n\nҚолданбаны бөгесеңіз, ол бұдан былай қызығушылықтарды бағаламайды. Қолданбаны бөгеуден алмайынша, ол қолданбалар тізіміне қайта қосылмайды. Қолданба бағалап қойған қызығушылықтар жойылады, бірақ оған қатысты кейбір жарнамалар көрсетіле беруі мүмкін."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Сіз бөгеген қолданбалар"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Қолданбалар бағалаған қызығушылықтарды бастапқы күйге қайтару"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android-тың жарнама құпиялылығы (бета нұсқасы) қолданбалар сізге ұнайды деген жарнамаларды көрсету үшін пайдалана алатын жаңа функцияларды ұсынады. Бұл технологиялар құрылғы идентификаторларын пайдаланбайды.\n\nҚолданбалар сізді қызықтыруы мүмкін жарнама түрлерін бағалап, бұл қызығушылықтарды құрылғыңызда уақытша сақтай алады. Соның нәтижесінде қолданбалар басқа әзірлеушілердің веб-сайттары мен қолданбаларында жасаған әрекеттеріңізді бақыламай, сізге сәйкес жарнамаларды көрсете алады."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Сізде бөгелген қолданбалар жоқ"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Сізде бөгелген қызығушылықтар жоқ"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Бас тарту"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Privacy Sandbox өшірілсін бе?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ойыңыздан айнып қалсаңыз немесе Android-тың жарнама құпиялылығы (бета нұсқасы) туралы толық ақпарат алғыңыз келсе, құпиялылық параметрлеріне өтіңіз."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Өшіру"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> бөгелсін бе?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Бұл қызығушылық бөгеледі және ол тізімге қосылмайынша пайда болмайды. Қызығушылықтарға қатысты кейбір жарнамалар көрсетіле беруі мүмкін."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Бөгеу"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> бөгеуден шығарылды"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android бұл қызығушылықты тізімге қайта қосуы мүмкін. Алайда ол тізімде дереу пайда болмайды."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Жарайды"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Барлық қызығушылық қайтарылсын ба?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Тізім өшіріледі және жаңа қызығушылықтар анықталады. Өшірілген қызығушылықтарға қатысты кейбір жарнамалар көрсетіле беруі мүмкін."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Қайтару"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> бөгелсін бе?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Бұл қолданба Privacy Sandbox технологиясына қатысты қызығушылықтарыңызды анықтамайды және оның бөгеуі алынбайынша тізімге қайта қосылмайды.\n\nҚолданба анықтаған қызығушылықтар жойылады, бірақ ұқсас жарнамалар көрсетілуі мүмкін."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> бөгеуден шығарылды"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Бұл қолданба қызығушылықтарыңызды қайта анықтай алады. Алайда ол тізімде дереу пайда болмайды. Ұқсас жарнамалардың көрсетілуіне біраз уақыт керек болуы мүмкін."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Қолданбалар арқылы жасалған қызығушылықтар қайтарылсын ба?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Қолданбалар анықтаған тізімдегі қызығушылықтар Privacy Sandbox-тан жойылады, ал қолданбалар жаңа қызығушылықтарды анықтайтын болады. Қызығушылықтарға қатысты кейбір жарнамалар көрсетіле беруі мүмкін."</string>
<string name="topic10001" msgid="1636806320891333775">"Өнер және ойын-сауық"</string>
<string name="topic10002" msgid="1226367977754287428">"Актерлік өнер және театр"</string>
<string name="topic10003" msgid="6949890838957814881">"Аниме және манга"</string>
diff --git a/adservices/apk/res/values-km/strings.xml b/adservices/apk/res/values-km/strings.xml
index cb3646150..c2d809bfb 100644
--- a/adservices/apk/res/values-km/strings.xml
+++ b/adservices/apk/res/values-km/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"របៀបចូលរួម"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"ការបើកកំណែបេតាអនុញ្ញាតឱ្យកម្មវិធីធ្វើតេស្តវិធីដែលកាន់តែមានលក្ខណៈឯកជនទាំងនេះ ដើម្បីបង្ហាញការផ្សាយពាណិជ្ជកម្មដល់អ្នក។ អ្នកអាចបិទកំណែបេតាបានគ្រប់ពេលនៅក្នុងការកំណត់ឯកជនភាពរបស់អ្នក។"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"ទេ អរគុណ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"បាទ/ចាស ខ្ញុំនឹងចូលរួម"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"បើក"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"ច្រើនទៀត"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"សូមអរគុណ​សម្រាប់​ការចូលរួម"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"អ្នកគឺជាផ្នែកនៃកំណែបេតានៃឯកជនភាពនៃការផ្សាយពាណិជ្ជកម្មរបស់ Android។ Privacy Sandbox ត្រូវបានបើកសម្រាប់ឧបករណ៍របស់អ្នក។\n\nអ្នកអាចស្វែងយល់បន្ថែម ឬបិទកំណែបេតាបានគ្រប់ពេលនៅក្នុងការកំណត់ឯកជនភាពរបស់អ្នក។"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"អ្នកបានជ្រើសរើស​មិន​ចូលរួម"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"គ្មានចំណាប់អារម្មណ៍ដែលត្រូវបង្ហាញឥឡូវនេះទេ"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"កំណែបេតានៃឯកជនភាពនៃការផ្សាយពាណិជ្ជកម្មរបស់ Android ផ្តល់នូវមុខងារថ្មីៗដែលកម្មវិធីអាចប្រើដើម្បីបង្ហាញអ្នកនូវការផ្សាយពាណិជ្ជកម្មដែលអ្នកប្រហែលជាចូលចិត្ត។ បច្ចេកវិទ្យាទាំងនេះមិនប្រើលេខកូដសម្គាល់ឧបករណ៍ទេ។\n\nAndroid អាចប៉ាន់ស្មានប្រភេទនៃការផ្សាយពាណិជ្ជកម្មដែលអ្នកប្រហែលជាចាប់អារម្មណ៍ និងរក្សាទុកចំណាប់អារម្មណ៍ទាំងនេះជាបណ្តោះអាសន្ននៅលើឧបករណ៍របស់អ្នក។ ដំណើរការនេះអនុញ្ញាតឱ្យកម្មវិធីបង្ហាញអ្នកនូវការផ្សាយពាណិជ្ជកម្មដែលពាក់ព័ន្ធ ដោយមិនតាមដានសកម្មភាពរបស់អ្នកនៅលើគេហទំព័រ និងកម្មវិធីពីអ្នកអភិវឌ្ឍន៍ផ្សេងទៀត។"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"អ្នកមិនមានចំណាប់អារម្មណ៍ដែលត្រូវបានទប់ស្កាត់ទេ"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"កម្មវិធីអាចប៉ាន់ស្មានចំណាប់អារម្មណ៍របស់អ្នក និងរក្សាទុកចំណាប់អារម្មណ៍ទាំងនេះជាបណ្តោះអាសន្នជាមួយ Android។ ក្រោយមក កម្មវិធីផ្សេងអាចបង្ហាញអ្នកនូវការផ្សាយពាណិជ្ជកម្មដោយផ្អែកលើចំណាប់អារម្មណ៍ទាំងនេះ។\n\nប្រសិនបើអ្នកទប់ស្កាត់កម្មវិធីណាមួយ វានឹងមិនអាចប៉ាន់ស្មានចំណាប់អារម្មណ៍ទៀតទេ។ កម្មវិធីនោះ​នឹងមិន​ត្រូវ​បាន​បញ្ចូលទៅក្នុង​បញ្ជីកម្មវិធីម្ដងទៀតនោះទេ លុះត្រាតែអ្នកឈប់​ទប់ស្កាត់វា។ ចំណាប់អារម្មណ៍​ដែល​ប៉ាន់ស្មាន​ដោយ​កម្មវិធី​នោះ​រួច​ហើយ​នឹង​ត្រូវ​បាន​លុបចេញ ប៉ុន្តែ​អ្នក​នៅ​តែ​អាច​មើល​ឃើញ​ការផ្សាយ​ពាណិជ្ជកម្ម​ដែល​ពាក់ព័ន្ធ​មួយ​ចំនួន។"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"កម្មវិធី​ដែល​អ្នក​បាន​ទប់ស្កាត់"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"កំណត់ចំណាប់អារម្មណ៍ដែលប៉ាន់ស្មានដោយកម្មវិធីឡើងវិញ"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"កំណែបេតានៃឯកជនភាពនៃការផ្សាយពាណិជ្ជកម្មរបស់ Android ផ្តល់នូវមុខងារថ្មីៗដែលកម្មវិធីអាចប្រើដើម្បីបង្ហាញអ្នកនូវការផ្សាយពាណិជ្ជកម្មដែលអ្នកប្រហែលជាចូលចិត្ត។ បច្ចេកវិទ្យាទាំងនេះមិនប្រើលេខកូដសម្គាល់ឧបករណ៍ទេ។\n\nកម្មវិធីអាចប៉ាន់ស្មានប្រភេទនៃការផ្សាយពាណិជ្ជកម្មដែលអ្នកប្រហែលជាចាប់អារម្មណ៍ និងរក្សាទុកចំណាប់អារម្មណ៍ទាំងនេះជាបណ្តោះអាសន្ននៅលើឧបករណ៍របស់អ្នក។ ដំណើរការនេះអនុញ្ញាតឱ្យកម្មវិធីបង្ហាញអ្នកនូវការផ្សាយពាណិជ្ជកម្មដែលពាក់ព័ន្ធ ដោយមិនតាមដានសកម្មភាពរបស់អ្នកនៅលើគេហទំព័រ និងកម្មវិធីពីអ្នកអភិវឌ្ឍន៍ផ្សេងទៀត។"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"អ្នកមិនមានកម្មវិធីដែលត្រូវបានទប់ស្កាត់ទេ"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"អ្នកមិនមានចំណាប់អារម្មណ៍ដែលត្រូវបានទប់ស្កាត់ទេ"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"បោះបង់"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"បិទ Privacy Sandbox ឬ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"ប្រសិនបើអ្នក​ប្ដូរចិត្តរបស់អ្នក ឬចង់ស្វែងយល់បន្ថែមអំពី​កំណែបេតានៃឯកជនភាពនៃការផ្សាយពាណិជ្ជកម្ម​របស់ Android សូមចូលទៅកាន់​ការកំណត់ឯកជនភាព​របស់អ្នក"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"បិទ"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"ទប់ស្កាត់ <xliff:g id="TOPIC">%1$s</xliff:g> ឬ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ចំណាប់អារម្មណ៍នេះ​នឹងត្រូវបានទប់ស្កាត់ ហើយនឹងមិនត្រូវបាន​បញ្ចូលទៅក្នុងបញ្ជីរបស់អ្នក​ម្ដងទៀតទេ ប្រសិនបើអ្នកមិនបញ្ចូល​កម្មវិធីនេះវិញទេ។ អ្នកនៅតែ​អាច​មើល​ឃើញការផ្សាយពាណិជ្ជកម្ម​ដែល​ពាក់ព័ន្ធមួយចំនួន។"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ទប់ស្កាត់"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> ត្រូវបាន​ឈប់ទប់ស្កាត់"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android អាចបញ្ចូល​ចំណាប់អារម្មណ៍នេះ​ទៅក្នុងបញ្ជីរបស់អ្នក​ម្ដងទៀត ប៉ុន្តែចំណាប់អារម្មណ៍នេះ​ប្រហែលជាមិនបង្ហាញភ្លាមៗទេ"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"យល់ព្រម"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"កំណត់​ចំណាប់អារម្មណ៍ទាំងអស់របស់អ្នក​ឡើងវិញឬ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"បញ្ជីរបស់អ្នកនឹងត្រូវបានលុប ហើយចំណាប់អារម្មណ៍ថ្មីនឹងត្រូវបានប៉ាន់ស្មានពីពេលនេះតទៅ។ អ្នកនៅតែអាចមើលឃើញការផ្សាយពាណិជ្ជកម្មមួយចំនួនដែលពាក់ព័ន្ធនឹងចំណាប់អារម្មណ៍ដែលបានលុប។"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"កំណត់​ឡើងវិញ"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"ទប់ស្កាត់ <xliff:g id="APP">%1$s</xliff:g> ឬ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"កម្មវិធីនេះ​នឹងមិនប៉ាន់ស្មាន​ចំណាប់អារម្មណ៍​សម្រាប់ Privacy Sandbox ហើយនឹងមិនត្រូវបានបញ្ចូល​ទៅក្នុងបញ្ជីរបស់អ្នក​ម្ដងទៀតទេ ប្រសិនបើអ្នក​មិនឈប់ទប់ស្កាត់​កម្មវិធីនេះទេ។\n\nចំណាប់អារម្មណ៍​ដែលបានប៉ាន់ស្មានដោយកម្មវិធីនេះ​រួចហើយ​នឹងត្រូវបានលុប ប៉ុន្តែអ្នកប្រហែលជា​នៅតែមើលឃើញ​ការផ្សាយពាណិជ្ជកម្មដែលពាក់ព័ន្ធ​មួយចំនួន។"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> ត្រូវបាន​ឈប់ទប់ស្កាត់"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"កម្មវិធីនេះ​អាចប៉ាន់ស្មាន​ចំណាប់អារម្មណ៍​សម្រាប់អ្នកម្ដងទៀត ប៉ុន្តែចំណាប់អារម្មណ៍នេះប្រហែលជាមិនបង្ហាញ​នៅលើបញ្ជីរបស់អ្នក​ភ្លាមៗនោះទេ។ អាចចំណាយពេលបន្តិច ដើម្បីឱ្យអ្នកមើលឃើញ​ការផ្សាយពាណិជ្ជកម្ម​ដែលពាក់ព័ន្ធ។"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"កំណត់ចំណាប់អារម្មណ៍​ដែលបង្កើតដោយកម្មវិធី​ឡើងវិញឬ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"ចំណាប់អារម្មណ៍ដែលបានប៉ាន់ស្មានដោយកម្មវិធីក្នុងបញ្ជីរបស់អ្នកនឹងត្រូវលុបចេញពី Privacy Sandbox ហើយកម្មវិធីនឹងប៉ាន់ស្មានចំណាប់អារម្មណ៍ថ្មីពីពេលនេះតទៅ។ អ្នកនៅតែអាចមើលឃើញការផ្សាយពាណិជ្ជកម្មមួយចំនួនដែលពាក់ព័ន្ធ។"</string>
<string name="topic10001" msgid="1636806320891333775">"សិល្បៈ និងការកម្សាន្ត"</string>
<string name="topic10002" msgid="1226367977754287428">"ការសម្ដែង និងរោង​ល្ខោន"</string>
<string name="topic10003" msgid="6949890838957814881">"គំនូរជីវចល និងតុក្កតា"</string>
diff --git a/adservices/apk/res/values-kn/strings.xml b/adservices/apk/res/values-kn/strings.xml
index 2ac9c1044..ae73923fc 100644
--- a/adservices/apk/res/values-kn/strings.xml
+++ b/adservices/apk/res/values-kn/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"ಭಾಗವಹಿಸುವುದು ಹೇಗೆ"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"ಬೀಟಾವನ್ನು ಆನ್ ಮಾಡುವುದರಿಂದ ನಿಮಗೆ ಜಾಹೀರಾತುಗಳನ್ನು ತೋರಿಸುವ ಈ ಇನ್ನಷ್ಟು ಖಾಸಗಿಯಾದ ವಿಧಾನವನ್ನು ಪರೀಕ್ಷಿಸುವ ಅವಕಾಶ ಆ್ಯಪ್‌ಗಳಿಗೆ ದೊರೆಯುತ್ತದೆ. ನಿಮ್ಮ ಗೌಪ್ಯತೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ನೀವು ಯಾವಾಗ ಬೇಕಾದರೂ ಬೀಟಾ ಅನ್ನು ಆಫ್ ಮಾಡಬಹುದು."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"ಬೇಡ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ಹೌದು, ನಾನು ಸೇರಿಕೊಳ್ಳುತ್ತೇನೆ"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ಆನ್ ಮಾಡಿ"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"ಇನ್ನಷ್ಟು"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"ಭಾಗವಹಿಸಿದ್ದಕ್ಕಾಗಿ ಧನ್ಯವಾದಗಳು"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"ನೀವು Android ನ ಜಾಹೀರಾತುಗಳ ಗೌಪ್ಯತೆ ಬೀಟಾದ ಭಾಗವಾಗಿದ್ದೀರಿ. ನಿಮ್ಮ ಸಾಧನಕ್ಕಾಗಿ ಪ್ರೈವೆಸಿ ಸ್ಯಾಂಡ್‌ಬಾಕ್ಸ್ ಅನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ.\n\nನಿಮ್ಮ ಗೌಪ್ಯತೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ನೀವು ಇನ್ನಷ್ಟು ತಿಳಿಯಬಹುದು ಅಥವಾ ಬೀಟಾ ಅನ್ನು ಯಾವಾಗ ಬೇಕಾದರೂ ಆಫ್ ಮಾಡಬಹುದು."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"ನೀವು ಭಾಗವಹಿಸದಿರಲು ಆಯ್ಕೆಮಾಡಿದ್ದೀರಿ"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ಇದೀಗ ತೋರಿಸಲು ಯಾವುದೇ ಆಸಕ್ತಿಗಳಿಲ್ಲ"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"ಗೌಪ್ಯತೆ ಸ್ಯಾಂಡ್‌ಬಾಕ್ಸ್"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android ಜಾಹೀರಾತುಗಳ ಗೌಪ್ಯತೆಯ ಬೀಟಾ ಹೊಸ ಫೀಚರ್‌ಗಳನ್ನು ಒದಗಿಸುತ್ತಿದ್ದು ಅವುಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್‌ಗಳು ನಿಮಗೆ ಇಷ್ಟವಾಗಬಹುದಾದ ಜಾಹೀರಾತುಗಳನ್ನು ತೋರಿಸಬಹುದು. ಈ ತಂತ್ರಜ್ಞಾನಗಳು ಸಾಧನ ಗುರುತಿಸುವಿಕೆಗಳನ್ನು ಬಳಸುವುದಿಲ್ಲ.\n\nAndroid ನಿಮಗೆ ಯಾವ ಪ್ರಕಾರದ ಜಾಹೀರಾತುಗಳು ಇಷ್ಟವಾಗುತ್ತವೆ ಎಂಬುದನ್ನು ಅಂದಾಜು ಮಾಡಬಲ್ಲದು ಮತ್ತು ಈ ಆಸಕ್ತಿಗಳನ್ನು ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ತಾತ್ಕಾಲಿಕವಾಗಿ ಉಳಿಸಬಲ್ಲದು. ಇತರ ಡೆವಲಪರ್‌ಗಳ ವೆಬ್‌ಸೈಟ್‌ಗಳು ಮತ್ತು ಆ್ಯಪ್‌ಗಳಾದ್ಯಂತ ನಿಮ್ಮ ಚಟುವಟಿಕೆಯನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡದೇ ನಿಮಗೆ ಸೂಕ್ತವಾದ ಜಾಹೀರಾತುಗಳನ್ನು ತೋರಿಸಲು ಇದು ಆ್ಯಪ್‌ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ನೀವು ಯಾವುದೇ ನಿರ್ಬಂಧಿತ ಆಸಕ್ತಿಗಳನ್ನು ಹೊಂದಿಲ್ಲ"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"ಆ್ಯಪ್‌ಗಳು ನಿಮ್ಮ ಆಸಕ್ತಿಗಳನ್ನು ಅಂದಾಜು ಮಾಡಬಲ್ಲವು ಮತ್ತು ಅವುಗಳನ್ನು Android ನಲ್ಲಿ ತಾತ್ಕಾಲಿಕವಾಗಿ ಉಳಿಸಬಲ್ಲವು. ನಂತರ ಈ ಆಸಕ್ತಿಗಳನ್ನು ಆಧರಿಸಿ ಮತ್ತೊಂದು ಆ್ಯಪ್ ನಿಮಗೆ ಜಾಹೀರಾತೊಂದನ್ನು ತೋರಿಸಬಹುದು.\n\nನೀವು ಆ್ಯಪ್ ಒಂದನ್ನು ನಿರ್ಬಂಧಿಸಿದರೆ, ಅದು ಯಾವುದೇ ಆಸಕ್ತಿಗಳನ್ನು ಅಂದಾಜು ಮಾಡುವುದಿಲ್ಲ. ನೀವು ಅದರ ಮೇಲಿನ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆದುಹಾಕದ ಹೊರತು ಅದನ್ನು ಈ ಆ್ಯಪ್‌ಗಳ ಪಟ್ಟಿಗೆ ಸೇರಿಸಲಾಗುವುದಿಲ್ಲ. ಆ್ಯಪ್ ಈಗಾಗಲೇ ಅಂದಾಜು ಮಾಡಿರುವ ಆಸಕ್ತಿಗಳನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ, ಆದರೆ ನಿಮಗೆ ಈಗಲೂ ಕೆಲವು ಸಂಬಂಧಿತ ಜಾಹೀರಾತುಗಳು ಕಾಣಿಸಬಹುದು."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"ನೀವು ನಿರ್ಬಂಧಿಸಿದ ಆ್ಯಪ್‌ಗಳು"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ಆ್ಯಪ್‌ಗಳು ಅಂದಾಜು ಮಾಡಿರುವ ಆಸಕ್ತಿಗಳನ್ನು ರೀಸೆಟ್ ಮಾಡಿ"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"ಗೌಪ್ಯತೆ ಸ್ಯಾಂಡ್‌ಬಾಕ್ಸ್"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android ಜಾಹೀರಾತುಗಳ ಗೌಪ್ಯತೆಯ ಬೀಟಾ ಹೊಸ ಫೀಚರ್‌ಗಳನ್ನು ಒದಗಿಸುತ್ತಿದ್ದು ಅವುಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್‌ಗಳು ನಿಮಗೆ ಇಷ್ಟವಾಗಬಹುದಾದ ಜಾಹೀರಾತುಗಳನ್ನು ತೋರಿಸಬಹುದು. ಈ ತಂತ್ರಜ್ಞಾನಗಳು ಸಾಧನ ಗುರುತಿಸುವಿಕೆಗಳನ್ನು ಬಳಸುವುದಿಲ್ಲ.\n\nಆ್ಯಪ್‌ಗಳು ನಿಮಗೆ ಯಾವ ಪ್ರಕಾರದ ಜಾಹೀರಾತುಗಳು ಇಷ್ಟವಾಗುತ್ತವೆ ಎಂಬುದನ್ನು ಅಂದಾಜು ಮಾಡಬಲ್ಲವು ಮತ್ತು ಈ ಆಸಕ್ತಿಗಳನ್ನು ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ತಾತ್ಕಾಲಿಕವಾಗಿ ಉಳಿಸಬಲ್ಲವು. ಇತರ ಡೆವಲಪರ್‌ಗಳ ವೆಬ್‌ಸೈಟ್‌ಗಳು ಮತ್ತು ಆ್ಯಪ್‌ಗಳಾದ್ಯಂತ ನಿಮ್ಮ ಚಟುವಟಿಕೆಯನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡದೇ ನಿಮಗೆ ಸೂಕ್ತವಾದ ಜಾಹೀರಾತುಗಳನ್ನು ತೋರಿಸಲು ಇದು ಆ್ಯಪ್‌ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ನೀವು ಯಾವುದೇ ನಿರ್ಬಂಧಿತ ಆ್ಯಪ್‌ಗಳನ್ನು ಹೊಂದಿಲ್ಲ"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ನೀವು ಯಾವುದೇ ನಿರ್ಬಂಧಿತ ಆಸಕ್ತಿಗಳನ್ನು ಹೊಂದಿಲ್ಲ"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"ರದ್ದುಮಾಡಿ"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"ಪ್ರೈವೆಸಿ ಸ್ಯಾಂಡ್‌ಬಾಕ್ಸ್ ಅನ್ನು ಆಫ್ ಮಾಡುವುದೇ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"ನೀವು ಮನಸ್ಸು ಬದಲಾಯಿಸಿದರೆ ಅಥವಾ Android ನ ಜಾಹೀರಾತುಗಳ ಗೌಪ್ಯತೆ ಬೀಟಾ ಕುರಿತು ಇನ್ನಷ್ಟು ತಿಳಿದುಕೊಳ್ಳಲು ಬಯಸಿದರೆ, ನಿಮ್ಮ ಗೌಪ್ಯತೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ಹೋಗಿ"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ಆಫ್ ಮಾಡಿ"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> ಅನ್ನು ನಿರ್ಬಂಧಿಸುವುದೇ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ಈ ಆಸಕ್ತಿಯನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಅದನ್ನು ನೀವು ಮರಳಿ ಸೇರಿಸದ ಹೊರತು ಮತ್ತೆ ನಿಮ್ಮ ಪಟ್ಟಿಗೆ ಸೇರಿಸಲಾಗುವುದಿಲ್ಲ. ನಿಮಗೆ ಆಗಲೂ ಕೆಲವು ಸಂಬಂಧಿತ ಜಾಹೀರಾತುಗಳು ಕಾಣಿಸಬಹುದು."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ನಿರ್ಬಂಧಿಸಿ"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> ನ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ಈ ಆಸಕ್ತಿಯನ್ನು ಮತ್ತೆ ನಿಮ್ಮ ಪಟ್ಟಿಗೆ ಸೇರಿಸಬಹುದು, ಆದರೆ ಅದು ತಕ್ಷಣವೇ ಗೋಚರಿಸದೇ ಇರಬಹುದು"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ಸರಿ"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"ನಿಮ್ಮ ಎಲ್ಲಾ ಆಸಕ್ತಿಗಳನ್ನು ಮರುಹೊಂದಿಸುವುದೇ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"ನಿಮ್ಮ ಪಟ್ಟಿಯನ್ನು ತೆರವುಗೊಳಿಸಲಾಗುತ್ತದೆ ಹಾಗೂ ಇನ್ನು ಮುಂದೆ ಹೊಸ ಆಸಕ್ತಿಗಳನ್ನು ಅಂದಾಜು ಮಾಡಲಾಗುತ್ತದೆ. ತೆರವುಗೊಳಿಸಲಾದ ಆಸಕ್ತಿಗಳಿಗೆ ಸಂಬಂಧಿಸಿದ ಕೆಲವು ಜಾಹೀರಾತುಗಳು ಈಗಲೂ ನಿಮಗೆ ಕಾಣಿಸಬಹುದು."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"ಮರುಹೊಂದಿಸಿ"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> ಅನ್ನು ನಿರ್ಬಂಧಿಸುವುದೇ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ಈ ಆ್ಯಪ್ ಪ್ರೈವೆಸಿ ಸ್ಯಾಂಡ್‌ಬಾಕ್ಸ್‌ಗಾಗಿ ಆಸಕ್ತಿಗಳನ್ನು ಅಂದಾಜು ಮಾಡುವುದಿಲ್ಲ ಮತ್ತು ನೀವು ಅದಕ್ಕೆ ವಿಧಿಸಿರುವ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆದುಹಾಕದ ಹೊರತು ಮತ್ತೆ ನಿಮ್ಮ ಪಟ್ಟಿಗೆ ಸೇರಿಸಲಾಗುವುದಿಲ್ಲ.\n\nಈ ಆ್ಯಪ್‌ನಿಂದ ಈಗಾಗಲೇ ಅಂದಾಜಿಸಲಾದ ಆಸಕ್ತಿಗಳನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ, ಆದರೂ ಸಹ ನಿಮಗೆ ಕೆಲವು ಸಂಬಂಧಿತ ಜಾಹೀರಾತುಗಳು ಕಾಣಬಹುದು."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> ನ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ಈ ಆ್ಯಪ್ ನಿಮಗಾಗಿ ಆಸಕ್ತಿಗಳನ್ನು ಮತ್ತೊಮ್ಮೆ ಅಂದಾಜು ಮಾಡಬಹುದು, ಆದರೆ ಇದು ತಕ್ಷಣವೇ ನಿಮ್ಮ ಪಟ್ಟಿಯಲ್ಲಿ ಕಾಣಿಸದೇ ಇರಬಹುದು. ನಿಮಗೆ ಸಂಬಂಧಿತ ಜಾಹೀರಾತುಗಳು ಕಾಣಲು ಸ್ವಲ್ಪ ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"ಆ್ಯಪ್‌ಗಳಿಂದ ರಚಿಸಲಾದ ಆಸಕ್ತಿಗಳನ್ನು ಮರುಹೊಂದಿಸುವುದೇ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"ನಿಮ್ಮ ಪಟ್ಟಿಯಲ್ಲಿರುವ ಆ್ಯಪ್‌ಗಳು ಅಂದಾಜು ಮಾಡಿರುವ ಆಸಕ್ತಿಗಳನ್ನು ಪ್ರೈವೆಸಿ ಸ್ಯಾಂಡ್‌ಬಾಕ್ಸ್‌ನಿಂದ ಅಳಿಸಲಾಗುತ್ತದೆ ಹಾಗೂ ಆ್ಯಪ್‌ಗಳು ಇನ್ನು ಮುಂದೆ ಹೊಸ ಆಸಕ್ತಿಗಳನ್ನು ಅಂದಾಜು ಮಾಡುತ್ತವೆ. ನಿಮಗೆ ಈಗಲೂ ಕೆಲವು ಸಂಬಂಧಿತ ಜಾಹೀರಾತುಗಳು ಕಾಣಿಸಬಹುದು."</string>
<string name="topic10001" msgid="1636806320891333775">"ಕಲೆ &amp; ಮನರಂಜನೆ"</string>
<string name="topic10002" msgid="1226367977754287428">"ನಟನೆ &amp; ಥೇಟರ್"</string>
<string name="topic10003" msgid="6949890838957814881">"ಆನಿಮೇ &amp; ಮಾಂಗ"</string>
diff --git a/adservices/apk/res/values-ko/strings.xml b/adservices/apk/res/values-ko/strings.xml
index be56cb1e0..e6ec43284 100644
--- a/adservices/apk/res/values-ko/strings.xml
+++ b/adservices/apk/res/values-ko/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"참여 방법"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"베타를 사용 설정하면 앱에서 개인 정보 보호를 강화하면서 광고를 표시하는 새로운 방법을 테스트할 수 있게 됩니다. 언제든지 개인 정보 보호 설정에서 베타 참여를 중단할 수 있습니다."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"아니요"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"참여"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"사용 설정"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"더보기"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"참여해 주셔서 감사합니다"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Android 광고 개인 정보 보호 베타에 참여 중입니다. 기기에 개인 정보 보호 샌드박스가 사용 설정되어 있습니다\n\n개인 정보 보호 설정에서 언제든지 자세히 알아보거나 베타 참여를 중단할 수 있습니다"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"참여하지 않기로 선택했습니다"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"지금 표시할 관심분야 없음"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"개인 정보 보호 샌드박스"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android의 광고 개인 정보 보호 베타는 앱에서 사용자가 좋아할 만한 광고를 표시할 수 있는 새로운 기능을 제공합니다. 이 새로운 기술은 기기 식별자를 사용하지 않습니다.\n\nAndroid는 사용자가 관심을 보일 만한 광고 종류를 추정하고 관심분야를 기기에 임시 저장합니다. 이렇게 하면 앱이 다른 개발자의 웹사이트/앱에서 이루어진 활동을 추적하지 않고도 관련성 있는 광고를 표시할 수 있습니다."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"차단한 관심분야가 없습니다."</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"앱에서 관심분야를 추정하고 Android를 통해 임시로 저장할 수 있습니다. 나중에 다른 앱이 이 관심분야를 토대로 광고를 표시할 수 있습니다\n\n특정 앱을 차단하면 그 앱은 더 이상 관심분야를 추정하지 않습니다. 차단을 해제하지 않는 한 앱은 목록에 다시 추가되지 않습니다. 차단된 앱에서 이미 추정한 관심분야는 삭제되지만 이와 관련된 일부 광고가 계속 표시될 수도 있습니다"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"차단한 앱"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"앱에서 추정한 관심분야 초기화"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"개인 정보 보호 샌드박스"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android의 광고 개인 정보 보호 베타는 앱에서 사용자가 좋아할 만한 광고를 표시할 수 있는 새로운 기능을 제공합니다. 이 새로운 기술은 기기 식별자를 사용하지 않습니다.\n\n앱은 사용자가 관심을 보일 만한 광고 종류를 추정하고 관심분야를 기기에 임시 저장합니다. 이렇게 하면 앱이 다른 개발자의 웹사이트/앱에서 이루어진 활동을 추적하지 않고도 관련성 있는 광고를 표시할 수 있습니다."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"차단한 앱이 없습니다."</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"차단한 관심분야가 없습니다."</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"취소"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"개인 정보 보호 샌드박스 사용을 중지하시겠습니까?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"생각이 바뀌었거나 Android의 광고 개인 정보 보호 베타에 관해 자세히 알아보고 싶다면 개인 정보 보호 설정으로 이동하세요"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"사용 중지"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> 관련 주제를 차단하시겠습니까?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"관심분야가 차단되며 사용자가 다시 추가하지 않는 한 목록에 다시 표시되지 않습니다. 관련된 일부 광고는 계속 표시될 수 있습니다."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"차단"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> 앱이 차단 해제됨"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android에서 이 관심분야를 목록에 다시 추가할 수 있지만 바로 표시되지는 않을 수도 있습니다."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"확인"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"모든 관심분야를 재설정하시겠습니까?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"목록을 삭제하고 앞으로는 새로운 관심분야를 추정합니다. 삭제된 관심분야의 광고가 일부 계속 표시될 수 있습니다."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"재설정"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> 앱을 차단하시겠습니까?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"이 앱은 개인 정보 보호 샌드박스의 관심분야를 추정하지 않으며 사용자가 앱 차단을 해제하지 않는 한 목록에 다시 추가되지 않습니다.\n\n앱에서 이미 추정한 관심분야는 삭제되지만 관련된 일부 광고는 계속 표시될 수 있습니다."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> 앱이 차단 해제됨"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"앱에서 사용자의 관심분야를 다시 추정할 수 있지만 관심분야가 목록에 바로 표시되지는 않을 수도 있습니다. 관련된 광고가 표시되기까지 시간이 걸릴 수 있습니다."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"앱에서 생성한 관심분야를 재설정하시겠습니까?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"목록의 앱에서 추정한 관심분야가 개인 정보 보호 샌드박스에서 삭제되고 앞으로는 앱에서 관심분야를 새로 추정합니다. 관련된 일부 광고는 계속 표시될 수 있습니다."</string>
<string name="topic10001" msgid="1636806320891333775">"예술 및 엔터테인먼트"</string>
<string name="topic10002" msgid="1226367977754287428">"연기 및 연극"</string>
<string name="topic10003" msgid="6949890838957814881">"일본 애니메이션 및 망가"</string>
diff --git a/adservices/apk/res/values-ky/strings.xml b/adservices/apk/res/values-ky/strings.xml
index 19b8c35fa..8568e9f8b 100644
--- a/adservices/apk/res/values-ky/strings.xml
+++ b/adservices/apk/res/values-ky/strings.xml
@@ -36,7 +36,7 @@
<string name="permdesc_modifyAdServicesState" msgid="2485849128464307209">"Колдонмого AdService кызматынын иштетүү абалын өзгөртүү API\'cин пайдаланууга мүмкүнчүлүк берет."</string>
<string name="permlab_accessAdServicesState" msgid="482058883975394398">"AdService кызматынын иштетүү абалынын API\'сине кирүү"</string>
<string name="permdesc_accessAdServicesState" msgid="1165015604618567202">"Колдонмого AdService кызматынын иштетүү абалынын API\'син пайдаланууга мүмкүнчүлүк берет."</string>
- <string name="app_label" msgid="5985320129629013968">"Android Тутуму"</string>
+ <string name="app_label" msgid="5985320129629013968">"Android системасы"</string>
<string name="notificationUI_notification_title_eu" msgid="6328781145332100489">"Android\'деги жарнамалардын купуялыгынын бета версиясына кошулуңуз"</string>
<string name="notificationUI_notification_content_eu" msgid="4119222762583017840">"Android сизге жарнамаларды көрсөтүүнүн мындан да купуя жолдорун изилдеп жатат"</string>
<string name="notificationUI_notification_cta_eu" msgid="1294908454704864207">"Кененирээк"</string>
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Кантип катышуу керек"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Бета версияны күйгүзсөңүз, колдонмолор сизге жарнамаларды көрсөтүүнүн жаңы купуя жолдорун сынай алат. Бул бета версияны каалаган убакта купуялык параметрлеринен өчүрө аласыз."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Жок, рахмат"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ооба, кошулам"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Күйгүзүү"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Дагы"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Катышканыңыз үчүн рахмат"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Android\'деги жарнамалардын купуялыгынын бета версиясына катышып жатасыз. Түзмөгүңүздө Privacy Sandbox өчүрүлдү.\n\nКеңири маалымат алып же каалаган убакта бета версияны купуялык параметрлеринен өчүрө аласыз."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Кошулбоону тандадыңыз"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Учурда көрсөтүү үчүн кызыккан нерселер жок"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android\'деги жарнамалардын купуялыгынын бета версиясы колдонмолор сизге кызыктуу жарнамаларды көрсөтүү үчүн пайдалана турган жаңы функцияларды сунуштайт. Бул технологиялар түзмөктүн идентификаторлорун колдонбойт.\n\nAndroid сизге кызыктуу жарнамаларды аныктап, бул кызыккан нерселериңизди түзмөгүңүзгө убактылуу сактай алат. Ал аркылуу колдонмолор сизге ылайыктуу жарнамаларды көрсөтөт, бирок башка иштеп чыгуучулардын колдонмолорунда жана вебсайттарында жасаган аракеттериңизге көз сала албайт."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Бөгөттөлгөн кызыккан нерселериңиз жок"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Колдонмолор кызыккан нерселериңизди аныктап, аларды Android\'ге убактылуу сактай алат. Кийинчерээк башка колдонмо сизге бул кызыккан нерселердин негизинде жарнама көрсөтө алат.\n\nЭгер колдонмону бөгөттөсөңүз, ал мындан кийин кызыккан нерселериңизди аныктабай калат. Аны бөгөттөн чыгармайынча, ал ушул колдонмолордун тизмесине кошулбайт. Колдонмо буга чейин аныктаган кызыккан нерселер өчүрүлөт, бирок ылайыктуу жарнамаларды дагы деле көрүшүңүз мүмкүн."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Сиз бөгөттөгөн колдонмолор"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Колдонмолор аныктаган кызыккан нерселерди баштапкы абалга келтирүү"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android\'деги жарнамалардын купуялыгынын бета версиясы колдонмолор сизге кызыктуу жарнамаларды көрсөтүү үчүн пайдалана турган жаңы функцияларды сунуштайт. Бул технологиялар түзмөктүн идентификаторлорун колдонбойт.\n\nКолдонмолор сизге кызыктуу жарнамаларды аныктап, бул кызыккан нерселериңизди түзмөгүңүзгө убактылуу сактай алат. Ал аркылуу колдонмолор сизге ылайыктуу жарнамаларды көрсөтөт, бирок башка иштеп чыгуучулардын колдонмолорунда жана вебсайттарында жасаган аракеттериңизге көз сала албайт."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Бөгөттөлгөн колдонмолоруңуз жок"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Бөгөттөлгөн кызыккан нерселериңиз жок"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Токтотуу"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Privacy Sandbox өчүрүлсүнбү?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Эгер аларды кайра иштеткиңиз келсе же \"Android’де көрүнгөн жарнамалардын купуялыгы\" программасын сыноо жөнүндө кененирээк маалымат алгыңыз келсе, купуялык параметрлерине өтүңүз."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Өчүрүү"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> бөгөттөлсүнбү?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Бул кызыккан нерсе бөгөттөлбөй, тизмеңизде кайра көрүнүшү үчүн аны кайра кошушуңуз керек. Бирок айрым окшош жарнамаларды көрө бересиз."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Бөгөттөө"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> бөгөттөн чыгарылды"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android бул кызыккан нерсени тизмеге кошо алат, бирок ал дароо көрүнбөйт"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Жарайт"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Бардык кызыккан нерселериңиз баштапкы абалга келтирилсинби?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Тизме тазаланып, жаңы кызыккан нерселериңиз божомолдонот. Тазаланган тизмедеги кызыккан нерселериңиз боюнча жарнамаларды дагы деле көрүшүңүз мүмкүн."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Баштапкы абалга келтирүү"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> бөгөттөлсүнбү?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Бул колдонмо Privacy Sandbox технологиялары үчүн кызыккан нерселериңизди божомолдоп, тизмеңизде көрүнүшү үчүн аны бөгөттөн чыгарышыңыз керек.\n\nБуга чейин аныкталган кызыгууларыңыз өчүрүлөт, бирок алардын негизинде тандалган жарнамалар көрүнө берет."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> бөгөттөн чыгарылды"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Бул колдонмо кызыккан нерселериңизди кайра божомолдой алат, бирок алар тизмеде дароо көрүнбөйт. Ушул нерселердин негизинде тандалган жарнамалар бир аздан кийин көрүнө баштайт."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Колдонмолор чогулткан кызыккан нерселер баштапкы абалга келтирилсинби?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Тизмедеги колдонмолор божомолдогон кызыккан нерселериңиз Privacy Sandbox\'тон өчүрүлүп, колдонмолор жаңы кызыккан нерселериңизди божомолдойт. Бирок айрым окшош жарнамаларды көрө бересиз."</string>
<string name="topic10001" msgid="1636806320891333775">"Искусство жана көңүл ачуу"</string>
<string name="topic10002" msgid="1226367977754287428">"Актёрдук жана театр"</string>
<string name="topic10003" msgid="6949890838957814881">"Аниме жана Манга"</string>
diff --git a/adservices/apk/res/values-lo/strings.xml b/adservices/apk/res/values-lo/strings.xml
index c1900308e..45e19665e 100644
--- a/adservices/apk/res/values-lo/strings.xml
+++ b/adservices/apk/res/values-lo/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"ວິທີການເຂົ້າຮ່ວມ"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"ການເປີດໃຊ້ເວີຊັນເບຕ້າຈະອະນຸຍາດໃຫ້ແອັບຕ່າງໆທົດສອບການສະແດງໂຄສະນາແບບໃໝ່ທີ່ມີຄວາມເປັນສ່ວນຕົວຫຼາຍຂຶ້ນເຫຼົ່ານີ້ໃຫ້ທ່ານໄດ້. ທ່ານສາມາດປິດເວີຊັນເບຕ້າຕອນໃດກໍໄດ້ຢູ່ການຕັ້ງຄ່າຄວາມເປັນສ່ວນຕົວຂອງທ່ານ."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"ບໍ່, ຂອບໃຈ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ແມ່ນ, ຂ້ອຍຈະເຂົ້າຮ່ວມ"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ເປີດໃຊ້"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"ເພີ່ມເຕີມ"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"ຂໍຂອບໃຈທີ່ທ່ານເຂົ້າຮ່ວມ"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"ທ່ານເປັນສ່ວນໜຶ່ງຂອງເວີຊັນເບຕ້າຂອງຄວາມເປັນສ່ວນຕົວສຳລັບໂຄສະນາໃນ Android. Privacy Sandbox ຖືກເປີດໄວ້ສຳລັບອຸປະກອນຂອງທ່ານ.\n\nທ່ານສາມາດສຶກສາເພີ່ມເຕີມ ຫຼື ປິດໃຊ້ເວີຊັນເບຕ້າຕອນໃດກໍໄດ້ໃນການຕັ້ງຄ່າຄວາມເປັນສ່ວນຕົວຂອງທ່ານ."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"ທ່ານໄດ້ເລືອກທີ່ຈະບໍ່ເຂົ້າຮ່ວມ"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ບໍ່ມີຄວາມໃຈເພື່ອສະແດງຂຶ້ນໃນຕອນນີ້"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"ເວີຊັນເບຕ້າຂອງຄວາມເປັນສ່ວນຕົວສຳລັບໂຄສະນາໃນ Android ມີຄຸນສົມບັດໃໝ່ໆທີ່ແອັບສາມາດໃຊ້ເພື່ອສະແດງໂຄສະນາທີ່ທ່ານອາດມັກໃຫ້ແກ່ທ່ານ. ເທັກໂນໂລຢີເຫຼົ່ານີ້ບໍ່ໄດ້ໃຊ້ຕົວລະບຸອຸປະກອນ.\n\nAndroid ສາມາດຄາດການປະເພດຂອງໂຄສະນາທີ່ທ່ານອາດສົນໃຈ ແລະ ບັນທຶກຄວາມສົນໃຈເຫຼົ່ານີ້ໄວ້ຢູ່ອຸປະກອນຂອງທ່ານຊົ່ວຄາວ. ສິ່ງນີ້ຊ່ວຍໃຫ້ແອັບຕ່າງໆສະແດງໂຄສະນາທີ່ກ່ຽວຂ້ອງໃຫ້ແກ່ທ່ານໂດຍທີ່ບໍ່ຕ້ອງຕິດຕາມການເຄື່ອນໄຫວຂອງທ່ານຢູ່ເວັບໄຊ ແລະ ແອັບຈາກນັກພັດທະນາອື່ນໆ."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ທ່ານບໍ່ມີຄວາມສົນໃຈໃດຖືກບລັອກ"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"ແອັບສາມາດຄາດການຄວາມສົນໃຈຂອງທ່ານ ແລະ ບັນທຶກສິ່ງເຫຼົ່ານີ້ໄວ້ກັບ Android ແບບຊົ່ວຄາວໄດ້. ຈາກນັ້ນ, ແອັບອື່ນສາມາດສະແດງໂຄສະນາໂດຍອີງຕາມຄວາມສົນໃຈເຫຼົ່ານີ້ໃຫ້ທ່ານໄດ້.\n\nຫາກທ່ານບລັອກແອັບໃດໜຶ່ງ, ມັນຈະບໍ່ຄາດການຄວາມສົນໃຈອີກຕໍ່ໄປ. ມັນຈະບໍ່ຖືກເພີ່ມໃສ່ລາຍການນີ້ຂອງແອັບອີກຄັ້ງ ເວັ້ນແຕ່ວ່າທ່ານຍົກເລີກການບລັອກມັນ. ຄວາມສົນໃຈທີ່ແອັບຄາດການໄວ້ແລ້ວຈະຖືກລຶບ, ແຕ່ທ່ານອາດຍັງເຫັນໂຄສະນາບາງຢ່າງທີ່ກ່ຽວຂ້ອງຢູ່."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"ແອັບທີ່ທ່ານໄດ້ບລັອກ"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ຣີເຊັດຄວາມສົນໃຈທີ່ຄາດການໂດຍແອັບ"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"ເວີຊັນເບຕ້າຂອງຄວາມເປັນສ່ວນຕົວສຳລັບໂຄສະນາໃນ Android ມີຄຸນສົມບັດໃໝ່ໆທີ່ແອັບສາມາດໃຊ້ເພື່ອສະແດງໂຄສະນາທີ່ທ່ານອາດມັກໃຫ້ແກ່ທ່ານ. ເທັກໂນໂລຢີເຫຼົ່ານີ້ບໍ່ໄດ້ໃຊ້ຕົວລະບຸອຸປະກອນ.\n\nແອັບສາມາດຄາດການປະເພດຂອງໂຄສະນາທີ່ທ່ານອາດສົນໃຈ ແລະ ບັນທຶກຄວາມສົນໃຈເຫຼົ່ານີ້ໄວ້ຢູ່ອຸປະກອນຂອງທ່ານຊົ່ວຄາວ. ສິ່ງນີ້ຊ່ວຍໃຫ້ແອັບຕ່າງໆສະແດງໂຄສະນາທີ່ກ່ຽວຂ້ອງໃຫ້ແກ່ທ່ານໂດຍທີ່ບໍ່ຕ້ອງຕິດຕາມການເຄື່ອນໄຫວຂອງທ່ານຢູ່ເວັບໄຊ ແລະ ແອັບຈາກນັກພັດທະນາອື່ນໆ."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ທ່ານບໍ່ມີແອັບໃດຖືກບລັອກ"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ທ່ານບໍ່ມີຄວາມສົນໃຈໃດຖືກບລັອກ"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"ຍົກເລີກ"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"ປິດ Privacy Sandbox ໄວ້ບໍ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"ຫາກທ່ານປ່ຽນໃຈ ຫຼື ຕ້ອງການສຶກສາເພີ່ມເຕີມກ່ຽວກັບຄວາມເປັນສ່ວນຕົວໂຄສະນາຂອງ Android (ເບຕ້າ), ໃຫ້ເຂົ້າໄປການຕັ້ງຄ່າຄວາມເປັນສ່ວນຕົວຂອງທ່ານ"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ປິດໄວ້"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"ບລັອກ <xliff:g id="TOPIC">%1$s</xliff:g> ບໍ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ຄວາມສົນໃຈນີ້ຈະຖືກບລັອກ ແລະ ຈະບໍ່ຖືກເພີ່ມໄປໃສ່ລາຍຊື່ຂອງທ່ານຄືນໃໝ່, ເວັ້ນແຕ່ທ່ານຈະແອດມັນກັບໄປ. ທ່ານອາດຈະຍັງເຫັນໂຄສະນາທີ່ກ່ຽວຂ້ອງບາງຢ່າງ."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ບລັອກ"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"ປົດບລັອກ <xliff:g id="TOPIC">%1$s</xliff:g> ແລ້ວ"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ອາດເພີ່ມຄວາມສົນໃຈນີ້ໄປໃສ່ລາຍຊື່ຂອງທ່ານຄືນໃໝ່, ແຕ່ມັນອາດບໍ່ປາກົດໃນທັນທີ"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ຕົກລົງ"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"ຣີເຊັດຄວາມສົນໃຈທັງໝົດຂອງທ່ານບໍ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"ລາຍຊື່ຂອງທ່ານຈະຖືກລຶບລ້າງ ແລະ ຈະມີການປະເມີນຄວາມສົນໃຈໃໝ່ໃນອະນາຄົດ. ທ່ານອາດຍັງຄົງເຫັນໂຄສະນາບາງຢ່າງທີ່ກ່ຽວຂ້ອງກັບຄວາມສົນໃຈທີ່ຖືກລຶບລ້າງໄປແລ້ວຢູ່."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"ຣີເຊັດ"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"ບລັອກ <xliff:g id="APP">%1$s</xliff:g> ບໍ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ແອັບນີ້ຈະບໍ່ຄາດການຄວາມສົນໃຈສຳລັບ Privacy Sandbox ແລະ ຈະບໍ່ຖືກເພີ່ມໃສ່ລາຍຊື່ຂອງທ່ານຄືນໃໝ່, ເວັ້ນແຕ່ທ່ານຈະປົດບລັອກມັນ.\n\nຄວາມສົນໃຈທີ່ຖືກຄາດການໂດຍແອັບນີ້ໄປກ່ອນແລ້ວຈະຖືກລຶບອອກ, ແຕ່ທ່ານອາດຍັງຄົງເຫັນບາງແອັບທີ່ກ່ຽວຂ້ອງຢູ່."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"ປົດບລັອກ <xliff:g id="APP">%1$s</xliff:g> ແລ້ວ"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ແອັບນີ້ສາມາດຄາດການຄວາມສົນໃຈໃຫ້ທ່ານຄືນໃໝ່ໄດ້, ແຕ່ມັນອາດບໍ່ປາກົດຢູ່ລາຍຊື່ຂອງທ່ານໃນທັນທີ. ມັນອາດໃຊ້ເວລາໄລຍະໜຶ່ງເພື່ອໃຫ້ທ່ານເຫັນໂຄສະນາທີ່ກ່ຽວຂ້ອງ."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"ຣີເຊັດຄວາມສົນໃຈທີ່ແອັບສ້າງຂຶ້ນບໍ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"ຄວາມສົນໃຈທີ່ປະເມີນໂດຍແອັບຢູ່ລາຍຊື່ຂອງທ່ານຈະຖືກລຶບອອກຈາກ Privacy Sandbox ແລະ ແອັບຈະປະເມີນຄວາມສົນໃຈໃໝ່ໃນອະນາຄົດ. ທ່ານອາດຈະຍັງຄົງເຫັນໂຄສະນາທີ່ກ່ຽວຂ້ອງບາງຢ່າງຢູ່."</string>
<string name="topic10001" msgid="1636806320891333775">"ສິນລະປະ ແລະ ການບັນເທີງ"</string>
<string name="topic10002" msgid="1226367977754287428">"ການສະແດງ ແລະ ໂຮງຮູບເງົາ"</string>
<string name="topic10003" msgid="6949890838957814881">"ອະນິເມະ ແລະ ມັງງະ"</string>
diff --git a/adservices/apk/res/values-lt/strings.xml b/adservices/apk/res/values-lt/strings.xml
index 6f55d8d3e..7766c46c2 100644
--- a/adservices/apk/res/values-lt/strings.xml
+++ b/adservices/apk/res/values-lt/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Kaip dalyvauti"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Įjungus beta versiją, programos gali išbandyti šiuos naujus privatesnius būdus skelbimams rodyti. Galite bet kuriuo metu išjungti beta versiją privatumo nustatymuose."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ne, ačiū"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Taip, prisijungsiu"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Įjungti"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Daugiau"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Dėkojame, kad dalyvaujate"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Dalyvaujate „Android“ su skelbimais susijusio privatumo beta versijos programoje. Privatumo „sandbox“ (smėlio dėžė) jūsų įrenginyje įjungta.\n\nGalite sužinoti daugiau arba bet kuriuo metu išjungti beta versiją privatumo nustatymuose."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Pasirinkote nedalyvauti"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Šiuo metu nėra rodytinų pomėgių"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privatumo „sandbox“ (smėlio dėžė)"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"„Android“ su skelbimais susijusio privatumo beta versijos programa teikia naujų funkcijų, kurias programos gali naudoti, kad rodytų skelbimus, kurie jums gali patikti. Šios technologijos nenaudoja įrenginio identifikatorių.\n\n„Android“ gali nustatyti, kokio tipo skelbimai jus gali dominti, ir laikinai išsaugoti šiuos pomėgius įrenginyje. Tai leidžia programoms rodyti aktualius skelbimus, nestebint jūsų veiklos svetainėse ir kitų kūrėjų programose."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nėra užblokuotų pomėgių"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Programos gali nustatyti jūsų pomėgius ir laikinai juos išsaugoti sistemoje „Android“. Vėliau kita programa gali parodyti su šiais pomėgiais susijusį skelbimą.\n\nJei užblokuosite programą, ji nebenustatys pomėgių. Ji nebebus pridėta į šį programų sąrašą, nebent ją atblokuosite. Pomėgiai, kuriuos programa jau nustatė, bus ištrinti, bet vis tiek gali būti rodomi kai kurie susiję skelbimai."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Jūsų užblokuotos programos"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Iš naujo nustatyti programų nustatytus pomėgius"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privatumo „sandbox“ (smėlio dėžė)"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"„Android“ su skelbimais susijusio privatumo beta versijos programa teikia naujų funkcijų, kurias programos gali naudoti, kad rodytų skelbimus, kurie jums gali patikti. Šios technologijos nenaudoja įrenginio identifikatorių.\n\nProgramos gali nustatyti, kokio tipo skelbimai jus gali dominti, ir laikinai išsaugoti šiuos pomėgius įrenginyje. Tai leidžia programoms rodyti aktualius skelbimus, nestebint jūsų veiklos svetainėse ir kitų kūrėjų programose."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nėra užblokuotų programų"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nėra užblokuotų pomėgių"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Atšaukti"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Išjungti privatumo „sandbox“ (smėlio dėžę)?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Jei apsigalvotumėte ar norėtumėte sužinoti daugiau apie „Android“ skelbimų privatumo beta versiją, eikite į privatumo nustatymus"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Išjungti"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Blokuoti „<xliff:g id="TOPIC">%1$s</xliff:g>“?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Šis pomėgis bus užblokuotas ir nebus vėl pridėtas prie jūsų sąrašo, nebent patys vėl pridėsite. Vis tiek gali būti rodomi kai kurie susiję skelbimai."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokuoti"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"„<xliff:g id="TOPIC">%1$s</xliff:g>“ blokavimas panaikintas"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"„Android“ gali vėl pridėti šį pomėgį prie jūsų sąrašo, bet jis gali būti pateiktas ne iš karto."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Gerai"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Iš naujo nustatyti visus pomėgius?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Sąrašas bus išvalytas ir toliau bus nustatomi nauji pomėgiai. Vis tiek galite matyti kelis su išvalytais pomėgiais susijusius skelbimus."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Nustatyti iš naujo"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Blokuoti „<xliff:g id="APP">%1$s</xliff:g>“?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ši programa nevertins jūsų privatumo „sandbox“ (smėlio dėžės) pomėgių ir nebus vėl pridėta prie jūsų sąrašo, nebent panaikinsite jos blokavimą.\n\nŠios programos jau įvertinti pomėgiai bus ištrinti, bet vis tiek gali būti rodoma susijusių skelbimų."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"„<xliff:g id="APP">%1$s</xliff:g>“ blokavimas panaikintas"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Ši programa gali vėl įvertinti jūsų pomėgius, bet jūsų sąraše gali būti pateikta ne iš karto. Gali šiek tiek užtrukti, kol bus rodomi susiję skelbimai."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Iš naujo nustatyti programų sugeneruotus pomėgius?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Į sąrašą įtrauktų programų nustatyti pomėgiai bus ištrinti iš Privatumo „sandbox“ (smėlio dėžės) ir programos toliau nustatys naujus pomėgius. Vis tiek gali būti rodomi kai kurie susiję skelbimai."</string>
<string name="topic10001" msgid="1636806320891333775">"Menas ir pramogos"</string>
<string name="topic10002" msgid="1226367977754287428">"Vaidyba ir teatras"</string>
<string name="topic10003" msgid="6949890838957814881">"Animė ir manga"</string>
diff --git a/adservices/apk/res/values-lv/strings.xml b/adservices/apk/res/values-lv/strings.xml
index 7b2e7248b..59d53670b 100644
--- a/adservices/apk/res/values-lv/strings.xml
+++ b/adservices/apk/res/values-lv/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Kā piedalīties"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ieslēdzot beta versiju, lietotnēm tiek atļauts testēt šos jaunos reklāmu rādīšanas veidus, kas nodrošina lielāku konfidencialitāti. Savos konfidencialitātes iestatījumos varat jebkurā laikā izslēgt beta versiju."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nē, paldies"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Jā, vēlos pievienoties"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Ieslēgt"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Vairāk"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Pateicamies par dalību!"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Jūs piedalāties Android reklāmu konfidencialitātes beta programmā. Jūsu ierīcē ir ieslēgta konfidencialitātes smilškaste.\n\nKonfidencialitātes iestatījumos jebkurā laikā varat uzzināt vairāk vai izslēgt beta versiju."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Izvēlējāties nepiedalīties"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Pašlaik nav parādāmu interešu"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Konfidencialitātes smilškaste"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android reklāmu konfidencialitātes beta programma ietver jaunas funkcijas, ko lietotnes var izmantot, lai rādītu reklāmas, kas jums varētu interesēt. Šīs tehnoloģijas neizmanto ierīču identifikatorus.\n\nOperētājsistēma Android var aptuveni noteikt, kādas reklāmas jūs varētu interesēt, un šīs intereses var īslaicīgi saglabāt jūsu ierīcē. Tādējādi lietotnes var rādīt jums atbilstošas reklāmas, neizsekojot jūsu darbības tīmekļa vietnēs un citu izstrādātāju lietotnēs."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Jums nav bloķētu interešu"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Lietotnēs var tikt aptuveni noteiktas jūsu intereses, un tās īslaicīgi var tikt saglabātas Android ierīcē. Vēlāk cita lietotne var jums rādīt uz šīm interesēm balstītu reklāmu.\n\nJa kādu lietotni bloķēsiet, tā vairs nevarēs noteikt jūsu intereses. Tā netiks pievienota šim lietotņu sarakstam, līdz to neatbloķēsiet. Intereses, kas lietotnē jau tika noteiktas, tiks dzēstas, taču jums joprojām var tikt rādītas dažas atbilstošas reklāmas."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Bloķētās lietotnes"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Atiestatīt lietotnēs noteiktās intereses"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Konfidencialitātes smilškaste"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android reklāmu konfidencialitātes beta programma ietver jaunas funkcijas, ko lietotnes var izmantot, lai rādītu reklāmas, kas jums varētu interesēt. Šīs tehnoloģijas neizmanto ierīču identifikatorus.\n\nLietotnes var aptuveni noteikt, kādas reklāmas jūs varētu interesēt, un šīs intereses var īslaicīgi saglabāt jūsu ierīcē. Tādējādi lietotnes var rādīt jums atbilstošas reklāmas, neizsekojot jūsu darbības tīmekļa vietnēs un citu izstrādātāju lietotnēs."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nav bloķēta neviena lietotne"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Jums nav bloķētu interešu"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Atcelt"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Vai izslēgt konfidencialitātes smilškasti?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ja pārdomājat vai vēlaties uzzināt vairāk par Android reklāmu konfidencialitātes beta programmu, dodieties uz konfidencialitātes iestatījumiem."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Izslēgt"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Vai bloķēt tēmu “<xliff:g id="TOPIC">%1$s</xliff:g>”?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Šī interešu kategorija tiks bloķēta un netiks atkal pievienota jūsu sarakstam, ja vien pats to nedarīsiet. Iespējams, tik un tā redzēsiet dažas saistītas reklāmas."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloķēt"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Tēma “<xliff:g id="TOPIC">%1$s</xliff:g>” ir atbloķēta"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android var atkal pievienot šo interešu kategoriju sarakstam, taču, iespējams, tā netiks rādīta uzreiz."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Labi"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Vai atiestatīt visas intereses?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Jūsu saraksts tiks notīrīts, un ar laiku tiks noteiktas jaunas intereses Tomēr, iespējams, joprojām redzēsiet dažas ar notīrītajām interesēm saistītas reklāmas."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Atiestatīt"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Vai bloķēt lietotni <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Šī lietotne nenoteiks intereses konfidencialitātes smilškastei un netiks atkal pievienota jūsu sarakstam, kamēr to neatbloķēsiet.\n\nŠīs lietotnes līdz šim noteiktās intereses tiks dzēstas, taču, iespējams, tik un tā redzēsiet dažas saistītas reklāmas."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Lietotne <xliff:g id="APP">%1$s</xliff:g> ir atbloķēta"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Šī lietotne atkal var noteikt intereses, taču, iespējams, tā netiks uzreiz rādīta sarakstā. Var paiet kāds laiks, līdz tiks rādītas saistītas reklāmas."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Vai atiestatīt intereses, ko veidojušas lietotnes?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Pēc jūsu sarakstā iekļautajām lietotnēm noteiktās intereses tiks dzēstas no konfidencialitātes smilškastes, un lietotnēs ar laiku tiks noteiktas jaunas intereses. Tomēr, iespējams, joprojām redzēsiet dažas saistītas reklāmas."</string>
<string name="topic10001" msgid="1636806320891333775">"Māksla un izklaide"</string>
<string name="topic10002" msgid="1226367977754287428">"Aktiermāksla un teātris"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime un manga"</string>
diff --git a/adservices/apk/res/values-mk/strings.xml b/adservices/apk/res/values-mk/strings.xml
index 695565e0d..158ab9e0e 100644
--- a/adservices/apk/res/values-mk/strings.xml
+++ b/adservices/apk/res/values-mk/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Како да учествувате"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Вклучувањето на бета-верзијата им овозможува на апликациите да ги тестираат овие нови поприватни начини за прикажување реклами. Може да ја исклучите бета-верзијата во секое време во поставките за приватност."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Не, фала"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Да, ќе се придружам"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Вклучи"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Повеќе"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Благодариме за учеството"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Вие сте дел од бета-верзија за приватноста на рекламите на Android. Privacy Sandbox е вклучена за вашиот уред.\n\nМоже да дознаете повеќе или да ја исклучите бета-верзијата во секое време во вашите поставки за приватност."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Избравте да не учествувате"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Нема интереси за прикажување во моментов"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Бета-верзијата за приватноста на рекламите на Android обезбедува нови функции што апликациите може да ги користат за да ви прикажуваат реклами што можеби ќе ви се допаднат. Овие технологии не користат идентификатори на уреди.\n\nAndroid може да ги процени типовите реклами што можеби ве интересираат и привремено да ги зачува овие интереси на вашиот уред. Ова им овозможува на апликациите да ви прикажуваат релевантни реклами, без да ја следат вашата активност на веб-сајтови и апликации од други програмери."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Немате блокирани интереси"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Апликациите може да ги проценат вашите интереси и привремено да ги зачуваат со Android. Подоцна друга апликација може да ви прикаже реклама според овие интереси.\n\nАко блокирате апликација, таа веќе нема да проценува интереси. Нема да биде додадена на списоков со апликации повторно, освен ако не ја одблокирате. Интересите што апликацијата веќе ги проценила ќе се избришат, но можеби сѐ уште ќе ви се прикажуваат некои слични реклами."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Апликации што сте ги блокирале"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Ресетирајте ги интересите проценети од апликациите"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Бета-верзијата за приватноста на рекламите на Android обезбедува нови функции што апликациите може да ги користат за да ви прикажуваат реклами што можеби ќе ви се допаднат. Овие технологии не користат идентификатори на уреди.\n\nАпликациите може да ги проценат типовите реклами што можеби ве интересираат и привремено да ги зачуваат овие интереси на вашиот уред. Ова им овозможува на апликациите да ви прикажуваат релевантни реклами, без да ја следат вашата активност на веб-сајтови и апликации од други програмери."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Немате блокирани апликации"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Немате блокирани интереси"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Откажи"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Да се исклучи Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ако се премислите или сакате да дознаете повеќе за бета-верзијата за приватност на рекламите на Android, одете на поставките за приватност"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Исклучи"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Да се блокира <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Интересов ќе се блокира и нема да се додаде во вашиот список повторно, освен ако го вратите. Можеби сѐ уште ќе ви се прикажуваат некои слични реклами."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Блокирај"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> е одблокирана"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android може да го врати интересов во списокот, но можеби нема да се прикаже веднаш"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Во ред"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Да се ресетираат сите ваши интереси?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Списокот ќе се исчисти и ќе се проценуваат нови интереси отсега-натаму. Можеби сѐ уште ќе ви се прикажуваат некои реклами поврзани со исчистените интереси."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Ресетирај"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Да се блокира <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Апликацијава нема да ги процени интересите за Privacy Sandbox и нема да се додаде на вашиот список повторно, освен ако ја одблокирате.\n\nИнтересите што се веќе проценети од апликацијава ќе бидат избришани, но сепак може да ви се прикажуваат некои поврзани реклами."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> е одблокирана"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Апликацијава може да ги процени интересите за вас повторно, но можеби нема да се прикажат на списокот веднаш. Можеби ќе помине одредено време додека да ви се прикажат поврзани реклами."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Да се ресетираат интересите генерирани од апликации?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Интересите проценети од апликациите на вашиот список ќе се избришат од Privacy Sandbox и апликациите ќе проценуваат нови интереси отсега-натаму. Можеби сѐ уште ќе ви се прикажуваат некои слични реклами."</string>
<string name="topic10001" msgid="1636806320891333775">"Уметност и забава"</string>
<string name="topic10002" msgid="1226367977754287428">"Глума и театар"</string>
<string name="topic10003" msgid="6949890838957814881">"Аниме и манга"</string>
diff --git a/adservices/apk/res/values-ml/strings.xml b/adservices/apk/res/values-ml/strings.xml
index 7c7e153f9..2c1e7bafe 100644
--- a/adservices/apk/res/values-ml/strings.xml
+++ b/adservices/apk/res/values-ml/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"എങ്ങനെ പങ്കെടുക്കാം"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"ബീറ്റ ഓണാക്കുന്നത്, നിങ്ങളെ പരസ്യങ്ങൾ കാണിക്കുന്നതിന് പുതിയതും കൂടുതൽ സ്വകാര്യവുമായ ഈ വഴികൾ പരീക്ഷിക്കാൻ ആപ്പുകളെ അനുവദിക്കും. നിങ്ങളുടെ സ്വകാര്യതാ ക്രമീകരണത്തിൽ ഏതുസമയത്തും ബീറ്റ ഓഫാക്കാം."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"വേണ്ട"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ഉവ്വ്, ഞാൻ ചേരും"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ഓണാക്കുക"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"കൂടുതൽ"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"പങ്കെടുത്തതിന് നന്ദി"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"നിങ്ങൾ Android-ന്റെ പരസ്യ സ്വകാര്യതാ ബീറ്റയുടെ ഭാഗമാണ്. നിങ്ങളുടെ ഉപകരണത്തിൽ സ്വകാര്യതാ സാൻഡ്ബോക്‌സ് ഓണാക്കിയിരിക്കുന്നു.\n\nനിങ്ങളുടെ സ്വകാര്യതാ ക്രമീകരണത്തിൽ ഏതുസമയത്തും ബീറ്റയെക്കുറിച്ച് കൂടുതലറിയാനോ ബീറ്റ ഓഫാക്കാനോ കഴിയും."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"പങ്കെടുക്കേണ്ടതില്ലെന്ന് നിങ്ങൾ തിരഞ്ഞെടുത്തു"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ഇപ്പോൾ കാണിക്കാൻ താൽപ്പര്യങ്ങളൊന്നുമില്ല"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"സ്വകാര്യതാ സാൻഡ്ബോക്‌സ്"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"നിങ്ങൾക്ക് ഇഷ്ടപ്പെടാനിടയുള്ള പരസ്യങ്ങൾ കാണിക്കാൻ ആപ്പുകൾക്ക് ഉപയോഗിക്കാനാകുന്ന പുതിയ ഫീച്ചറുകൾ Android-ന്റെ പരസ്യ സ്വകാര്യതാ ബീറ്റ നൽകുന്നു. ഈ സാങ്കേതികവിദ്യകൾ ഉപകരണ ഐഡന്റിഫയറുകൾ ഉപയോഗിക്കുന്നില്ല.\n\nAndroid-ന് നിങ്ങൾക്ക് താൽപ്പര്യമുണ്ടാകാനിടയുള്ള പരസ്യങ്ങൾ കണക്കാക്കാനും നിങ്ങളുടെ ഉപകരണത്തിൽ താൽപ്പര്യങ്ങൾ താൽക്കാലികമായി സംരക്ഷിക്കാനും കഴിയും. മറ്റ് ഡെവലപ്പർമാരിൽ നിന്നുള്ള വെബ്‌സൈറ്റുകളിലും ആപ്പുകളിലും ഉടനീളമുള്ള നിങ്ങളുടെ ആക്റ്റിവിറ്റി ട്രാക്ക് ചെയ്യാതെ തന്നെ, പ്രസക്തമായ പരസ്യങ്ങൾ നിങ്ങളെ കാണിക്കാൻ ഇത് ആപ്പുകളെ അനുവദിക്കുന്നു."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"നിങ്ങൾക്ക് ബ്ലോക്ക് ചെയ്തിരിക്കുന്ന താൽപ്പര്യങ്ങളൊന്നുമില്ല"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"ആപ്പുകൾക്ക് നിങ്ങളുടെ താൽപ്പര്യങ്ങൾ കണക്കാക്കാനും Android-ൽ അവ താൽക്കാലികമായി സംരക്ഷിക്കാനും കഴിയും. പിന്നീട്, മറ്റൊരു ആപ്പിന് ഈ താൽപ്പര്യങ്ങളെ അടിസ്ഥാനമാക്കി നിങ്ങളെ പരസ്യം കാണിക്കാനാകും.\n\nനിങ്ങൾ ഒരു ആപ്പ് ബ്ലോക്ക് ചെയ്യുകയാണെങ്കിൽ, അത് തുടർന്ന് താൽപ്പര്യം കണക്കാക്കില്ല. നിങ്ങൾ അൺബ്ലോക്ക് ചെയ്യുന്നത് വരെ അത് വീണ്ടും ഈ ആപ്പുകളുടെ ലിസ്റ്റിലേക്ക് ചേർക്കില്ല. ആപ്പ് ഇതിനകം കണക്കാക്കിയ താൽപ്പര്യങ്ങൾ ഇല്ലാതാക്കും, എന്നാൽ നിങ്ങൾ തുടർന്നും അനുബന്ധ പരസ്യങ്ങൾ കണ്ടേക്കാം."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"നിങ്ങൾ ബ്ലോക്ക് ചെയ്‌ത ആപ്പുകൾ"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ആപ്പുകൾ കണക്കാക്കിയ താൽപ്പര്യങ്ങൾ റീസെറ്റ് ചെയ്യുക"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"സ്വകാര്യതാ സാൻഡ്ബോക്‌സ്"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"നിങ്ങൾക്ക് ഇഷ്ടപ്പെടാനിടയുള്ള പരസ്യങ്ങൾ കാണിക്കാൻ ആപ്പുകൾക്ക് ഉപയോഗിക്കാനാകുന്ന പുതിയ ഫീച്ചറുകൾ Android-ന്റെ പരസ്യ സ്വകാര്യതാ ബീറ്റ നൽകുന്നു. ഈ സാങ്കേതികവിദ്യകൾ ഉപകരണ ഐഡന്റിഫയറുകൾ ഉപയോഗിക്കുന്നില്ല.\n\nആപ്പുകൾക്ക് നിങ്ങൾക്ക് താൽപ്പര്യമുണ്ടാകാനിടയുള്ള പരസ്യങ്ങൾ കണക്കാക്കാനും നിങ്ങളുടെ ഉപകരണത്തിൽ താൽപ്പര്യങ്ങൾ താൽക്കാലികമായി സംരക്ഷിക്കാനും കഴിയും. മറ്റ് ഡെവലപ്പർമാരിൽ നിന്നുള്ള വെബ്‌സൈറ്റുകളിലും ആപ്പുകളിലും ഉടനീളമുള്ള നിങ്ങളുടെ ആക്റ്റിവിറ്റി ട്രാക്ക് ചെയ്യാതെ തന്നെ, പ്രസക്തമായ പരസ്യങ്ങൾ നിങ്ങളെ കാണിക്കാൻ ഇത് ആപ്പുകളെ അനുവദിക്കുന്നു."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"നിങ്ങൾക്ക് ബ്ലോക്ക് ചെയ്‌ത ആപ്പുകളൊന്നുമില്ല"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"നിങ്ങൾക്ക് ബ്ലോക്ക് ചെയ്തിരിക്കുന്ന താൽപ്പര്യങ്ങളൊന്നുമില്ല"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"റദ്ദാക്കുക"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"സ്വകാര്യതാ സാൻഡ്ബോക്‌സ് ഓഫാക്കണോ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"നിങ്ങൾ തീരുമാനം മാറ്റുകയാണെങ്കിലോ Android പരസ്യ സ്വകാര്യതാ ബീറ്റയെ കുറിച്ച് കൂടുതലറിയണമെന്നുണ്ടെങ്കിലോ നിങ്ങളുടെ സ്വകാര്യതാ ക്രമീകരണത്തിലേക്ക് പോകുക"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ഓഫാക്കുക"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> ബ്ലോക്ക് ചെയ്യണോ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ഈ താൽപ്പര്യം ബ്ലോക്ക് ചെയ്യും, നിങ്ങൾ അത് വീണ്ടും ചേർക്കുന്നില്ലെങ്കിൽ നിങ്ങളുടെ ലിസ്‌റ്റിലേക്ക് ചേർക്കുകയുമില്ല. നിങ്ങൾ തുടർന്നും ചില അനുബന്ധ പരസ്യങ്ങൾ കണ്ടേക്കാം."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ബ്ലോക്ക് ചെയ്യുക"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> അൺബ്ലോക്ക് ചെയ്‌തു"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ഈ താൽപ്പര്യം നിങ്ങളുടെ ലിസ്റ്റിലേക്ക് വീണ്ടും ചേർത്തേക്കാം, പക്ഷേ അത് ഉടനടി ദൃശ്യമാകണമെന്നില്ല"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ശരി"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"നിങ്ങളുടെ താൽപ്പര്യങ്ങളെല്ലാം റീസെറ്റ് ചെയ്യണോ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"നിങ്ങളുടെ ലിസ്റ്റ് മായ്ക്കും, ഭാവിയിൽ പുതിയ താൽപ്പര്യങ്ങൾ കണക്കാക്കും. മായ്ച്ച താൽപ്പര്യങ്ങളുമായി ബന്ധപ്പെട്ട ചില പരസ്യങ്ങൾ നിങ്ങൾ തുടർന്നും കണ്ടേക്കാം."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"റീസെറ്റ് ചെയ്യുക"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> ബ്ലോക്ക് ചെയ്യണോ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ഈ ആപ്പ് ഇനി സ്വകാര്യതാ സാൻഡ്‌ബോക്‌സിനായി താൽപ്പര്യം നിർണ്ണയിക്കില്ല, നിങ്ങൾ ഇത് അൺബ്ലോക്ക് ചെയ്യുന്നില്ലെങ്കിൽ വീണ്ടും നിങ്ങളുടെ ലിസ്റ്റിലേക്ക് ചേർക്കുകയുമില്ല.\n\nഈ ആപ്പ് ഇതിനകം നിർണ്ണയിച്ച താൽപ്പര്യങ്ങൾ ഇല്ലാതാക്കും, എന്നാൽ നിങ്ങൾ തുടർന്നും ചില അനുബന്ധ പരസ്യങ്ങൾ കണ്ടേക്കാം."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> അൺബ്ലോക്ക് ചെയ്‌തു"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ഈ ആപ്പിന് നിങ്ങളുടെ താൽപ്പര്യങ്ങൾ വീണ്ടും നിർണ്ണയിക്കാൻ കഴിയും, എന്നാൽ ഇത് നിങ്ങളുടെ ലിസ്റ്റിൽ ഉടനടി ദൃശ്യമായേക്കില്ല. അനുബന്ധ പരസ്യങ്ങൾ കാണാൻ അൽപ്പസമയം എടുത്തേക്കാം."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"ആപ്പുകൾ സൃഷ്‌ടിച്ച താൽപ്പര്യങ്ങൾ റീസെറ്റ് ചെയ്യണോ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"നിങ്ങളുടെ ലിസ്റ്റിലുള്ള ആപ്പുകൾ കണക്കാക്കിയ താൽപ്പര്യങ്ങൾ സ്വകാര്യതാ സാൻഡ്ബോക്‌സിൽ നിന്ന് ഇല്ലാതാക്കും, ഭാവിയിൽ ആപ്പുകൾ പുതിയ താൽപ്പര്യങ്ങൾ കണക്കാക്കും. നിങ്ങൾ തുടർന്നും ചില അനുബന്ധ പരസ്യങ്ങൾ കണ്ടേക്കാം."</string>
<string name="topic10001" msgid="1636806320891333775">"കലയും വിനോദവും"</string>
<string name="topic10002" msgid="1226367977754287428">"അഭിനയവും തീയറ്ററും"</string>
<string name="topic10003" msgid="6949890838957814881">"ആനിമേയും മാങ്കയും"</string>
diff --git a/adservices/apk/res/values-mn/strings.xml b/adservices/apk/res/values-mn/strings.xml
index 6d6bed747..90087cca5 100644
--- a/adservices/apk/res/values-mn/strings.xml
+++ b/adservices/apk/res/values-mn/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Хэрхэн оролцох вэ?"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Бета хувилбарыг асааснаар аппуудад танд зар харуулах эдгээр шинэ бөгөөд илүү хувийн аргыг турших боломж олгоно. Та нууцлалын тохиргоо хэсэгт бета хувилбарыг хүссэн үедээ унтраах болно."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Үгүй, баярлалаа"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Тийм, би нэгдэнэ"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Асаах"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Бусад"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Оролцсонд баярлалаа"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Та Android-н зарын нууцлалын бетагийн нэг хэсэг юм. Таны төхөөрөмжид Privacy Sandbox-г асаасан.\n\nТа нэмэлт мэдээлэл авах эсвэл нууцлалын тохиргоондоо бетаг хүссэн үедээ унтраах боломжтой."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Та оролцохгүй байхаар сонгосон"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Яг одоо харуулах сонирхол байхгүй"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android-н зарын нууцлалын бета нь аппуудын танд таалагдаж магадгүй зарыг харуулахад ашиглаж болох шинэ онцлогуудыг олгоно. Эдгээр технологи төхөөрөмжийн таниулбарыг ашигладаггүй.\n\nAndroid таны сонирхож магадгүй зарын төрлийг тооцоолох болон эдгээр сонирхлыг таны төхөөрөмж дээр түр зуур хадгалах боломжтой. Энэ нь аппуудад бусад хөгжүүлэгчийн вебсайт болон апп дээрх үйл ажиллагааг хяналгүйгээр танд хамааралтай зарыг харуулах боломжийг олгоно."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Танд блоклосон сонирхол байхгүй"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Аппууд таны сонирхлуудыг тооцоолж, тэдгээрийг Android-д түр зуур хадгалах боломжтой. Дараа нь эдгээр сонирхолд тулгуурлан өөр апп танд зар харуулах боломжтой.\n\nХэрэв та апп блокловол энэ нь таны сонирхлыг цаашид тооцоолохгүй. Таныг үүнийг блокоос гаргах хүртэл аппуудын жагсаалтад дахин нэмэхгүй. Аппын аль хэдийн тооцоолсон сонирхлуудыг устгах хэдий ч та зарим холбоотой зарыг харсан хэвээр байж магадгүй."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Таны блоклосон аппууд"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Аппуудын тооцоолсон сонирхлыг шинэчлэх"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android-н зарын нууцлалын бета нь аппуудын танд таалагдаж магадгүй зарыг харуулахад ашиглаж болох шинэ онцлогуудыг олгоно. Эдгээр технологи төхөөрөмжийн таниулбарыг ашигладаггүй.\n\nAппууд таны сонирхож магадгүй зарын төрлийг тооцоолох болон эдгээр сонирхлыг таны төхөөрөмж дээр түр зуур хадгалах боломжтой. Энэ нь аппуудад бусад хөгжүүлэгчийн вебсайт болон апп дээрх үйл ажиллагааг хяналгүйгээр танд хамааралтай зарыг харуулах боломжийг олгоно."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Танд блоклосон апп байхгүй байна"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Танд блоклосон сонирхол байхгүй"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Цуцлах"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Privacy Sandbox-г унтраах уу?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Хэрэв та бодлоо өөрчилбөл эсвэл Android-н зарын нууцлалын бетагийн талаар нэмэлт мэдээлэл авах хүсэлтэй бол нууцлалын тохиргоо руугаа очно уу"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Унтраах"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g>-г блоклох уу?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Энэ сонирхлыг блоклох бөгөөд та үүнийг буцааж нэмээгүйгээс бусад тохиолдолд жагсаалтад тань дахин нэмэхгүй. Та зарим холбоотой зарыг харсан хэвээр байж магадгүй."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Блоклох"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g>-г блокоос гаргасан"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android энэ сонирхлыг таны жагсаалтад дахин нэмж магадгүй хэдий ч энэ нь тэр даруй харагдахгүй"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ОК"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Бүх сонирхлоо шинэчлэх үү?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Таны жагсаалтыг арилгах бөгөөд цаашид шинэ сонирхол тооцоолно. Та арилгасан сонирхолтой холбоотой зарим зарыг харсан хэвээр байж магадгүй."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Шинэчлэх"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g>-г блоклох уу?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Энэ апп Privacy Sandbox-д сонирхол тооцоолохгүй бөгөөд та үүнийг блокоос гаргаагүйгээс бусад тохиолдолд жагсаалтад тань дахин нэмэхгүй.\n\nЭнэ аппын аль хэдийн тооцоолсон сонирхлыг устгах хэдий ч та зарим холбоотой зарыг харсан хэвээр байж магадгүй."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g>-г блокоос гаргасан"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Энэ апп танд зориулж сонирхлыг дахин тооцоолох боломжтой хэдий ч энэ нь таны жагсаалтад тэр даруй харагдахгүй байж магадгүй. Та холбоотой зар харах хүртэл хэсэг хугацаа зарцуулж магадгүй."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Аппуудын үүсгэсэн сонирхлыг шинэчлэх үү?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Таны жагсаалт дээрх аппуудын тооцоолсон сонирхлыг Privacy Sandbox-с устгах бөгөөд аппууд цаашид шинэ сонирхол тооцоолно. Та зарим холбоотой зарыг харсан хэвээр байж магадгүй."</string>
<string name="topic10001" msgid="1636806320891333775">"Урлаг &amp; энтертэйнмент"</string>
<string name="topic10002" msgid="1226367977754287428">"Жүжиглэх &amp; театр"</string>
<string name="topic10003" msgid="6949890838957814881">"Анимэ &amp; манга"</string>
diff --git a/adservices/apk/res/values-mr/strings.xml b/adservices/apk/res/values-mr/strings.xml
index 3a4b1db4b..7431d0099 100644
--- a/adservices/apk/res/values-mr/strings.xml
+++ b/adservices/apk/res/values-mr/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"सहभागी कसे व्हावे"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"बीटा सुरू केल्याने ॲप्सना तुम्हाला जाहिराती दाखवण्यासाठी या नवीन अधिक खाजगी मार्गांची चाचणी घेण्याची अनुमती मिळते. तुम्ही तुमच्या गोपनीयता सेटिंग्जमध्ये बीटा कधीही बंद करू शकता."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"नाही, नको"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"होय, मी सामील होईन"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"सुरू करा"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"आणखी"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"सहभागी झाल्याबद्दल धन्यवाद"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"तुम्ही Android च्या जाहिरातींशी संबंधित गोपनीयतेची बीटा आवृत्तीचा भाग आहात. तुमच्या डिव्हाइससाठी प्रायव्हसी सॅंडबाॅक्स सुरू केला आहे.\n\nतुम्ही अधिक जाणून घेऊ शकता किंवा तुमच्या गोपनीयता सेटिंग्जमध्ये बीटा कधीही बंद करू शकता."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"तुम्ही सहभागी न होण्याचे निवडले आहे"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"आता दाखवण्यासाठी कोणतीही स्वारस्ये नाहीत"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"प्रायव्हसी सॅंडबाॅक्स"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"तुम्हाला कदाचित आवडतील अशा जाहिराती दाखवण्यासाठी अ‍ॅप्सना वापरता येतील अशी नवीन वैशिष्ट्ये Android ची जाहिरातींशी संबंधित गोपनीयतेची बीटा आवृत्ती पुरवते. ही तंत्रज्ञाने डिव्हाइस आयडेंटिफायर वापरत नाहीत.\n\nतुम्हाला कोणत्या प्रकारच्या जाहिरातींमध्ये स्वारस्य असू शकते याच अंदाज Android लावू शकते आणि तुमच्या डिव्हाइसवर ती स्वारस्ये तात्पुरती सेव्ह करू शकते. हे अ‍ॅप्सना इतर डेव्हलपरच्या सर्व वेबसाइटवर आणि अ‍ॅप्सवर तुमची अ‍ॅक्टिव्हिटी ट्रॅक न करता उपयुक्त जाहिराती दाखवण्याची अनुमती देते."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"तुमच्याकडे कोणतीही ब्लॉक केलेली स्वारस्ये नाहीत"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"अ‍ॅप्स ही तुमच्या स्वारस्यांचा अंदाज लावून ती Android वर तात्पुरती सेव्ह करू शकतात. नंतर, दुसरे एखादे अ‍ॅप या स्वारस्यांच्या आधारावर तुम्हाला जाहिरात दाखवू शकते.\n\nतुम्ही एखादे अ‍ॅप ब्लॉक केल्यास, ते आणखी स्वारस्यांचा अंदाज लावणार नाही. तुम्ही ते अनब्लॉक केल्याशिवाय पुन्हा या अ‍ॅप्सच्या सूचीमध्ये जोडले जाणार नाही. ॲपद्वारे आधीच अंजाद वर्तवलेली स्वारस्ये हटवली जातील, परंतु तरीही तुम्हाला काही संबंधित जाहिराती दिसतील."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"तुम्ही ब्लॉक केलेली अ‍ॅप्स"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ॲप्सद्वारे अंदाज लावलेली स्वारस्ये रीसेट करा"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"प्रायव्हसी सॅंडबाॅक्स"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"तुम्हाला कदाचित आवडतील अशा जाहिराती दाखवण्यासाठी अ‍ॅप्सना वापरता येतील अशी नवीन वैशिष्ट्ये Android ची जाहिरातींशी संबंधित गोपनीयतेची बीटा आवृत्ती पुरवते. ही तंत्रज्ञाने डिव्हाइस आयडेंटिफायर वापरत नाहीत.\n\nतुम्हाला कोणत्या प्रकारच्या जाहिरातींमध्ये स्वारस्य असू शकते याच अंदाज अ‍ॅप्स लावू शकतात आणि तुमच्या डिव्हाइसवर ती स्वारस्ये तात्पुरती सेव्ह करू शकतात. हे अ‍ॅप्सना इतर डेव्हलपरच्या सर्व वेबसाइटवर आणि अ‍ॅप्सवर तुमची अ‍ॅक्टिव्हिटी ट्रॅक न करता उपयुक्त जाहिराती दाखवण्याची अनुमती देते."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"तुमच्याकडे कोणतीही ब्लॉक केलेली अ‍ॅप्स नाहीत"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"तुमच्याकडे कोणतीही ब्लॉक केलेली स्वारस्ये नाहीत"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"रद्द करा"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"प्रायव्हसी सॅंडबाॅक्स बंद करायचा आहे का?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"तुम्ही तुमचे मत बदलल्यास किंवा तुम्हाला Android च्या जाहिराती गोपनीयता बीटाबद्दल अधिक जाणून घ्यायचे असल्यास, तुमच्या गोपनीयता सेटिंग्जवर जा"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"बंद करा"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> ला ब्लॉक करायचे आहे का?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"हे स्वारस्य ब्लॉक केले जाईल आणि तुम्ही ते न जोडल्यास, सूचीमध्ये पुन्हा जोडले जाणार नाही. तुम्हाला तरीही काही संबंधित जाहिराती दिसू शकतात."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ब्लॉक करा"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> अनब्लॉक केले आहे"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android हे स्वारस्य तुमच्या सूचीमध्ये पुन्हा जोडू शकते, पण ते कदाचित तात्काळ दिसणार नाही"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ओके"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"तुमची सर्व स्वारस्ये रीसेट करायची आहेत का?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"तुमची सूची साफ केली जाईल, त्यानंतर यापुढे नवीन स्वारस्यांचा अंदाज लावला जाईल. तुम्हाला तरीही साफ केलेल्या स्वारस्यांशी संबंधित काही जाहिराती दिसू शकतात."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"रीसेट करा"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> ब्लॉक करायचे आहे का?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"हे अ‍ॅप प्रायव्हसी सॅंडबाॅक्स साठी स्वारस्यांचा अंदाज लावणार नाही आणि तुम्ही ते अनब्लॉक न केल्यास, सूचीमध्ये पुन्हा जोडले जाणार नाही.\n\nया अ‍ॅपद्वारे आधीच अंदाज लावलेली स्वारस्ये हटवली जातील, पण तरीही तुम्हाला काही संबंधित जाहिराती दिसू शकतात."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> अनब्लॉक केले आहे"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"हे अ‍ॅप तुमच्यासाठी स्वारस्यांचा पुन्हा अंदाज लावू शकते, पण ती तुमच्या सूचीमध्ये कदाचित तात्काळ दिसणार नाही. तुम्हाला संबंधित जाहिराती पाहण्यासाठी कदाचित थोडा वेळ लागेल."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"अ‍ॅप्सद्वारे जनरेट केलेली स्वारस्ये रीसेट करायची आहेत का?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"तुमच्या सूचीवरील अ‍ॅप्सद्वारे अंदाज लावलेली स्वारस्ये प्रायव्हसी सॅंडबाॅक्समधून हटवली जातील आणि अ‍ॅप्स यापुढे नवीन स्वारस्यांचा अंदाज लावतील. तुम्हाला तरीही काही संबंधित जाहिराती दिसू शकतात."</string>
<string name="topic10001" msgid="1636806320891333775">"कला आणि मनोरंजन"</string>
<string name="topic10002" msgid="1226367977754287428">"अभिनय आणि रंगभूमी"</string>
<string name="topic10003" msgid="6949890838957814881">"ॲनिमे आणि मांगा"</string>
diff --git a/adservices/apk/res/values-ms/strings.xml b/adservices/apk/res/values-ms/strings.xml
index 998809131..c3b200b60 100644
--- a/adservices/apk/res/values-ms/strings.xml
+++ b/adservices/apk/res/values-ms/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Cara menyertai"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Tindakan menghidupkan beta membolehkan apl menguji cara baharu yang lebih peribadi ini untuk memaparkan iklan kepada anda. Anda boleh mematikan beta pada bila-bila masa dalam tetapan privasi anda."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Tidak perlu"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ya, saya akan sertai"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Hidupkan"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Lagi"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Terima kasih kerana mengambil bahagian"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Anda merupakan sebahagian daripada beta privasi iklan Android. Kotak Pasir Privasi telah dihidupkan untuk peranti anda.\n\nAnda boleh mengetahui lebih lanjut atau mematikan beta pada bila-bila masa dalam tetapan privasi anda."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Anda telah memilih untuk tidak mengambil bahagian"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Tiada minat untuk ditunjukkan sekarang"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Kotak Pasir Privasi"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Beta privasi iklan Android menyediakan ciri baharu yang boleh digunakan oleh apl untuk memaparkan iklan yang mungkin anda suka kepada anda. Teknologi ini tidak menggunakan pengecam peranti.\n\nAndroid boleh menganggarkan jenis iklan yang mungkin anda minati dan menyimpan minat ini pada peranti anda buat sementara waktu. Hal ini membolehkan apl memaparkan iklan yang berkaitan kepada anda tanpa menjejaki aktiviti anda merentas laman web dan apl daripada pembangun lain."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Anda tidak mempunyai minat yang disekat"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apl boleh menganggarkan minat anda dan menyimpan minat ini dengan Android buat sementara waktu. Kemudian, apl lain boleh memaparkan iklan berdasarkan minat ini kepada anda.\n\nJika anda menyekat apl, apl itu tidak akan menganggarkan minat lagi. Apl tersebut tidak akan ditambahkan pada senarai apl ini lagi melainkan anda menyahsekat apl. Minat yang telah dianggarkan oleh apl akan dipadamkan, tetapi anda mungkin masih melihat beberapa iklan yang berkaitan."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apl yang telah anda sekat"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Tetapkan semula minat yang dianggarkan oleh apl"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Kotak Pasir Privasi"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Beta privasi iklan Android menyediakan ciri baharu yang boleh digunakan oleh apl untuk memaparkan iklan yang mungkin anda suka kepada anda. Teknologi ini tidak menggunakan pengecam peranti.\n\nApl boleh menganggarkan jenis iklan yang mungkin anda minati dan menyimpan minat ini pada peranti anda buat sementara waktu. Hal ini membolehkan apl memaparkan iklan yang berkaitan kepada anda tanpa menjejaki aktiviti anda merentas laman web dan apl daripada pembangun lain."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Anda tidak mempunyai apl yang disekat"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Anda tidak mempunyai minat yang disekat"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Batal"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Matikan Kotak Pasir Privasi?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Jika anda berubah fikiran atau ingin mengetahui lebih lanjut tentang beta privasi iklan Android, pergi ke tetapan privasi anda"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Matikan"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Sekat <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Minat ini akan disekat dan tidak akan ditambahkan pada senarai anda semula melainkan anda menambahkan minat itu semula. Anda mungkin masih akan melihat sesetengah iklan yang berkaitan."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Sekat"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> dinyahsekat"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android mungkin menambahkan minat ini pada senarai anda semula tetapi minat ini mungkin tidak akan muncul dengan serta-merta"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Tetapkan semula semua minat anda?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Senarai anda akan dikosongkan dan minat baharu akan dianggarkan pada masa hadapan. Anda mungkin masih akan melihat sesetengah iklan yang berkaitan dengan minat yang dikosongkan."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Tetapkan semula"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Sekat <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Apl ini tidak akan menganggarkan minat untuk Kotak Pasir Privasi dan tidak akan ditambahkan pada senarai anda semula melainkan anda menyahsekat apl tersebut.\n\nMinat yang telah dianggarkan oleh apl ini akan dipadamkan tetapi anda mungkin masih akan melihat sesetengah iklan yang berkaitan."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> dinyahsekat"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Apl ini boleh menganggarkan minat untuk anda semula tetapi minat tersebut mungkin tidak akan muncul pada senarai anda dengan serta-merta. Sedikit masa mungkin diperlukan untuk anda melihat iklan yang berkaitan."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Tetapkan semula minat yang dijana oleh apl?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Minat yang dianggarkan oleh apl pada senarai anda akan dipadamkan daripada Kotak Pasir Privasi dan apl akan menganggarkan minat baharu pada masa hadapan. Anda mungkin masih akan melihat sesetengah iklan yang berkaitan."</string>
<string name="topic10001" msgid="1636806320891333775">"Seni &amp; Hiburan"</string>
<string name="topic10002" msgid="1226367977754287428">"Lakonan &amp; Teater"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime &amp; Manga"</string>
diff --git a/adservices/apk/res/values-my/strings.xml b/adservices/apk/res/values-my/strings.xml
index 27a7a56d0..17393b77e 100644
--- a/adservices/apk/res/values-my/strings.xml
+++ b/adservices/apk/res/values-my/strings.xml
@@ -47,17 +47,18 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"ပါဝင်ပုံ"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"စမ်းသပ်အစီအစဉ် ဖွင့်ခြင်းဖြင့် ကြော်ငြာပြရန်အတွက် ကိုယ်ရေးအချက်အလက် ပိုမိုလုံခြုံသောနည်းလမ်းသစ်များအား စမ်းသပ်ရန် အက်ပ်များကို ခွင့်ပြုရာရောက်သည်။ စမ်းသပ်အစီအစဉ်ကို ကိုယ်ရေးအချက်အလက်လုံခြုံမှု ဆက်တင်များတွင် အချိန်မရွေး ပိတ်နိုင်သည်။"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"မလိုပါ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ပါဝင်ရန်"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ဖွင့်ရန်"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"ပိုပြပါ"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"ပါဝင်ပေး၍ ကျေးဇူးတင်ပါသည်"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Android ၏ ကြော်ငြာဆိုင်ရာ ကိုယ်ရေးအချက်လုံခြုံမှု စမ်းသပ်အစီအစဉ်တွင် သင်ပါဝင်သည်။ Privacy Sandbox ကို သင့်စက်အတွက် ဖွင့်လိုက်သည်။\n\nကိုယ်ရေးအချက်အလက်လုံခြုံမှု ဆက်တင်များတွင် စမ်းသပ်အစီအစဉ်အကြောင်း ပိုမိုလေ့လာနိုင်သည် (သို့) ၎င်းကို အချိန်မရွေး ပိတ်နိုင်သည်။"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"မပါဝင်ရန် သင်ရွေးချယ်ထားပါသည်"</string>
<string name="notificationUI_confirmation_decline_subtitle" msgid="4194388733937808440">"ဖြေကြားမှုအတွက် ကျေးဇူးတင်ပါသည်။ Privacy Sandbox ကို သင့်စက်အတွက် ပိတ်လိုက်သည်။\n\nသင်စိတ်ပြောင်းသွားပါက (သို့) ပိုမိုလေ့လာလိုပါက ကိုယ်ရေးအချက်အလက်လုံခြုံမှု ဆက်တင်များသို့ သွားပါ။"</string>
<string name="notificationUI_confirmation_left_control_button_text" msgid="7459283556129950513">"ကိုယ်ရေးအချက်အလက်လုံခြုံမှု ဆက်တင်များ"</string>
- <string name="notificationUI_confirmation_right_control_button_text" msgid="1390803936115621269">"ရပြီ"</string>
+ <string name="notificationUI_confirmation_right_control_button_text" msgid="1390803936115621269">"နားလည်ပြီ"</string>
<string name="notificationUI_container2_title" msgid="3844342008598264655">"စမ်းသပ်အစီအစဉ်တွင် သင်ပါဝင်သည်"</string>
<string name="notificationUI_container2_body_text" msgid="758560159693325060">"Privacy Sandbox ကို သင့်စက်အတွက်ဖွင့်ထားပြီး ကြော်ငြာပြရန် ကိုယ်ရေးအချက်အလက် ပိုမိုလုံခြုံသောနည်းလမ်းသစ်များကို အက်ပ်များက စမ်းသပ်နိုင်သည်။ စမ်းသပ်အစီအစဉ်ကို ကိုယ်ရေးအချက်အလက်လုံခြုံမှု ဆက်တင်များတွင် အချိန်မရွေး ပိတ်နိုင်သည်။"</string>
<string name="notificationUI_left_control_button_text" msgid="6591519663037500665">"ကိုယ်ရေးအချက်အလက်လုံခြုံမှု ဆက်တင်များ စီမံရန်"</string>
- <string name="notificationUI_right_control_button_text" msgid="4418849554981401536">"ရပြီ"</string>
+ <string name="notificationUI_right_control_button_text" msgid="4418849554981401536">"နားလည်ပြီ"</string>
<string name="notificationUI_how_it_works_expanded_text1" msgid="5567641119079971239">"Android ၏ Privacy Sandbox သည် သင်နှစ်သက်နိုင်မည့် ကြော်ငြာများပြရန် အက်ပ်များအသုံးပြုနိုင်သော ဝန်ဆောင်မှုသစ်များကို ပံ့ပိုးသည်။ ဤနည်းပညာများသည် စက်ပစ္စည်း သတ်မှတ်မှုစနစ်များကို အသုံးမပြုပါ။\n\nAndroid နှင့် သင့်အက်ပ်များက သင်နှစ်သက်နိုင်မည့် ကြော်ငြာအမျိုးအစားများကို ခန့်မှန်းနိုင်ပြီး သင့်စက်တွင် စိတ်ဝင်စားမှုများကို ယာယီသိမ်းနိုင်သည်။ ၎င်းက သင့်လုပ်ဆောင်ချက်များအား ဝဘ်ဆိုက်နှင့် အခြားဆော့ဖ်ဝဲရေးသူများ၏ အက်ပ်များတွင် ခြေရာခံစရာမလိုဘဲ သင်နှစ်သက်နိုင်မည့် ကြော်ငြာများအား အက်ပ်များကို ပြခွင့်ပေးသည်။"</string>
<string name="notificationUI_how_it_works_expanded_text2" msgid="6676443093692450581">"Privacy Sandbox ဖြင့် ကြော်ငြာများ စိတ်ကြိုက်သတ်မှတ်ခြင်း"</string>
<string name="notificationUI_how_it_works_expanded_text3" msgid="603165966360369018">"•  Android က ခန့်မှန်းထားသည့် စိတ်ဝင်စားမှုများ"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ယခုပြသရန် စိတ်ဝင်စားမှုများ မရှိပါ"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android ၏ ကြော်ငြာဆိုင်ရာ ကိုယ်ရေးအချက်အလက်လုံခြုံမှု စမ်းသပ်အစီအစဉ်သည် သင်နှစ်သက်နိုင်မည့် ကြော်ငြာများပြရန် အက်ပ်များအသုံးပြုနိုင်သော ဝန်ဆောင်မှုသစ်များကို ပံ့ပိုးသည်။ ဤနည်းပညာများသည် စက်ပစ္စည်း သတ်မှတ်မှုစနစ်များကို အသုံးမပြုပါ။\n\nAndroid က သင်နှစ်သက်နိုင်မည့် ကြော်ငြာအမျိုးအစားများကို ခန့်မှန်းနိုင်ပြီး သင့်စက်တွင် စိတ်ဝင်စားမှုများကို ယာယီသိမ်းနိုင်သည်။ ၎င်းက သင့်လုပ်ဆောင်ချက်များကို ဝဘ်ဆိုက်နှင့် အခြားဆော့ဖ်ဝဲရေးသူများ၏ အက်ပ်များတွင် ခြေရာခံစရာမလိုဘဲ သင်နှင့်သက်ဆိုင်သည့် ကြော်ငြာများအား အက်ပ်များကို ပြခွင့်ပေးသည်။"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ပိတ်ထားသည့် စိတ်ဝင်စားမှုများ မရှိပါ"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"အက်ပ်များက သင့်စိတ်ဝင်စားမှုများကို ခန့်မှန်းပြီး ၎င်းတို့ကို Android ဖြင့် ယာယီသိမ်းနိုင်သည်။ နောက်ပိုင်းတွင် အခြားအက်ပ်တစ်ခုက ဤစိတ်ဝင်စားမှုများပေါ်မူတည်၍ ကြော်ငြာပြနိုင်သည်။\n\nအက်ပ်တစ်ခုအား ပိတ်ထားပါက ၎င်းသည် စိတ်ဝင်စားမှုများကို ခန့်မှန်းတော့မည်မဟုတ်ပါ။ ၎င်းကို ပြန်မဖွင့်မချင်း ဤအက်ပ်စာရင်းတွင် ထည့်မည်မဟုတ်ပါ။ အက်ပ်က ခန့်မှန်းထားသည့် စိတ်ဝင်စားမှုများကို ဖျက်မည်ဖြစ်သော်လည်း သက်ဆိုင်ရာကြော်ငြာအချို့ကို မြင်နိုင်သေးသည်။"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"သင်ပိတ်ထားသော အက်ပ်များ"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"အက်ပ်များက ခန့်မှန်းထားသည့် စိတ်ဝင်စားမှုများ ပြင်ဆင်သတ်မှတ်ရန်"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android ၏ ကြော်ငြာဆိုင်ရာ ကိုယ်ရေးအချက်အလက်လုံခြုံမှု စမ်းသပ်အစီအစဉ်သည် သင်နှစ်သက်နိုင်မည့် ကြော်ငြာများပြရန် အက်ပ်များအသုံးပြုနိုင်သော ဝန်ဆောင်မှုသစ်များကို ပံ့ပိုးသည်။ ဤနည်းပညာများသည် စက်ပစ္စည်း သတ်မှတ်မှုစနစ်များကို အသုံးမပြုပါ။\n\nအက်ပ်များက သင်နှစ်သက်နိုင်မည့် ကြော်ငြာအမျိုးအစားများကို ခန့်မှန်းနိုင်ပြီး သင့်စက်တွင် စိတ်ဝင်စားမှုများကို ယာယီသိမ်းနိုင်သည်။ ၎င်းက သင့်လုပ်ဆောင်ချက်များကို ဝဘ်ဆိုက်နှင့် အခြားဆော့ဖ်ဝဲရေးသူများ၏ အက်ပ်များတွင် ခြေရာခံစရာမလိုဘဲ သင်နှင့်သက်ဆိုင်သည့် ကြော်ငြာများအား အက်ပ်များကို ပြခွင့်ပေးသည်။"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ပိတ်ထားသည့်အက်ပ်များ မရှိပါ"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ပိတ်ထားသည့် စိတ်ဝင်စားမှုများ မရှိပါ"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"မလုပ်တော့"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Privacy Sandbox ပိတ်မလား။"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"စိတ်ပြောင်းသွားပါက (သို့) Android ၏ ကြော်ငြာများဆိုင်ရာ ကိုယ်ရေးအချက်အလက်လုံခြုံမှု ဘီတာဗားရှင်းအကြောင်း ပိုမိုလေ့လာလိုပါက သင်၏ကိုယ်ရေးအချက်အလက်လုံခြုံမှုဆိုင်ရာ ဆက်တင်များသို့ သွားနိုင်သည်"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ပိတ်ရန်"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> ကို ပိတ်ထားမလား။"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ဤစိတ်ဝင်စားမှုကို ပိတ်ထားမည်ဖြစ်ပြီး သင်ပြန်မထည့်ပါက သင်၏စာရင်းတွင် ၎င်းကို ထပ်ထည့်မည်မဟုတ်ပါ။ ဆက်စပ်သော ကြော်ငြာအချို့ကို မြင်ရနိုင်သေးသည်။"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ပိတ်ထားရန်"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> ကို ပြန်ဖွင့်လိုက်သည်"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android က ဤစိတ်ဝင်စားမှုကို သင်၏စာရင်းတွင် ထပ်ထည့်နိုင်သော်လည်း ၎င်းက ချက်ချင်းပေါ်မလာနိုင်ပါ"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"သင်၏ စိတ်ဝင်စားမှုအားလုံးကို ပြင်ဆင်သတ်မှတ်မလား။"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"သင့်စာရင်းကို ရှင်းလင်းပြီး ယခုမှစ၍ စိတ်ဝင်စားမှုအသစ်များကို ခန့်မှန်းမည်။ ရှင်းလင်းထားသော စိတ်ဝင်စားမှုများနှင့် သက်ဆိုင်သည့် ကြော်ငြာအချို့ကို မြင်နိုင်သေးသည်။"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"ပြင်ဆင်သတ်မှတ်ရန်"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> ကို ပိတ်ထားမလား။"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ဤအက်ပ်သည် Privacy Sandbox အတွက် စိတ်ဝင်စားမှုများကို ခန့်မှန်းမည်မဟုတ်ဘဲ သင်ပြန်မဖွင့်ပါက သင်၏စာရင်းတွင် ၎င်းကို ထပ်ထည့်မည်မဟုတ်ပါ။\n\nဤအက်ပ်က ခန့်မှန်းထားပြီးသော စိတ်ဝင်စားမှုများကို ဖျက်လိုက်မည်ဖြစ်သော်လည်း ဆက်စပ်သော ကြော်ငြာအချို့ကို မြင်ရနိုင်သေးသည်။"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> ကို ပြန်ဖွင့်လိုက်သည်"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ဤအက်ပ်သည် သင့်အတွက်စိတ်ဝင်စားမှုများကို ထပ်ခန့်မှန်းနိုင်သော်လည်း ၎င်းက သင်၏စာရင်းတွင် ချက်ချင်းပေါ်မလာနိုင်ပါ။ ဆက်စပ်သော ကြော်ငြာများ မြင်ရရန် အချိန်အနည်းငယ်ကြာနိုင်သည်။"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"အက်ပ်များကထုတ်ပေးသော စိတ်ဝင်စားမှုများကို ပြင်ဆင်သတ်မှတ်မလား။"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"သင့်စာရင်းတွင် အက်ပ်များမှ ခန့်မှန်းထားသော စိတ်ဝင်စားမှုများကို Privacy Sandbox မှ ဖျက်မည်ဖြစ်ပြီး အက်ပ်များက ယခုမှစ၍ စိတ်ဝင်စားမှုအသစ်များကို ခန့်မှန်းမည်။ ဆက်စပ်သော ကြော်ငြာအချို့ကို မြင်ရနိုင်သေးသည်။"</string>
<string name="topic10001" msgid="1636806320891333775">"အနုပညာနှင့် ဖျော်ဖြေရေး"</string>
<string name="topic10002" msgid="1226367977754287428">"သရုပ်ဆောင်ခြင်းနှင့် ကဇာတ်ရုံ"</string>
<string name="topic10003" msgid="6949890838957814881">"အန်နီမီနှင့် မန်ဂါ"</string>
diff --git a/adservices/apk/res/values-nb/strings.xml b/adservices/apk/res/values-nb/strings.xml
index 623bd94c3..6dca2ec3a 100644
--- a/adservices/apk/res/values-nb/strings.xml
+++ b/adservices/apk/res/values-nb/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Slik deltar du"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Hvis du slår på betaversjonen, kan apper teste disse nye, mer private måtene å vise deg annonser på. Du kan når som helst slå av betaversjonen i personverninnstillingene."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nei takk"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ja, jeg vil bli med"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Slå på"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Mer"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Takk for deltakelsen"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Du er en del av Android-betaversjonen for annonsepersonvern. Privacy Sandbox er slått på for enheten din.\n\nDu kan finne ut mer eller slå av betaversjonen når som helst i personverninnstillingene."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Du har valgt å ikke delta"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Ingen interesser å vise akkurat nå"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android-betaversjonen for annonsepersonvern har nye funksjoner som apper kan bruke for å vise deg annonser du kan være interessert i. Disse teknologiene bruker ikke enhetsidentifikatorer.\n\nAndroid kan anslå hvilke typer annonser du kan være interessert i, og lagre disse interessene midlertidig på enheten din. Dette gjør at apper kan vise deg relevante annonser uten å spore aktiviteten din på nettsteder og i apper fra andre utviklere."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Du har ingen blokkerte interesser"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apper kan anslå interessene dine og midlertidig lagre disse med Android. Senere kan andre apper viser deg annonser basert på disse interessene.\n\nHvis du blokkerer en app, anslår den ikke flere interesser. Den legges ikke til i denne applisten igjen, med mindre du opphever blokkeringen av den. Interesser som allerede er anslått av appen, blir slettet, men du kan fremdeles se noen relaterte annonser."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apper du har blokkert"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Tilbakestill interesser anslått av apper"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android-betaversjonen for annonsepersonvern har nye funksjoner som apper kan bruke for å vise deg annonser du kan være interessert i. Disse teknologiene bruker ikke enhetsidentifikatorer.\n\nApper kan anslå hvilke typer annonser du kan være interessert i, og lagre disse interessene midlertidig på enheten din. Dette gjør at apper kan vise deg relevante annonser uten å spore aktiviteten din på nettsteder og i apper fra andre utviklere."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Du har ingen blokkerte apper"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Du har ingen blokkerte interesser"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Avbryt"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Vil du slå av Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Hvis du ombestemmer deg eller vil finne ut mer om betaversjonen av annonsepersonvern for Android, kan du gå til personverninnstillingene."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Slå av"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Vil du blokkere <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Denne interessen blir blokkert og blir ikke lagt til i listen igjen, med mindre du legger den til igjen. Du kan fortsatt se noen relaterte annonser."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokkér"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Blokkeringen av <xliff:g id="TOPIC">%1$s</xliff:g> er opphevet"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android kan legge til denne interessen i listen igjen, men den vises muligens ikke umiddelbart"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Vil du tilbakestille alle interessene dine?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Listen blir tømt, og nye interesser blir anslått i fremtiden. Du kan fortsatt se noen annonser relatert til fjernede interesser."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Tilbakestill"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Vil du blokkere <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Denne appen anslår ikke interesser for Privacy Sandbox og blir ikke lagt til i listen igjen, med mindre du opphever blokkeringen av den.\n\nInteresser som allerede er anslått av denne appen, blir slettet, men det kan hende du fortsatt ser noen relaterte annonser."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Blokkeringen av <xliff:g id="APP">%1$s</xliff:g> er opphevet"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Denne appen kan anslå interesser for deg igjen, men den vises muligens ikke i listen umiddelbart. Det kan ta en stund før du ser relaterte annonser."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Vil du tilbakestille interesser som er anslått av apper?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interesser anslått av appene i listen blir slettet fra Privacy Sandbox, og appene anslår nye interesser i fremtiden. Du kan fortsatt se noen relaterte annonser."</string>
<string name="topic10001" msgid="1636806320891333775">"Kunst og underholdning"</string>
<string name="topic10002" msgid="1226367977754287428">"Skuespill og teater"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime og manga"</string>
diff --git a/adservices/apk/res/values-ne/strings.xml b/adservices/apk/res/values-ne/strings.xml
index c5741cdfb..8389d9a7b 100644
--- a/adservices/apk/res/values-ne/strings.xml
+++ b/adservices/apk/res/values-ne/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"सहभागी हुन के गर्नु पर्छ"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"तपाईंले यो बिटा सुविधा अन गर्नुभयो भने एपहरू तपाईंलाई विज्ञापन देखाउने यी नयाँ अनि अझ गोप्य तरिकाको परीक्षण गर्न सक्छन्। तपाईं जुनसुकै बेला गोपनीयतासम्बन्धी सेटिङमा गई यो बिटा सुविधा अफ गर्न सक्नुहुन्छ।"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"पर्दैन"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"अँ, म सामेल हुन चाहन्छु"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"अन गर्नुहोस्"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"थप देखाइयोस्"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"सामेल हुनुभएकोमा धन्यवाद"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"तपाईं Android को विज्ञापनसम्बन्धी गोपनीयता सुविधाको बिटा संस्करणको सदस्य बन्नुभएको छ। तपाईंको डिभाइसमा प्राइभेसी स्यान्डबक्स अन गरिएको छ।\n\nतपाईं जुनसुकै बेला गोपनीयतासम्बन्धी सेटिङमा गई यसका बारेमा थप जान्न वा यसलाई अफ गर्न सक्नुहुन्छ।"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"तपाईंले यस कार्यक्रममा सामेल नहुने विकल्प छनौट गर्नुभएको छ"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"यस बखत देखाउनका लागि कुनै पनि रुचि छैन"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"प्राइभेसी स्यान्डबक्स"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android को विज्ञापनसम्बन्धी गोपनीयता सुविधाको बिटा संस्करणले एपहरूले तपाईंलाई मन पर्न सक्ने विज्ञापनहरू देखाउनका निम्ति प्रयोग गर्न सक्ने नयाँ सुविधाहरू उपलब्ध गराउँछ। यी प्रविधिले डिभाइस आइडेन्टिफायर प्रयोग गर्दैनन्।\n\nAndroid ले तपाईंको रुचि हुन सक्ने विज्ञापनहरूका बारेमा अनुमान लगाउन र ती रुचिहरू अस्थायी रूपमा तपाईंको डिभाइसमा सेभ गर्न सक्छ। यसले गर्दा एपहरूले अन्य विकासकर्ताका वेबसाइट तथा एपहरूमा तपाईंले गर्ने क्रियाकलाप ट्र्याक नगरिकनै तपाईंलाई सान्दर्भिक विज्ञापनहरू देखाउन सक्छन्।"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"तपाईंले कुनै पनि रुचि ब्लक गर्नुभएको छैन"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"एपहरूले तपाईंका रुचिका बारेमा अनुमान लगाउन र ती रुचिहरू अस्थायी रूपमा Android मा सेभ गर्न सक्छन्। पछि अर्कै एपले ती रुचिका आधारमा तपाईंलाई कुनै विज्ञापन देखाउन सक्छ।\n\nतपाईंले कुनै एप ब्लक गर्नुभयो भने उक्त एपले थप रुचिहरू हाल्ने छैन। तपाईंले सो एप अनब्लक नगरेसम्म त्यसलाई फेरि एपहरूको यो सूचीमा समावेश गरिने छैन। उक्त एपले अनुमान गरिसकेका रुचिहरू मेटाइने छन् तर तपाईं अझै पनि त्यससँग सम्बन्धित केही विज्ञापनहरू देख्न सक्नुहुन्छ।"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"तपाईंले ब्लक गर्नुभएका एपहरू"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"एपहरूले अनुमान गरेका रुचि रिसेट गर्नुहोस्"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"प्राइभेसी स्यान्डबक्स"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android को विज्ञापनसम्बन्धी गोपनीयता सुविधाको बिटा संस्करणले एपहरूले तपाईंलाई मन पर्न सक्ने विज्ञापनहरू देखाउनका निम्ति प्रयोग गर्न सक्ने नयाँ सुविधाहरू उपलब्ध गराउँछ। यी प्रविधिले डिभाइस आइडेन्टिफायर प्रयोग गर्दैनन्।\n\nएपहरूले तपाईंको रुचि हुन सक्ने विज्ञापनहरूका बारेमा अनुमान लगाउन र ती रुचिहरू अस्थायी रूपमा तपाईंको डिभाइसमा सेभ गर्न सक्छन्। यसले गर्दा एपहरूले अन्य विकासकर्ताका वेबसाइट तथा एपहरूमा तपाईंले गर्ने क्रियाकलाप ट्र्याक नगरिकनै तपाईंलाई सान्दर्भिक विज्ञापनहरू देखाउन सक्छन्।"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"तपाईंले कुनै पनि एप ब्लक गर्नुभएको छैन"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"तपाईंले कुनै पनि रुचि ब्लक गर्नुभएको छैन"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"रद्द गर्नुहोस्"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"प्राइभेसी स्यान्डबक्स अफ गर्ने हो?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"तपाईंलाई पछि प्राइभेसी स्यान्डबक्स अन गर्न वा Android को विज्ञापनसम्बन्धी गोपनीयता सुविधाको बिटा संस्करणका बारेमा थप जान्न मन लाग्यो भने तपाईं गोपनीयतासम्बन्धी सेटिङमा जान सक्नुहुन्छ"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"अफ गर्नुहोस्"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> ब्लक गर्ने हो?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"यो रुचि ब्लक गरिने छ र तपाईंले यसलाई फेरि आफ्नो सूचीमा नहालेसम्म यसलाई सो सूचीमा हालिने छैन। तपाईं अझै पनि त्यससँग सम्बन्धित केही विज्ञापनहरू देख्न सक्नुहुन्छ।"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ब्लक गर्नुहोस्"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> अनब्लक गरिएको छ"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ले यो रुचि फेरि तपाईंको सूचीमा हाल्न सक्छ तर सो रुचि तुरुन्तै तपाईंको सूचीमा भने नदेखिन सक्छ"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ठिक छ"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"आफ्ना सबै रुचिहरू रिसेट गर्ने हो?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"तपाईंको सूची मेटाइने छ र अबदेखि नयाँ रूचिहरू अनुमान गरिने छ। तपाईं अझै पनि मेटाइएका रूचिहरूसँग सम्बन्धित केही विज्ञापनहरू देख्न सक्नुहुन्छ।"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"रिसेट गर्नुहोस्"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> ब्लक गर्ने हो?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"यो एपले प्राइभेसी स्यान्डबक्सका हकमा रुचिहरूको अनुमान गर्ने छैन र तपाईंले कुनै रुचि अनब्लक नगरेसम्म त्यसलाई फेरि तपाईंको सूचीमा हालिने छैन।\n\nयो एपले अनुमान गरिसकेका रुचिहरू मेटाइने छन् तर तपाईं अझै पनि त्यससँग सम्बन्धित केही विज्ञापनहरू देख्न सक्नुहुन्छ।"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> अनब्लक गरिएको छ"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"यो एपले फेरि तपाईंका रुचिहरूको अनुमान गर्न सक्छ तर ती रुचिहरू तुरुन्तै तपाईंको सूचीमा भने नदेखिन सक्छ। तपाईं केही बेरपछि मात्र त्यससँग सम्बन्धित विज्ञापनहरू देख्न सक्नुहुन्छ।"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"एपहरूले अनुमान गरेका रुचिहरू रिसेट गर्ने हो?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"तपाईंको सूचीमा रहेका एपहरूले अनुमान गरेका रूचिहरू प्राइभेसी स्यान्डबक्सबाट मेटाइने छन् र ती एपहरूले अबदेखि नयाँ रूचिहरू अनुमान गर्ने छन्। तपाईं अझै पनि त्यससँग सम्बन्धित केही विज्ञापनहरू देख्न सक्नुहुन्छ।"</string>
<string name="topic10001" msgid="1636806320891333775">"कला तथा मनोरञ्जन"</string>
<string name="topic10002" msgid="1226367977754287428">"अभिनय तथा रङ्गमञ्च"</string>
<string name="topic10003" msgid="6949890838957814881">"एनिम तथा माङ्गा"</string>
diff --git a/adservices/apk/res/values-night/colors.xml b/adservices/apk/res/values-night/colors.xml
index 4a0a89996..fe78e9110 100644
--- a/adservices/apk/res/values-night/colors.xml
+++ b/adservices/apk/res/values-night/colors.xml
@@ -1,11 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <color name="background_color">#1B1B1B</color>
+ <color name="background_color">@color/settingslib_background_device_default_dark</color>
<color name="divider_color">#474747</color>
<color name="card_background_color">#303030</color>
- <color name="primary_text_color">#F2F2F2</color>
- <color name="secondary_text_color">#C7C7C7</color>
-
<!-- Material inverse ripple color, useful for inverted backgrounds. -->
<color name="ripple_material_inverse">@*android:color/ripple_material_light</color>
</resources>
diff --git a/adservices/apk/res/values-nl/strings.xml b/adservices/apk/res/values-nl/strings.xml
index d42fa90b4..12868b42f 100644
--- a/adservices/apk/res/values-nl/strings.xml
+++ b/adservices/apk/res/values-nl/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Deelnamevereisten"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Als je de bèta aanzet, kunnen apps deze nieuwe, meer privacyvriendelijke manieren testen om jou advertenties te laten zien. Je kunt de bèta altijd uitzetten in de privacyinstellingen."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nee"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ja, ik doe mee"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Aanzetten"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Meer"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Bedankt voor je deelname"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Je doet mee aan de Android-bèta voor advertentieprivacy. De Privacy Sandbox staat aan voor je apparaat.\n\nIn de privacyinstellingen vind je meer informatie. Ook kun je de bèta daar op elk gewenst moment uitzetten."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Je hebt ervoor gekozen om niet mee te doen"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Geen interesses om momenteel te tonen"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"De Android-bèta voor advertentieprivacy biedt nieuwe functies waarmee apps je advertenties kunnen laten zien die je misschien interessant vindt. Deze technologieën gebruiken geen apparaat-ID\'s.\n\nAndroid kan inschatten wat voor advertenties interessant voor jou zouden zijn en deze interesses tijdelijk op je apparaat opslaan. Zo kunnen apps relevante advertenties laten zien zonder je activiteit op websites en in apps van andere ontwikkelaars te tracken."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Je hebt geen interesses geblokkeerd"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Apps kunnen een inschatting maken van je interesses en die tijdelijk opslaan via Android. Later kan een andere app je een advertentie laten zien op basis van deze interesses.\n\nAls je een app blokkeert, maakt deze geen inschattingen van interesses meer. De app wordt niet meer aan deze lijst toegevoegd, tenzij je de blokkering opheft. Interesses die al door de app zijn geschat, worden verwijderd, maar je kunt nog wel een aantal gerelateerde advertenties te zien krijgen."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps die je hebt geblokkeerd"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Interesses die zijn geschat door apps resetten"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"De Android-bèta voor advertentieprivacy biedt nieuwe functies waarmee apps je advertenties kunnen laten zien die je misschien interessant vindt. Deze technologieën gebruiken geen apparaat-ID\'s.\n\nApps kunnen inschatten wat voor advertenties interessant voor jou zouden zijn en deze interesses tijdelijk op je apparaat opslaan. Zo kunnen apps relevante advertenties laten zien zonder je activiteit op websites en in apps van andere ontwikkelaars te tracken."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Je hebt geen apps geblokkeerd"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Je hebt geen interesses geblokkeerd"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Annuleren"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Privacy Sandbox uitzetten?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Als je van gedachten verandert of meer informatie over de bèta voor advertentieprivacy van Android wilt, ga je naar je privacyinstellingen"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Uitzetten"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> blokkeren?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Deze interesse wordt geblokkeerd en wordt niet weer aan je lijst toegevoegd, tenzij je deze zelf weer toevoegt. Je ziet misschien nog wel bepaalde gerelateerde advertenties."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokkeren"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> wordt niet meer geblokkeerd"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android kan deze interesse weer aan je lijst toevoegen, maar deze wordt misschien niet meteen getoond"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Al je interesses resetten?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Je lijst wordt gewist. Vanaf dat moment worden nieuwe interesses geschat. Je ziet misschien nog wel bepaalde advertenties gerelateerd aan gewiste interesses."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Resetten"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> blokkeren?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Deze app schat geen interesses voor de Privacy Sandbox en wordt niet weer aan je lijst toegevoegd, tenzij je de app niet meer blokkeert.\n\nInteresses die deze app al heeft geschat, worden verwijderd. Je ziet misschien nog wel bepaalde gerelateerde advertenties."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> wordt niet meer geblokkeerd"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Deze app kan weer interesses voor je schatten, maar wordt misschien niet meteen in je lijst getoond. Het kan even duren voordat je gerelateerde advertenties ziet."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Door apps gegenereerde interesses resetten?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interesses die zijn geschat door de apps op je lijst worden verwijderd uit de Privacy Sandbox. Vanaf dat moment schatten de apps nieuwe interesses. Je ziet misschien nog wel bepaalde gerelateerde advertenties."</string>
<string name="topic10001" msgid="1636806320891333775">"Kunst en entertainment"</string>
<string name="topic10002" msgid="1226367977754287428">"Acteren en theater"</string>
<string name="topic10003" msgid="6949890838957814881">"Animatie en manga"</string>
diff --git a/adservices/apk/res/values-or/strings.xml b/adservices/apk/res/values-or/strings.xml
index b321e7631..e1d352484 100644
--- a/adservices/apk/res/values-or/strings.xml
+++ b/adservices/apk/res/values-or/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"କିପରି ଅଂଶଗ୍ରହଣ କରିବେ"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"ବିଟା ଚାଲୁ କରିବା ଫଳରେ ଆପଣଙ୍କୁ ବିଜ୍ଞାପନଗୁଡ଼ିକ ଦେଖାଇବା ପାଇଁ ଏହି ନୂଆ, ଅଧିକ ବ୍ୟକ୍ତିଗତ ଉପାୟକୁ ପରୀକ୍ଷା କରିବା ପାଇଁ ଏହା ଆପ୍ସକୁ ଅନୁମତି ଦିଏ। ଆପଣ ଯେ କୌଣସି ସମୟରେ ଆପଣଙ୍କ ଗୋପନୀୟତା ସେଟିଂସରେ ବିଟାକୁ ବନ୍ଦ କରିପାରିବେ।"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"ନା, ଧନ୍ୟବାଦ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ହଁ, ମୁଁ ଯୋଗ ଦେବି"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ଚାଲୁ କରନ୍ତୁ"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"ଅଧିକ"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"ଅଂଶଗ୍ରହଣ ପାଇଁ ଆପଣଙ୍କୁ ଧନ୍ୟବାଦ"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"ଆପଣ Androidର ବିଜ୍ଞାପନ ଗୋପନୀୟତା ବିଟାର ଅଂଶ ଅଟନ୍ତି। ଆପଣଙ୍କ ଡିଭାଇସ ପାଇଁ ପ୍ରାଇଭେସି ସେଣ୍ଡବକ୍ସ ଚାଲୁ ଅଛି।\n\nଆପଣଙ୍କର ଗୋପନୀୟତା ସେଟିଂସରେ ଆପଣ ଅଧିକ ଜାଣିପାରିବେ କିମ୍ବା ଯେ କୌଣସି ସମୟରେ ବିଟାକୁ ବନ୍ଦ କରିପାରିବେ।"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"ଆପଣ ଅଂଶଗ୍ରହଣ ନକରିବା ପାଇଁ ବାଛିଛନ୍ତି"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ବର୍ତ୍ତମାନ ଦେଖାଇବା ପାଇଁ କୌଣସି ଆଗ୍ରହ ନାହିଁ"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"ପ୍ରାଇଭେସି ସେଣ୍ଡବକ୍ସ"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"ଆପଣ ପସନ୍ଦ କରୁଥିବା ବିଜ୍ଞାପନଗୁଡ଼ିକ ଆପଣଙ୍କୁ ଦେଖାଇବା ପାଇଁ ଆପ୍ସ ବ୍ୟବହାର କରିପାରୁଥିବା ନୂଆ ଫିଚରଗୁଡ଼ିକ Androidର ଗୋପନୀୟତା ବିଟା ପ୍ରଦାନ କରେ। ଏହି ଟେକ୍ନୋଲୋଜିଗୁଡ଼ିକ ଡିଭାଇସ ଚିହ୍ନଟକାରୀଗୁଡ଼ିକ ବ୍ୟବହାର କରେ ନାହିଁ।\n\nଆପଣ କେଉଁ ପ୍ରକାରର ବିଜ୍ଞାପନଗୁଡ଼ିକ ପାଇଁ ଆଗ୍ରହୀ ହୋଇପାରନ୍ତି ତାହା Android ଆକଳନ କରିପାରେ ଏବଂ ସେହି ଆଗ୍ରହଗୁଡ଼ିକୁ ଆପଣଙ୍କ ଡିଭାଇସରେ ସାମୟିକ ଭାବରେ ସେଭ କରିପାରେ। ସମସ୍ତ ୱେବସାଇଟ ଏବଂ ଅନ୍ୟ ଡେଭେଲପରଙ୍କ ଆପ୍ସରେ ଆପଣଙ୍କ କାର୍ଯ୍ୟକଳାପକୁ ଟ୍ରାକ ନକରି, ଏହା ପ୍ରାସଙ୍ଗିକ ବିଜ୍ଞାପନଗୁଡ଼ିକ ଆପଣଙ୍କୁ ଦେଖାଇବା ପାଇଁ ଅନୁମତି ଦିଏ।"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ଆପଣଙ୍କର କୌଣସି ବ୍ଲକ କରାଯାଇଥିବା ଆଗ୍ରହ ନାହିଁ"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"ଆପ୍ସ ଆପଣଙ୍କ ଆଗ୍ରହଗୁଡ଼ିକୁ ଆକଳନ କରିପାରେ ଏବଂ Android ମାଧ୍ୟମରେ ସେଗୁଡ଼ିକୁ ସାମୟିକ ଭାବେ ସେଭ କରିପାରେ। ପରେ, ଏକ ଭିନ୍ନ ଆପ ଆପଣଙ୍କୁ ଏହି ଆଗ୍ରହଗୁଡ଼ିକ ଆଧାରରେ ଏକ ବିଜ୍ଞାପନ ଦେଖାଇପାରେ।\n\nଯଦି ଆପଣ ଏକ ଆପକୁ ବ୍ଲକ କରନ୍ତି, ଏହା ଆଉ କୌଣସି ଆଗ୍ରହର ଆକଳନ କରିବ ନାହିଁ। ଆପଣ ଏହାକୁ ଅନବ୍ଲକ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହି ଆପ୍ସର ତାଲିକାରେ ଏହାକୁ ପୁଣି ଯୋଗ କରିହେବ ନାହିଁ। ଆପ ଦ୍ୱାରା ପୂର୍ବରୁ ଆକଳନ କରାଯାଇଥିବା ଆଗ୍ରହଗୁଡ଼ିକ ଡିଲିଟ ହୋଇଯିବ, କିନ୍ତୁ ଆପଣ ଏବେ ବି କିଛି ସମ୍ବନ୍ଧିତ ବିଜ୍ଞାପନ ଦେଖିପାରନ୍ତି।"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"ଆପଣ ବ୍ଲକ କରିଥିବା ଆପ୍ସ"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ଆପ୍ସ ଦ୍ୱାରା ଆକଳନ କରାଯାଇଥିବା ଆଗ୍ରହଗୁଡ଼ିକୁ ରିସେଟ କରନ୍ତୁ"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"ପ୍ରାଇଭେସି ସେଣ୍ଡବକ୍ସ"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"ଆପଣ ପସନ୍ଦ କରୁଥିବା ବିଜ୍ଞାପନଗୁଡ଼ିକ ଆପଣଙ୍କୁ ଦେଖାଇବା ପାଇଁ ଆପ୍ସ ବ୍ୟବହାର କରିପାରୁଥିବା ନୂଆ ଫିଚରଗୁଡ଼ିକ Androidର ଗୋପନୀୟତା ବିଟା ପ୍ରଦାନ କରେ। ଏହି ଟେକ୍ନୋଲୋଜିଗୁଡ଼ିକ ଡିଭାଇସ ଚିହ୍ନଟକାରୀଗୁଡ଼ିକ ବ୍ୟବହାର କରେ ନାହିଁ।\n\nଆପଣ କେଉଁ ପ୍ରକାରର ବିଜ୍ଞାପନଗୁଡ଼ିକ ପାଇଁ ଆଗ୍ରହୀ ହୋଇପାରନ୍ତି ତାହା ଆପ୍ସ ଆକଳନ କରିପାରେ ଏବଂ ସେହି ଆଗ୍ରହଗୁଡ଼ିକୁ ଆପଣଙ୍କ ଡିଭାଇସରେ ସାମୟିକ ଭାବରେ ସେଭ କରିପାରେ। ସମସ୍ତ ୱେବସାଇଟ ଏବଂ ଅନ୍ୟ ଡେଭେଲପରଙ୍କ ଆପ୍ସରେ ଆପଣଙ୍କ କାର୍ଯ୍ୟକଳାପକୁ ଟ୍ରାକ ନକରି, ଏହା ପ୍ରାସଙ୍ଗିକ ବିଜ୍ଞାପନଗୁଡ଼ିକ ଆପଣଙ୍କୁ ଦେଖାଇବା ପାଇଁ ଅନୁମତି ଦିଏ।"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ବ୍ଲକ କରାଯାଇଥିବା କୌଣସି ଆପ୍ସ ଆପଣଙ୍କର ନାହିଁ"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ଆପଣଙ୍କର କୌଣସି ବ୍ଲକ କରାଯାଇଥିବା ଆଗ୍ରହ ନାହିଁ"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"ବାତିଲ କରନ୍ତୁ"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"ପ୍ରାଇଭେସି ସେଣ୍ଡବକ୍ସକୁ ବନ୍ଦ କରିବେ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"ଯଦି ଆପଣ ଆପଣଙ୍କ ମନ ପରିବର୍ତ୍ତନ କରନ୍ତି କିମ୍ବା Androidର ବିଜ୍ଞାପନ ଗୋପନୀୟତା ବିଟା ବିଷୟରେ ଅଧିକ ଜାଣିବାକୁ ଚାହାଁନ୍ତି, ତେବେ ଆପଣଙ୍କ ଗୋପନୀୟତା ସେଟିଂସକୁ ଯାଆନ୍ତୁ"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g>କୁ ବ୍ଲକ କରିବେ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ଏହି ଆଗ୍ରହକୁ ବ୍ଲକ କରିଦିଆଯିବ ଏବଂ ଆପଣ ଏହାକୁ ପୁଣି ଯୋଗ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଆପଣଙ୍କ ତାଲିକାରେ ଏହାକୁ ପୁନର୍ବାର ଯୋଗ କରିହେବ ନାହିଁ। ଆପଣ ଏବେ ବି କିଛି ସମ୍ବନ୍ଧିତ ବିଜ୍ଞାପନ ଦେଖିପାରନ୍ତି।"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ବ୍ଲକ କରନ୍ତୁ"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g>କୁ ଅନବ୍ଲକ କରାଯାଇଛି"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ଆପଣଙ୍କ ତାଲିକାରେ ଏହି ଆଗ୍ରହକୁ ପୁଣି ଯୋଗ କରିପାରେ, କିନ୍ତୁ ଏହା ତୁରନ୍ତ ଦେଖାଯାଇନପାରେ"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ଠିକ୍ ଅଛି"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"ଆପଣଙ୍କର ସମସ୍ତ ଆଗ୍ରହକୁ ରିସେଟ କରିବେ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"ଆପଣଙ୍କ ତାଲିକା ଖାଲି ହୋଇଯିବ ଏବଂ ଭବିଷ୍ୟତରେ ନୂଆ ଆଗ୍ରହଗୁଡ଼ିକୁ ଆକଳନ କରାଯିବ। ଆପଣ ଏବେ ବି ଖାଲି କରାଯାଇଥିବା ଆଗ୍ରହ ସମ୍ବନ୍ଧିତ କିଛି ବିଜ୍ଞାପନ ଦେଖିପାରନ୍ତି।"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"ରିସେଟ କରନ୍ତୁ"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g>କୁ ବ୍ଲକ କରିବେ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ଏହି ଆପ ପ୍ରାଇଭେସି ସେଣ୍ଡବକ୍ସ ପାଇଁ ଆଗ୍ରହଗୁଡ଼ିକୁ ଆକଳନ କରିବ ନାହିଁ ଏବଂ ଆପଣ ଏହାକୁ ଅନବ୍ଲକ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଆପଣଙ୍କ ତାଲିକାରେ ଏହାକୁ ପୁଣି ଯୋଗ କରିହେବ ନାହିଁ।\n\nଏହି ଆପ ଦ୍ୱାରା ପୂର୍ବରୁ ଆକଳନ କରାଯାଇଥିବା ଆଗ୍ରହଗୁଡ଼ିକ ଡିଲିଟ ହୋଇଯିବ, କିନ୍ତୁ ଆପଣ ଏବେ ବି କିଛି ସମ୍ବନ୍ଧିତ ବିଜ୍ଞାପନ ଦେଖିପାରନ୍ତି।"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g>କୁ ଅନବ୍ଲକ କରାଯାଇଛି"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ଏହି ଆପ ଆପଣଙ୍କ ପାଇଁ ଆଗ୍ରହଗୁଡ଼ିକୁ ପୁଣି ଆକଳନ କରିପାରିବ, କିନ୍ତୁ ସେଗୁଡ଼ିକ ତୁରନ୍ତ ଆପଣଙ୍କ ତାଲିକାରେ ଦେଖାଯାଇନପାରେ। ସମ୍ବନ୍ଧିତ ବିଜ୍ଞାପନଗୁଡ଼ିକୁ ଆପଣ ଦେଖିବା ପାଇଁ କିଛି ସମୟ ଲାଗିପାରେ।"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"ଆପ୍ସ ଦ୍ୱାରା ସୃଷ୍ଟି ହୋଇଥିବା ଆଗ୍ରହଗୁଡ଼ିକୁ ରିସେଟ କରିବେ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"ଆପଣଙ୍କ ତାଲିକାରେ ଥିବା ଆପ୍ସ ଦ୍ୱାରା ଅନୁମାନ କରାଯାଇଥିବା ଆଗ୍ରହଗୁଡ଼ିକୁ ପ୍ରାଇଭେସି ସେଣ୍ଡବକ୍ସରୁ ଡିଲିଟ କରାଯିବ ଏବଂ ଭବିଷ୍ୟତରେ ଆପ୍ସ ନୂଆ ଆଗ୍ରହଗୁଡ଼ିକୁ ଆକଳନ କରିବ। ଆପଣ ଏବେ ବି କିଛି ସମ୍ବନ୍ଧିତ ବିଜ୍ଞାପନ ଦେଖିପାରନ୍ତି।"</string>
<string name="topic10001" msgid="1636806320891333775">"କଳା ଏବଂ ମନୋରଞ୍ଜନ"</string>
<string name="topic10002" msgid="1226367977754287428">"ଅଭିନୟ ଏବଂ ଥିଏଟର"</string>
<string name="topic10003" msgid="6949890838957814881">"ଆନିମେ ଏବଂ ମାଙ୍ଗା"</string>
diff --git a/adservices/apk/res/values-pa/strings.xml b/adservices/apk/res/values-pa/strings.xml
index 14346b2d0..2a4f29d48 100644
--- a/adservices/apk/res/values-pa/strings.xml
+++ b/adservices/apk/res/values-pa/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"ਭਾਗ ਲੈਣ ਦਾ ਤਰੀਕਾ"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"ਬੀਟਾ ਨੂੰ ਚਾਲੂ ਕਰਨ ਨਾਲ ਐਪਾਂ ਨੂੰ ਤੁਹਾਨੂੰ ਵਿਗਿਆਪਨ ਦਿਖਾਉਣ ਲਈ ਇਨ੍ਹਾਂ ਨਵੇਂ, ਹੋਰ ਜ਼ਿਆਦਾ ਨਿੱਜੀ ਤਰੀਕਿਆਂ ਦੀ ਜਾਂਚ ਕਰਨ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ। ਤੁਸੀਂ ਆਪਣੀਆਂ ਪਰਦੇਦਾਰੀ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਕਿਸੇ ਵੇਲੇ ਵੀ ਬੀਟਾ ਨੂੰ ਬੰਦ ਕਰ ਸਕਦੇ ਹੋ।"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ਹਾਂ, ਮੈਂ ਸ਼ਾਮਲ ਹੋਣ ਲਈ ਤਿਆਰ ਹਾਂ"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ਚਾਲੂ ਕਰੋ"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"ਹੋਰ"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"ਭਾਗੀਦਾਰੀ ਲਈ ਤੁਹਾਡਾ ਧੰਨਵਾਦ"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"ਤੁਸੀਂ Android ਦੇ ਵਿਗਿਆਪਨ ਪਰਦੇਦਾਰੀ ਸੰਬੰਧੀ ਬੀਟਾ ਵਰਜਨ ਦਾ ਹਿੱਸਾ ਹੋ। ਪ੍ਰਾਈਵੇਸੀ ਸੈਂਡਬਾਕਸ ਨੂੰ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਲਈ ਚਾਲੂ ਕੀਤਾ ਗਿਆ ਹੈ।\n\nਤੁਸੀਂ ਆਪਣੀਆਂ ਪਰਦੇਦਾਰੀ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਹੋਰ ਜਾਣ ਸਕਦੇ ਹੋ ਜਾਂ ਕਿਸੇ ਵੀ ਸਮੇਂ ਬੀਟਾ ਨੂੰ ਬੰਦ ਕਰ ਸਕਦੇ ਹੋ।"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"ਤੁਸੀਂ ਭਾਗੀਦਾਰੀ ਨਾ ਕਰਨ ਦੀ ਚੋਣ ਕੀਤੀ ਹੈ"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ਇਸ ਵੇਲੇ ਦਿਖਾਉਣ ਲਈ ਕੋਈ ਦਿਲਚਸਪੀ ਨਹੀਂ ਹੈ"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"ਪ੍ਰਾਈਵੇਸੀ ਸੈਂਡਬਾਕਸ"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android ਦਾ ਵਿਗਿਆਪਨ ਪਰਦੇਦਾਰੀ ਸੰਬੰਧੀ ਬੀਟਾ ਵਰਜਨ ਨਵੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਮੁਹੱਈਆ ਕਰਵਾਉਂਦਾ ਹੈ, ਜਿਨ੍ਹਾਂ ਨੂੰ ਐਪਾਂ ਤੁਹਾਡੇ ਮਨਪਸੰਦ ਵਿਗਿਆਪਨ ਦਿਖਾਉਣ ਲਈ ਵਰਤ ਸਕਦੀਆਂ ਹਨ। ਇਹ ਤਕਨਾਲੋਜੀਆਂ ਡੀਵਾਈਸ ਪਛਾਣਕਰਤਾਵਾਂ ਦੀ ਵਰਤੋਂ ਨਹੀਂ ਕਰਦੀਆਂ।\n\nAndroid ਤੁਹਾਡੇ ਦਿਲਚਸਪੀ ਵਾਲੇ ਵਿਗਿਆਪਨਾਂ ਦੀਆਂ ਕਿਸਮਾਂ ਬਾਰੇ ਅੰਦਾਜ਼ਾ ਲਗਾ ਸਕਦਾ ਹੈ, ਅਤੇ ਉਨ੍ਹਾਂ ਦਿਲਚਸਪੀਆਂ ਨੂੰ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਕੁਝ ਸਮੇਂ ਲਈ ਰੱਖਿਅਤ ਕਰ ਸਕਦਾ ਹੈ। ਇਸ ਨਾਲ ਐਪਾਂ ਨੂੰ ਹੋਰ ਵਿਕਾਸਕਾਰਾਂ ਦੀਆਂ ਵੈੱਬਸਾਈਟਾਂ ਅਤੇ ਐਪਾਂ \'ਤੇ ਤੁਹਾਡੀ ਸਰਗਰਮੀ ਨੂੰ ਟਰੈਕ ਕੀਤੇ ਬਿਨਾਂ, ਤੁਹਾਨੂੰ ਢੁਕਵੇਂ ਵਿਗਿਆਪਨ ਦਿਖਾਉਣ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਬਲਾਕ ਕੀਤੀ ਦਿਲਚਸਪੀ ਨਹੀਂ ਹੈ"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"ਐਪਾਂ ਤੁਹਾਡੀਆਂ ਦਿਲਚਸਪੀਆਂ ਬਾਰੇ ਅੰਦਾਜ਼ਾ ਲਗਾ ਸਕਦੀਆਂ ਹਨ ਅਤੇ ਉਨ੍ਹਾਂ ਨੂੰ Android ਨਾਲ ਕੁਝ ਸਮੇਂ ਲਈ ਰੱਖਿਅਤ ਕਰ ਸਕਦੀਆਂ ਹਨ। ਬਾਅਦ ਵਿੱਚ, ਕੋਈ ਵੱਖਰੀ ਐਪ ਇਨ੍ਹਾਂ ਦਿਲਚਸਪੀਆਂ ਦੇ ਆਧਾਰ \'ਤੇ ਤੁਹਾਨੂੰ ਵਿਗਿਆਪਨ ਦਿਖਾ ਸਕਦੀ ਹੈ।\n\nਜੇ ਤੁਸੀਂ ਕਿਸੇ ਐਪ ਨੂੰ ਬਲਾਕ ਕਰਦੇ ਹੋ, ਤਾਂ ਇਹ ਕਿਸੇ ਹੋਰ ਦਿਲਚਸਪੀ ਬਾਰੇ ਅੰਦਾਜ਼ਾ ਨਹੀਂ ਲਗਾ ਸਕੇਗੀ। ਇਸਨੂੰ ਉਦੋਂ ਤੱਕ ਐਪਾਂ ਦੀ ਇਸ ਸੂਚੀ ਵਿੱਚ ਦੁਬਾਰਾ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ, ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਸਨੂੰ ਅਣਬਲਾਕ ਨਹੀਂ ਕਰ ਦਿੰਦੇ। ਐਪ ਵੱਲੋਂ ਪਹਿਲਾਂ ਤੋਂ ਅੰਦਾਜ਼ਨ ਦਿਲਚਸਪੀਆਂ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ, ਪਰ ਤੁਹਾਨੂੰ ਹਾਲੇ ਵੀ ਕੁਝ ਸੰਬੰਧਿਤ ਵਿਗਿਆਪਨ ਦਿਸ ਸਕਦੇ ਹਨ।"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤੀਆਂ ਗਈਆਂ ਐਪਾਂ"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ਐਪਾਂ ਦੀਆਂ ਅੰਦਾਜ਼ਨ ਦਿਲਚਸਪੀਆਂ ਨੂੰ ਰੀਸੈੱਟ ਕਰੋ"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"ਪ੍ਰਾਈਵੇਸੀ ਸੈਂਡਬਾਕਸ"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android ਦਾ ਵਿਗਿਆਪਨ ਪਰਦੇਦਾਰੀ ਸੰਬੰਧੀ ਬੀਟਾ ਵਰਜਨ ਨਵੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਮੁਹੱਈਆ ਕਰਵਾਉਂਦਾ ਹੈ, ਜਿਨ੍ਹਾਂ ਨੂੰ ਐਪਾਂ ਤੁਹਾਡੇ ਮਨਪਸੰਦ ਵਿਗਿਆਪਨ ਦਿਖਾਉਣ ਲਈ ਵਰਤ ਸਕਦੀਆਂ ਹਨ। ਇਹ ਤਕਨਾਲੋਜੀਆਂ ਡੀਵਾਈਸ ਪਛਾਣਕਰਤਾਵਾਂ ਦੀ ਵਰਤੋਂ ਨਹੀਂ ਕਰਦੀਆਂ।\n\nਐਪਾਂ ਤੁਹਾਡੇ ਦਿਲਚਸਪੀ ਵਾਲੇ ਵਿਗਿਆਪਨਾਂ ਦੀਆਂ ਕਿਸਮਾਂ ਬਾਰੇ ਅੰਦਾਜ਼ਾ ਲਗਾ ਸਕਦੀਆਂ ਹਨ, ਅਤੇ ਉਨ੍ਹਾਂ ਦਿਲਚਸਪੀਆਂ ਨੂੰ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਕੁਝ ਸਮੇਂ ਲਈ ਰੱਖਿਅਤ ਕਰ ਸਕਦੀਆਂ ਹਨ। ਇਸ ਨਾਲ ਐਪਾਂ ਨੂੰ ਹੋਰ ਵਿਕਾਸਕਾਰਾਂ ਦੀਆਂ ਵੈੱਬਸਾਈਟਾਂ ਅਤੇ ਐਪਾਂ \'ਤੇ ਤੁਹਾਡੀ ਸਰਗਰਮੀ ਨੂੰ ਟਰੈਕ ਕੀਤੇ ਬਿਨਾਂ, ਤੁਹਾਨੂੰ ਢੁਕਵੇਂ ਵਿਗਿਆਪਨ ਦਿਖਾਉਣ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਬਲਾਕ ਕੀਤੀ ਐਪ ਨਹੀਂ ਹੈ"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਬਲਾਕ ਕੀਤੀ ਦਿਲਚਸਪੀ ਨਹੀਂ ਹੈ"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"ਰੱਦ ਕਰੋ"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"ਕੀ ਪ੍ਰਾਈਵੇਸੀ ਸੈਂਡਬਾਕਸ ਨੂੰ ਬੰਦ ਕਰਨਾ ਹੈ?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"ਜੇ ਤੁਸੀਂ ਆਪਣਾ ਮਨ ਬਦਲਦੇ ਹੋ ਜਾਂ Android ਦੇ ਵਿਗਿਆਪਨ ਪਰਦੇਦਾਰੀ ਬੀਟਾ ਬਾਰੇ ਹੋਰ ਜਾਣਨਾ ਚਾਹੁੰਦੇ ਹੋ, ਤਾਂ ਆਪਣੀਆਂ ਪਰਦੇਦਾਰੀ ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ਬੰਦ ਕਰੋ"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"ਕੀ <xliff:g id="TOPIC">%1$s</xliff:g> ਨੂੰ ਬਲਾਕ ਕਰਨਾ ਹੈ?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ਇਸ ਦਿਲਚਸਪੀ ਨੂੰ ਬਲਾਕ ਕਰ ਦਿੱਤਾ ਜਾਵੇਗਾ ਅਤੇ ਇਸਨੂੰ ਤੁਹਾਡੀ ਸੂਚੀ ਵਿੱਚ ਉਦੋਂ ਤੱਕ ਦੁਬਾਰਾ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ, ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਸਨੂੰ ਵਾਪਸ ਸ਼ਾਮਲ ਨਹੀਂ ਕਰਦੇ। ਤੁਹਾਨੂੰ ਹਾਲੇ ਵੀ ਕੁਝ ਸੰਬੰਧਿਤ ਵਿਗਿਆਪਨ ਦਿਸ ਸਕਦੇ ਹਨ।"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"ਬਲਾਕ ਕਰੋ"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> ਨੂੰ ਅਣਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ਤੁਹਾਡੀ ਸੂਚੀ ਵਿੱਚ ਇਸ ਦਿਲਚਸਪੀ ਨੂੰ ਦੁਬਾਰਾ ਸ਼ਾਮਲ ਕਰ ਸਕਦਾ ਹੈ, ਪਰ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਇਹ ਤੁਰੰਤ ਨਾ ਦਿਸੇ"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ਠੀਕ ਹੈ"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"ਕੀ ਤੁਹਾਡੀਆਂ ਸਾਰੀਆਂ ਦਿਲਚਸਪੀਆਂ ਨੂੰ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"ਤੁਹਾਡੀ ਸੂਚੀ ਨੂੰ ਕਲੀਅਰ ਕੀਤਾ ਜਾਵੇਗਾ ਅਤੇ ਅੱਗੇ ਵਾਸਤੇ ਨਵੀਆਂ ਦਿਲਚਸਪੀਆਂ ਦਾ ਅੰਦਾਜ਼ਾ ਲਗਾਇਆ ਜਾਵੇਗਾ। ਤੁਹਾਨੂੰ ਹਾਲੇ ਵੀ ਕਲੀਅਰ ਕੀਤੀਆਂ ਦਿਲਚਸਪੀਆਂ ਨਾਲ ਸੰਬੰਧਿਤ ਕੁਝ ਵਿਗਿਆਪਨ ਦਿਸ ਸਕਦੇ ਹਨ।"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"ਰੀਸੈੱਟ ਕਰੋ"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"ਕੀ <xliff:g id="APP">%1$s</xliff:g> ਨੂੰ ਬਲਾਕ ਕਰਨਾ ਹੈ?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ਇਹ ਐਪ ਪ੍ਰਾਈਵੇਸੀ ਸੈਂਡਬਾਕਸ ਲਈ ਦਿਲਚਸਪੀਆਂ ਦਾ ਅੰਦਾਜ਼ਾ ਨਹੀਂ ਲਗਾਏਗੀ ਅਤੇ ਇਸਨੂੰ ਤੁਹਾਡੀ ਸੂਚੀ ਵਿੱਚ ਉਦੋਂ ਤੱਕ ਦੁਬਾਰਾ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ, ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਸਨੂੰ ਅਣਬਲਾਕ ਨਹੀਂ ਕਰਦੇ।\n\nਇਸ ਐਪ ਵੱਲੋਂ ਪਹਿਲਾਂ ਤੋਂ ਅੰਦਾਜ਼ਨ ਦਿਲਚਸਪੀਆਂ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ, ਪਰ ਤੁਹਾਨੂੰ ਹਾਲੇ ਵੀ ਕੁਝ ਸੰਬੰਧਿਤ ਵਿਗਿਆਪਨ ਦਿਸ ਸਕਦੇ ਹਨ।"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> ਨੂੰ ਅਣਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਲਈ ਦੁਬਾਰਾ ਦਿਲਚਸਪੀਆਂ ਦਾ ਅੰਦਾਜ਼ਾ ਲਗਾ ਸਕਦੀ ਹੈ, ਪਰ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਇਹ ਤੁਰੰਤ ਤੁਹਾਡੀ ਸੂਚੀ ਵਿੱਚ ਨਾ ਦਿਸਣ। ਤੁਹਾਨੂੰ ਸੰਬੰਧਿਤ ਵਿਗਿਆਪਨ ਦਿਸਣ ਵਿੱਚ ਕੁਝ ਸਮਾਂ ਲੱਗ ਸਕਦਾ ਹੈ।"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"ਕੀ ਐਪਾਂ ਵੱਲੋਂ ਸਿਰਜੀਆਂ ਗਈਆਂ ਦਿਲਚਸਪੀਆਂ ਨੂੰ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"ਤੁਹਾਡੀ ਸੂਚੀ ਦੀਆਂ ਐਪਾਂ ਦੁਆਰਾ ਅੰਦਾਜ਼ਾ ਲਗਾਈਆਂ ਦਿਲਚਸਪੀਆਂ ਨੂੰ ਪ੍ਰਾਈਵੇਸੀ ਸੈਂਡਬਾਕਸ ਤੋਂ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ ਅਤੇ ਉਹ ਐਪਾਂ ਅੱਗੇ ਵਾਸਤੇ ਨਵੀਆਂ ਦਿਲਚਸਪੀਆਂ ਦਾ ਅੰਦਾਜ਼ਾ ਲਗਾਉਣਗੀਆਂ। ਤੁਹਾਨੂੰ ਹਾਲੇ ਵੀ ਕੁਝ ਸੰਬੰਧਿਤ ਵਿਗਿਆਪਨ ਦਿਸ ਸਕਦੇ ਹਨ।"</string>
<string name="topic10001" msgid="1636806320891333775">"ਕਲਾ ਅਤੇ ਮਨੋਰੰਜਨ"</string>
<string name="topic10002" msgid="1226367977754287428">"ਅਦਾਕਾਰੀ ਅਤੇ ਥੀਏਟਰ"</string>
<string name="topic10003" msgid="6949890838957814881">"ਐਨੀਮੇ ਅਤੇ ਮਾਂਗਾ"</string>
diff --git a/adservices/apk/res/values-pl/strings.xml b/adservices/apk/res/values-pl/strings.xml
index 1fc08e5b7..f08160547 100644
--- a/adservices/apk/res/values-pl/strings.xml
+++ b/adservices/apk/res/values-pl/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Zasady uczestnictwa"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Włączenie wersji beta umożliwia testowanie w aplikacjach nowych sposobów wyświetlania Ci reklam – z lepszą ochroną prywatności. W każdej chwili możesz wyłączyć wersję beta w ustawieniach prywatności."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nie, dziękuję"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Tak, chcę wziąć udział"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Włącz"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Więcej"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Dziękujemy za udział w programie"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Bierzesz udział w testach wersji beta ustawień prywatności w reklamach na Androidzie. Masz na urządzeniu włączoną Piaskownicę prywatności.\n\nW ustawieniach prywatności możesz dowiedzieć się więcej i wyłączyć wersję beta."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Nie zamierzasz brać udziału w programie"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Obecnie nie można wyświetlić żadnych zainteresowań"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Piaskownica prywatności"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Wersja beta ustawień prywatności w reklamach na Androidzie oferuje nowe funkcje, których aplikacje mogą używać do wyświetlania Ci trafnie dobranych reklam. Te technologie nie korzystają z identyfikatorów urządzeń.\n\nAndroid może oszacować, jakie rodzaje reklam będą Ci odpowiadać, i tymczasowo zapisać Twoje zainteresowania na urządzeniu. Dzięki temu reklamy w aplikacjach będą lepiej dobrane, a Twoja aktywność na stronach internetowych i w aplikacjach innych deweloperów nie będzie śledzona."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nie masz zablokowanych zainteresowań"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikacje mogą szacować Twoje zainteresowania i tymczasowo zapisywać je na Androidzie. Następnie inna aplikacja może pokazać Ci reklamy na tej podstawie.\n\nZablokowane aplikacje nie będą już szacować zainteresowań. Dopóki ich nie odblokujesz, nie pojawią się na liście aplikacji. Zainteresowania określone wcześniej przez te aplikacje zostaną usunięte, ale możliwe, że wciąż będą Ci się wyświetlać reklamy powiązane z tymi obszarami."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Zablokowane przez Ciebie aplikacje"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Zresetuj zainteresowania oszacowane przez aplikacje"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Piaskownica prywatności"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Wersja beta ustawień prywatności w reklamach na Androidzie oferuje nowe funkcje, których aplikacje mogą używać do wyświetlania Ci trafnie dobranych reklam. Te technologie nie korzystają z identyfikatorów urządzeń.\n\nAplikacje mogą oszacować, jakie rodzaje reklam będą Ci odpowiadać, i tymczasowo zapisać Twoje zainteresowania na urządzeniu. Dzięki temu reklamy w aplikacjach będą lepiej dobrane, a Twoja aktywność na stronach internetowych i w aplikacjach innych deweloperów nie będzie śledzona."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nie masz zablokowanych aplikacji"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nie masz zablokowanych zainteresowań"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Anuluj"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Wyłączyć Piaskownicę prywatności?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Jeśli zmienisz zdanie lub zechcesz dowiedzieć się więcej o wersji beta prywatności w reklamach na Androidzie, otwórz ustawienia prywatności"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Wyłącz"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Zablokować temat <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Te zainteresowania zostaną zablokowane i nie zostaną ponownie dodane do listy, chyba że zrobisz to samodzielnie. Wciąż możesz widzieć pewne powiązane reklamy."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Zablokuj"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Temat <xliff:g id="TOPIC">%1$s</xliff:g> jest odblokowany"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android może ponownie dodać te zainteresowania do Twojej listy, ale możliwe, że nie pojawią się na niej od razu"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Zresetować wszystkie Twoje zainteresowania?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Lista zostanie wyczyszczona, a zainteresowania będą teraz szacowane od nowa. Wciąż możesz widzieć pewne reklamy powiązane z usuniętymi zainteresowaniami."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Resetuj"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Zablokować aplikację <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ta aplikacja nie będzie szacować Twoich zainteresowań na potrzeby Piaskownicy prywatności i nie zostanie ponownie dodana do listy, dopóki jej nie odblokujesz.\n\nZainteresowania wcześniej oszacowane przez tę aplikację zostaną usunięte, ale wciąż możesz widzieć pewne powiązane reklamy."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Aplikacja <xliff:g id="APP">%1$s</xliff:g> jest odblokowana"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Ta aplikacja ponownie może szacować Twoje zainteresowania, ale możliwe, że nie pojawi się od razu na liście. Zanim zobaczysz powiązane reklamy, może minąć trochę czasu."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Zresetować zainteresowania wygenerowane przez aplikacje?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Oszacowane przez aplikacje zainteresowania z listy zostaną usunięte z Piaskownicy prywatności, a aplikacje będą teraz od nowa szacować zainteresowania. Wciąż możesz widzieć pewne powiązane reklamy."</string>
<string name="topic10001" msgid="1636806320891333775">"Sztuka i rozrywka"</string>
<string name="topic10002" msgid="1226367977754287428">"Aktorstwo i teatr"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime i manga"</string>
diff --git a/adservices/apk/res/values-pt-rBR/strings.xml b/adservices/apk/res/values-pt-rBR/strings.xml
index 0d78d65f2..007a4c3ba 100644
--- a/adservices/apk/res/values-pt-rBR/strings.xml
+++ b/adservices/apk/res/values-pt-rBR/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Como participar"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ativar a versão Beta permite que os apps testem essas formas novas e mais particulares de mostrar anúncios. Você pode desativar a versão Beta a qualquer momento nas configurações de privacidade."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Agora não"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Sim, vou participar"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Ativar"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Mais"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Agradecemos a participação"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Você está participando do programa Beta da privacidade de anúncios do Android. O Sandbox de privacidade está ativado para seu dispositivo.\n\nSaiba mais ou desative a versão Beta a qualquer momento nas configurações de privacidade."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Você preferiu não participar"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Não há interesses para mostrar no momento"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Sandbox de privacidade"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"A versão Beta da privacidade de anúncios do Android oferece novos recursos usados pelos apps para mostrar anúncios que você pode gostar. Essas tecnologias não usam identificadores de dispositivo.\n\nO Android pode estimar os tipos de anúncios que podem ser do seu interesse e salvar essa informação temporariamente no seu dispositivo. Isso permite que os apps mostrem anúncios relevantes sem rastrear sua atividade nos sites e apps de outros desenvolvedores."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Você não tem interesses bloqueados"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Os apps podem estimar e salvar seus interesses temporariamente com o Android. Depois, outro app pode mostrar um anúncio com base nesses interesses.\n\nSe você bloquear um app, ele não vai mais estimar interesses. Ele não vai ser adicionado à lista de apps novamente, a menos que você o desbloqueie. Os interesses já estimados pelo app vão ser excluídos, mas você talvez ainda encontre alguns anúncios relacionados."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps bloqueados"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Redefinir interesses estimados por apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Sandbox de privacidade"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"A versão Beta da privacidade de anúncios do Android oferece novos recursos usados pelos apps para mostrar anúncios que você pode gostar. Essas tecnologias não usam identificadores de dispositivo.\n\nOs apps podem estimar os tipos de anúncios que podem ser do seu interesse e salvar essa informação temporariamente no seu dispositivo. Isso permite que os apps mostrem anúncios relevantes sem rastrear sua atividade nos sites e apps de outros desenvolvedores."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Você não tem apps bloqueados"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Você não tem interesses bloqueados"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancelar"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Desativar o Sandbox de privacidade?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Se você mudar de ideia ou quiser saber mais sobre a versão Beta da privacidade de anúncios do Android, acesse as configurações de privacidade."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Desativar"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Bloquear <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Esse interesse vai ser bloqueado, e só você vai poder adicioná-lo à sua lista novamente. Talvez você ainda encontre alguns anúncios relacionados ao interesse bloqueado."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloquear"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"O app <xliff:g id="TOPIC">%1$s</xliff:g> foi desbloqueado"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"O Android pode adicionar novamente esse interesse à sua lista, mas ele pode não aparecer de forma imediata."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Quer mesmo redefinir todos os seus interesses?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Sua lista vai ser limpa, e novos interesses serão estimados daqui em diante. Talvez você ainda veja alguns anúncios relacionados a interesses excluídos."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Redefinir"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Bloquear <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Este app não vai estimar os interesses do Sandbox de privacidade e só poderá ser adicionado à sua lista novamente se for desbloqueado.\n\nOs interesses já estimados por este app vão ser excluídos, mas você ainda poderá ver alguns anúncios relacionados."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"O app <xliff:g id="APP">%1$s</xliff:g> foi desbloqueado"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Este app pode voltar a estimar interesses para você, mas eles podem não aparecer na sua lista de forma imediata. Pode demorar um pouco para você ver anúncios relacionados."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Quer mesmo redefinir os interesses gerados por apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Os interesses estimados pelos apps em sua lista vão ser excluídos do Sandbox de privacidade, e novos interesses serão estimados daqui em diante. Talvez você ainda encontre alguns anúncios relacionados ao interesse bloqueado."</string>
<string name="topic10001" msgid="1636806320891333775">"Artes e entretenimento"</string>
<string name="topic10002" msgid="1226367977754287428">"Atuação e cinema"</string>
<string name="topic10003" msgid="6949890838957814881">"Animê e Mangá"</string>
diff --git a/adservices/apk/res/values-pt-rPT/strings.xml b/adservices/apk/res/values-pt-rPT/strings.xml
index 464d08aec..5d4cb940c 100644
--- a/adservices/apk/res/values-pt-rPT/strings.xml
+++ b/adservices/apk/res/values-pt-rPT/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Como participar"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ativar a versão beta permite que as apps testem estas novas formas mais privadas de lhe mostrar anúncios. Pode desativar a versão beta em qualquer altura nas suas definições de privacidade."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Não"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Sim, quero aderir"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Ativar"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Mais"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Agradecemos a sua participação"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Faz parte da versão beta da privacidade de anúncios do Android O Privacy Sandbox está ativado para o seu dispositivo.\n\nPode saber mais ou desativar a versão beta em qualquer altura nas suas definições de privacidade."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Optou por não participar"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Não existem interesses para mostrar neste momento"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"A versão beta da privacidade de anúncios do Android fornece novas funcionalidades que as apps podem usar para lhe mostrar anúncios de que pode gostar. Estas tecnologias não usam identificadores de dispositivos.\n\nO Android podem estimar os tipos de anúncios em que pode ter interesse e guardar estes interesses temporariamente no seu dispositivo. Isto permite que as apps lhe mostrem anúncios relevantes, sem monitorizar a sua atividade em Websites e apps de outros programadores."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Não tem interesses bloqueados"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"As apps podem estimar os seus interesses e guardá-los temporariamente com o Android. Posteriormente, uma app diferente pode mostrar-lhe um anúncio com base nestes interesses.\n\nSe bloquear uma app, a mesma deixa de estimar interesses. Não volta a ser adicionada a esta lista de apps, a menos que a desbloqueie. Os interesses já estimados pela app são eliminados, mas pode continuar a ver alguns anúncios relacionados."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps que bloqueou"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Repor interesses estimados pelas apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"A versão beta da privacidade de anúncios do Android fornece novas funcionalidades que as apps podem usar para lhe mostrar anúncios de que pode gostar. Estas tecnologias não usam identificadores de dispositivos.\n\nAs apps podem estimar os tipos de anúncios em que pode ter interesse e guardar estes interesses temporariamente no seu dispositivo. Isto permite que as apps lhe mostrem anúncios relevantes, sem monitorizar a sua atividade em Websites e apps de outros programadores."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Não tem apps bloqueadas"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Não tem interesses bloqueados"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancelar"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Desativar o Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Se mudar de ideias ou quiser saber mais sobre a privacidade de anúncios beta do Android, aceda às definições de privacidade"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Desativar"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Bloquear o tópico <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Este interesse vai ser bloqueado e não vai ser adicionado à sua lista novamente, a menos que o adicione novamente. Ainda pode ver alguns anúncios relacionados."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloquear"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"A app <xliff:g id="TOPIC">%1$s</xliff:g> está desbloqueada"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"O Android pode adicionar este interesse à sua lista novamente, mas pode não aparecer de imediato"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Repor todos os seus interesses?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"A sua lista vai ser limpa, e vão ser estimados novos interesses no futuro. Ainda pode ver alguns anúncios relacionados com os interesses limpos."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Repor"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Bloquear a app <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Esta app não vai estimar interesses para o Privacy Sandbox nem vai ser adicionada novamente à sua lista, a menos que a desbloqueie.\n\nOs interesses já estimados por esta app vão ser eliminados, mas ainda pode ver alguns anúncios relacionados."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"A app <xliff:g id="APP">%1$s</xliff:g> está desbloqueada"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Esta app pode estimar interesses para si novamente, mas pode não aparecer na sua lista de imediato. Pode demorar algum tempo para conseguir ver anúncios relacionados."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Repor interesses gerados pelas apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Os interesses estimados pelas apps na sua lista vão ser eliminados do Privacy Sandbox, e as apps vão estimar novos interesses no futuro. Ainda pode ver alguns anúncios relacionados."</string>
<string name="topic10001" msgid="1636806320891333775">"Artes e entretenimento"</string>
<string name="topic10002" msgid="1226367977754287428">"Representação e teatro"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime e manga"</string>
diff --git a/adservices/apk/res/values-pt/strings.xml b/adservices/apk/res/values-pt/strings.xml
index 0d78d65f2..007a4c3ba 100644
--- a/adservices/apk/res/values-pt/strings.xml
+++ b/adservices/apk/res/values-pt/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Como participar"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ativar a versão Beta permite que os apps testem essas formas novas e mais particulares de mostrar anúncios. Você pode desativar a versão Beta a qualquer momento nas configurações de privacidade."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Agora não"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Sim, vou participar"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Ativar"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Mais"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Agradecemos a participação"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Você está participando do programa Beta da privacidade de anúncios do Android. O Sandbox de privacidade está ativado para seu dispositivo.\n\nSaiba mais ou desative a versão Beta a qualquer momento nas configurações de privacidade."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Você preferiu não participar"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Não há interesses para mostrar no momento"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Sandbox de privacidade"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"A versão Beta da privacidade de anúncios do Android oferece novos recursos usados pelos apps para mostrar anúncios que você pode gostar. Essas tecnologias não usam identificadores de dispositivo.\n\nO Android pode estimar os tipos de anúncios que podem ser do seu interesse e salvar essa informação temporariamente no seu dispositivo. Isso permite que os apps mostrem anúncios relevantes sem rastrear sua atividade nos sites e apps de outros desenvolvedores."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Você não tem interesses bloqueados"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Os apps podem estimar e salvar seus interesses temporariamente com o Android. Depois, outro app pode mostrar um anúncio com base nesses interesses.\n\nSe você bloquear um app, ele não vai mais estimar interesses. Ele não vai ser adicionado à lista de apps novamente, a menos que você o desbloqueie. Os interesses já estimados pelo app vão ser excluídos, mas você talvez ainda encontre alguns anúncios relacionados."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Apps bloqueados"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Redefinir interesses estimados por apps"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Sandbox de privacidade"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"A versão Beta da privacidade de anúncios do Android oferece novos recursos usados pelos apps para mostrar anúncios que você pode gostar. Essas tecnologias não usam identificadores de dispositivo.\n\nOs apps podem estimar os tipos de anúncios que podem ser do seu interesse e salvar essa informação temporariamente no seu dispositivo. Isso permite que os apps mostrem anúncios relevantes sem rastrear sua atividade nos sites e apps de outros desenvolvedores."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Você não tem apps bloqueados"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Você não tem interesses bloqueados"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Cancelar"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Desativar o Sandbox de privacidade?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Se você mudar de ideia ou quiser saber mais sobre a versão Beta da privacidade de anúncios do Android, acesse as configurações de privacidade."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Desativar"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Bloquear <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Esse interesse vai ser bloqueado, e só você vai poder adicioná-lo à sua lista novamente. Talvez você ainda encontre alguns anúncios relacionados ao interesse bloqueado."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloquear"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"O app <xliff:g id="TOPIC">%1$s</xliff:g> foi desbloqueado"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"O Android pode adicionar novamente esse interesse à sua lista, mas ele pode não aparecer de forma imediata."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Quer mesmo redefinir todos os seus interesses?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Sua lista vai ser limpa, e novos interesses serão estimados daqui em diante. Talvez você ainda veja alguns anúncios relacionados a interesses excluídos."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Redefinir"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Bloquear <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Este app não vai estimar os interesses do Sandbox de privacidade e só poderá ser adicionado à sua lista novamente se for desbloqueado.\n\nOs interesses já estimados por este app vão ser excluídos, mas você ainda poderá ver alguns anúncios relacionados."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"O app <xliff:g id="APP">%1$s</xliff:g> foi desbloqueado"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Este app pode voltar a estimar interesses para você, mas eles podem não aparecer na sua lista de forma imediata. Pode demorar um pouco para você ver anúncios relacionados."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Quer mesmo redefinir os interesses gerados por apps?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Os interesses estimados pelos apps em sua lista vão ser excluídos do Sandbox de privacidade, e novos interesses serão estimados daqui em diante. Talvez você ainda encontre alguns anúncios relacionados ao interesse bloqueado."</string>
<string name="topic10001" msgid="1636806320891333775">"Artes e entretenimento"</string>
<string name="topic10002" msgid="1226367977754287428">"Atuação e cinema"</string>
<string name="topic10003" msgid="6949890838957814881">"Animê e Mangá"</string>
diff --git a/adservices/apk/res/values-ro/strings.xml b/adservices/apk/res/values-ro/strings.xml
index 6be426b2f..5e72e94a6 100644
--- a/adservices/apk/res/values-ro/strings.xml
+++ b/adservices/apk/res/values-ro/strings.xml
@@ -47,16 +47,17 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Cum să participi"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Dacă activezi versiunea beta, aplicațiile pot testa noile modalități mai private de a-ți afișa anunțuri. Poți să dezactivezi oricând versiunea beta în setările de confidențialitate."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nu, mulțumesc"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Da, mă înscriu"</string>
- <string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Vă mulțumim pentru participare"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Activează"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Mai mult"</string>
+ <string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Mulțumim pentru participare"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Te-ai înscris în versiunea beta pentru confidențialitatea anunțurilor Android. Privacy Sandbox este activat pentru dispozitivul tău.\n\nPoți să afli mai multe sau să dezactivezi oricând versiunea beta în setările de confidențialitate."</string>
- <string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Ați ales să nu participați"</string>
+ <string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Ai ales să nu participi"</string>
<string name="notificationUI_confirmation_decline_subtitle" msgid="4194388733937808440">"Mulțumim pentru răspuns. Privacy Sandbox este dezactivat pentru dispozitivul tău.\n\nDacă te răzgândești sau dorești să afli mai multe, accesează setările de confidențialitate."</string>
<string name="notificationUI_confirmation_left_control_button_text" msgid="7459283556129950513">"Setări de confidențialitate"</string>
<string name="notificationUI_confirmation_right_control_button_text" msgid="1390803936115621269">"OK"</string>
<string name="notificationUI_container2_title" msgid="3844342008598264655">"Te-ai înscris în versiunea beta"</string>
<string name="notificationUI_container2_body_text" msgid="758560159693325060">"Privacy Sandbox este activat pentru dispozitivul tău și aplicațiile pot testa aceste noi modalități mai private de a-ți afișa anunțuri. Poți să dezactivezi oricând versiunea beta în setările de confidențialitate."</string>
- <string name="notificationUI_left_control_button_text" msgid="6591519663037500665">"Gestionați setările de confidențialitate"</string>
+ <string name="notificationUI_left_control_button_text" msgid="6591519663037500665">"Gestionează setările de confidențialitate"</string>
<string name="notificationUI_right_control_button_text" msgid="4418849554981401536">"OK"</string>
<string name="notificationUI_how_it_works_expanded_text1" msgid="5567641119079971239">"Privacy Sandbox pentru Android oferă noi funcții pe care aplicațiile le pot folosi pentru a-ți afișa anunțuri care ți-ar putea plăcea. Aceste tehnologii nu utilizează identificatorii dispozitivului.\n\nAndroid și aplicațiile tale pot să estimeze tipurile de anunțuri care te-ar putea interesa și salvează temporar categoriile de interese pe dispozitivul tău. Astfel, aplicațiile îți pot afișa anunțuri care ar putea să-ți placă, fără să-ți urmărească activitatea în site-uri și aplicații de la alți dezvoltatori."</string>
<string name="notificationUI_how_it_works_expanded_text2" msgid="6676443093692450581">"Personalizarea anunțurilor cu Privacy Sandbox"</string>
@@ -72,23 +73,40 @@
<string name="settingsUI_main_view_info_text8" msgid="2262800588014737545">"Cuantificarea anunțurilor"</string>
<string name="settingsUI_main_view_info_text9" msgid="5431131706102562540">"Aplicațiile pot folosi Privacy Sandbox pentru a măsura eficiența anunțurilor asociate. În acest scop, advertiserii pot să salveze temporar date pe Android despre interacțiunile tale cu anunțurile și aplicațiile lor. Cantitatea de date pe care o pot salva este limitată și datele vor fi șterse cu regularitate.\n\nPoți să ștergi oricând aceste date dezactivând Privacy Sandbox."</string>
<string name="settingsUI_topics_view_subtitle" msgid="6608135144119617292">"Android estimează periodic categoriile tale de interese principale în funcție de aplicațiile pe care le folosești. Aplicațiile pot solicita permisiunea dispozitivului Android de a folosi acele categorii de interese pentru a-ți afișa anunțuri mai relevante.\n\nDacă blochezi o categorie de interese, aceasta nu va fi adăugată din nou în listă decât dacă o deblochezi. Este posibil să vezi în continuare unele anunțuri asociate."</string>
- <string name="settingsUI_block_topic_title" msgid="1883542291264768402">"Blocați"</string>
- <string name="settingsUI_unblock_topic_title" msgid="1710283575567727667">"Deblocați"</string>
+ <string name="settingsUI_block_topic_title" msgid="1883542291264768402">"Blochează"</string>
+ <string name="settingsUI_unblock_topic_title" msgid="1710283575567727667">"Deblochează"</string>
<string name="settingsUI_blocked_topics_title" msgid="5030422883546261820">"Interese blocate"</string>
- <string name="settingsUI_reset_topics_title" msgid="6917960006583871987">"Resetați toate interesele"</string>
+ <string name="settingsUI_reset_topics_title" msgid="6917960006583871987">"Resetează toate interesele"</string>
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Momentan, nu există categorii de interese de afișat"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Versiunea beta pentru confidențialitatea anunțurilor Android oferă noi funcții pe care aplicațiile le pot folosi pentru a-ți afișa anunțuri care ți-ar putea plăcea. Aceste tehnologii nu utilizează identificatorii dispozitivului.\n\nAndroid poate să estimeze tipurile de anunțuri care te-ar putea interesa și salvează temporar categoriile de interese pe dispozitivul tău. Astfel, aplicațiile îți pot afișa anunțuri relevante, fără să-ți urmărească activitatea în site-uri și aplicații de la alți dezvoltatori."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nu ai blocat nicio categorie de interese"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplicațiile pot să-ți estimeze categoriile de interese și să le salveze temporar pe Android. Mai târziu, altă aplicație îți poate afișa un anunț pe baza acestor interese.\n\nDacă blochezi o aplicație, aceasta nu îți va mai estima interesele. Aplicația nu va fi adăugată din nou în lista de aplicații decât dacă o deblochezi. Categoriile de interese estimate deja de aplicație vor fi șterse, dar este posibil să vezi în continuare câteva anunțuri asociate."</string>
- <string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplicații pe care le-ați blocat"</string>
+ <string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplicații pe care le-ai blocat"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Resetează categoriile de interese estimate de aplicații"</string>
<string name="settingsUI_apps_view_no_apps_text" msgid="7460005016322426137">"Momentan, nicio aplicație nu sugerează categorii de interese"</string>
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Versiunea beta pentru confidențialitatea anunțurilor Android oferă noi funcții pe care aplicațiile le pot folosi pentru a-ți afișa anunțuri care ți-ar putea plăcea. Aceste tehnologii nu utilizează identificatorii dispozitivului.\n\nAplicațiile pot să estimeze tipurile de anunțuri care te-ar putea interesa și salvează temporar categoriile de interese pe dispozitivul tău. Astfel, aplicațiile îți pot afișa anunțuri relevante, fără să-ți urmărească activitatea în site-uri și aplicații de la alți dezvoltatori."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nu ai blocat nicio aplicație"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nu ai blocat nicio categorie de interese"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Anulează"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Dezactivezi Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Dacă te răzgândești sau vrei să afli mai multe despre versiunea beta de confidențialitate a anunțurilor Android, accesează setările de confidențialitate"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Dezactivează"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Blochezi subiectul <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Interesul va fi blocat și nu se va adăuga din nou în listă decât dacă îl adaugi tu. Este posibil să vezi în continuare unele anunțuri asociate."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blochează"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Subiectul <xliff:g id="TOPIC">%1$s</xliff:g> este deblocat"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android poate adăuga din nou acest interes în listă, dar el poate să nu apară imediat"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Resetezi toate interesele?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Lista se va goli și se vor estima noi interese de acum încolo. Este posibil să vezi în continuare unele anunțuri legate de interesele șterse."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Resetează"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Blochezi aplicația <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Aplicația nu estimează interesele pentru Privacy Sandbox și nu se va adăuga din nou în listă decât dacă îi anulezi blocarea.\n\nInteresele deja estimate de aplicație se vor șterge, dar este posibil să vezi totuși anumite anunțuri asociate."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> este deblocată"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Aplicația îți poate estima din nou interesele, dar poate să nu apară imediat în listă. Poate dura o vreme să vezi anunțuri asociate."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Resetezi interesele generate de aplicații?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interesele estimate de aplicațiile din lista noastră se vor șterge din Privacy Sandbox, iar aplicațiile vor estima alte interese de acum încolo. Este posibil să vezi în continuare unele anunțuri asociate."</string>
<string name="topic10001" msgid="1636806320891333775">"Artă și divertisment"</string>
<string name="topic10002" msgid="1226367977754287428">"Actorie și teatru"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime și manga"</string>
@@ -109,7 +127,7 @@
<string name="topic10018" msgid="6823676154109177487">"Filme de acțiune și aventură"</string>
<string name="topic10019" msgid="6875960630918973127">"Filme de animație"</string>
<string name="topic10020" msgid="4323870300602151489">"Filme de comedie"</string>
- <string name="topic10021" msgid="446758643774468418">"Filme de cult și independente"</string>
+ <string name="topic10021" msgid="446758643774468418">"Filme cult și independente"</string>
<string name="topic10022" msgid="3671929742155787005">"Filme documentare"</string>
<string name="topic10023" msgid="3547616830531182863">"Drame"</string>
<string name="topic10024" msgid="1044853632986202120">"Filme pentru familie"</string>
@@ -316,7 +334,7 @@
<string name="topic10225" msgid="8056906319560292131">"Jocuri de cărți"</string>
<string name="topic10226" msgid="714463563336281099">"Jocuri cu cartonașe de colecție"</string>
<string name="topic10227" msgid="218768654597520683">"Jocuri video și pe computer"</string>
- <string name="topic10228" msgid="850597136074192489">"Jocuri de acțiune și pe platformă"</string>
+ <string name="topic10228" msgid="850597136074192489">"Jocuri de acțiune și tip platformă"</string>
<string name="topic10229" msgid="4284501233897149886">"Jocuri de aventură"</string>
<string name="topic10230" msgid="7156822286576815181">"Jocuri ocazionale"</string>
<string name="topic10231" msgid="7620407218897744361">"Competiții de jocuri video"</string>
diff --git a/adservices/apk/res/values-ru/strings.xml b/adservices/apk/res/values-ru/strings.xml
index be2b7d751..5e61c94bc 100644
--- a/adservices/apk/res/values-ru/strings.xml
+++ b/adservices/apk/res/values-ru/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Как принять участие"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Если вы примете участие в бета-тестировании, приложения смогут опробовать новые варианты показа рекламы с более высоким уровнем конфиденциальности. Отказаться от участия можно в любой момент в настройках конфиденциальности."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Нет"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ОК"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Включить"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Ещё"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Благодарим за участие в тестировании"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Вы участвуете в бета-тестировании функции конфиденциального просмотра рекламы в Android. На вашем устройстве включены технологии Privacy Sandbox.\n\nЧтобы ознакомиться с информацией о бета-тестировании или отказаться от участия в нем, перейдите в настройки конфиденциальности."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Вы отказались от участия в программе"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Пока интересов нет."</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"В бета-версии функции конфиденциального просмотра рекламы в Android приложениям предоставлены новые возможности для демонстрации объявлений. При этом не требуется применять идентификаторы устройств.\n\nAndroid сможет определять, реклама на какие темы может вас заинтересовать, и временно хранить эти сведения на вашем устройстве. Так приложения будут показывать релевантные объявления без отслеживания ваших действий на сайтах и в продуктах других разработчиков."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Нет заблокированных интересов"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Приложения могут собирать информацию о ваших интересах и временно хранить ее в Android. Другое приложение может воспользоваться этими данными и показать вам подходящую рекламу.\n\nЕсли вы не хотите, чтобы приложение анализировало ваши интересы, заблокируйте его. Оно не появится в списке, пока вы не снимете блокировку. Собранная информация об интересах будет удалена, но объявления, подобранные на ее основе, иногда могут появляться."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Заблокированные приложения"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Сбросить интересы по данным приложений"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"В бета-версии функции конфиденциального просмотра рекламы в Android приложениям предоставлены новые возможности для демонстрации объявлений. При этом не требуется применять идентификаторы устройств.\n\nПриложения смогут определять, реклама на какие темы может вас заинтересовать, и временно хранить эти сведения на вашем устройстве. Так приложения будут показывать релевантные объявления без отслеживания ваших действий на сайтах и в продуктах других разработчиков."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Нет заблокированных приложений"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Нет заблокированных интересов"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Отмена"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Отключить технологии Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Если вы захотите включить их снова или узнать больше о бета-тестировании программы \"Конфиденциальность при просмотре рекламы в Android\", перейдите в настройки конфиденциальности."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Отключить"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Заблокировать тему \"<xliff:g id="TOPIC">%1$s</xliff:g>\"?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Эта тема будет заблокирована и не появится в списке, пока вы не добавите ее снова. Иногда вы ещё сможете видеть связанную с ней рекламу."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Заблокировать"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Тема \"<xliff:g id="TOPIC">%1$s</xliff:g>\" разблокирована"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android сможет снова добавить эту тему в список. Вероятно, она появится не сразу."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ОК"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Сбросить все интересы?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Из списка будут удалены все интересы, а со временем в нем появятся новые. Иногда вы всё ещё будете видеть связанную со старыми интересами рекламу."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Сбросить"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Заблокировать приложение \"<xliff:g id="APP">%1$s</xliff:g>\"?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Это приложение не будет определять ваши интересы для технологий Privacy Sandbox и появляться в вашем списке, пока вы не разблокируете его.\n\nОпределенные ранее интересы будут удалены, но вы ещё сможете видеть рекламу, подобранную на их основе."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Приложение \"<xliff:g id="APP">%1$s</xliff:g>\" разблокировано"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Это приложение снова сможет определять ваши интересы. Вероятно, вы увидите их в списке не сразу. Реклама, подобранная на их основе, начнет появляться через некоторое время."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Сбросить интересы, сгенерированные приложениями?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Выявленные ранее интересы будут удалены из Privacy Sandbox, а приложения начнут определять их заново. Иногда вы всё ещё будете видеть связанную со старыми интересами рекламу."</string>
<string name="topic10001" msgid="1636806320891333775">"Искусство и развлечения"</string>
<string name="topic10002" msgid="1226367977754287428">"Актерское искусство и театр"</string>
<string name="topic10003" msgid="6949890838957814881">"Аниме и манга"</string>
diff --git a/adservices/apk/res/values-si/strings.xml b/adservices/apk/res/values-si/strings.xml
index bc94143c3..5cc3900bc 100644
--- a/adservices/apk/res/values-si/strings.xml
+++ b/adservices/apk/res/values-si/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"සහභාගී වන ආකාරය"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"බීටා ක්‍රියාත්මක කිරීම යෙදුම්වලට ඔබට වෙළඳ දැන්වීම් පෙන්විය හැකි මෙම නව වඩා පෞද්ගලික ක්‍රම පරීක්ෂා කිරීමට ඉඩ සලසයි. ඔබට ඕනෑම මොහොතක ඔබේ පෞද්ගලිකත්ව සැකසීම් වෙතින් බීටා ක්‍රියා විරහිත කළ හැක."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"එපා ස්තුතියි"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ඔව්, මම එක් වෙමි"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ක්‍රියාත්මක කරන්න"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"තව"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"සහභාගී වීම වෙනුවෙන් ඔබට ස්තුතියි"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"ඔබ Android වෙළෙඳ දැන්වීම් පෞද්ගලිකත්ව බීටාවල කොටසකි. ඔබේ උපාංගය සඳහා පෞද්ගලිකත්ව වැලිපෙට්ටිය සක්‍රීයයි.\n\nඔබට ඕනෑම මොහොතක ඔබේ පෞද්ගලිකත්ව සැකසීම් වෙතින් වැඩි විස්තර දැන ගැනීමට හෝ බීටා අක්‍රිය කළ හැක."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"ඔබ සහභාගී නොවීමට තෝරා ගෙන ඇත"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"දැනට පෙන්වීමට ලැදිකම් කිසිවක් නැත"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"පෞද්ගලිකත්ව වැලිපෙට්ටිය"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android හි දැන්වීම් පෞද්ගලිකත්ව බීටා යෙදුම් ඔබට ඔබ කැමති වීමට ඉඩ ඇති දැන්වීම් පෙන්වීමට භාවිත කළ හැකි නව විශේෂාංග සපයයි. මෙම තාක්ෂණයන් මගින් උපාංග හැඳුනුම්කාරක භාවිතා නොකරයි.\n\nAndroid හට ඔබ උනන්දුවක් දැක්වීමට ඉඩ ඇති ආකාරයේ වෙළෙඳ දැන්වීම් අනුමාන කර ඒවා තාවකාලිකව ඔබේ උපාංගය මත සුරැකිය හැක. මින් වෙනත් සංවර්ධකයන්ගේ වෙබ් අඩවි හරහා ඔබේ ක්‍රියාකාරකම් නිරීක්ෂණය නොකර ඔබට අදාළ වෙළෙඳ දැන්වීම් පෙන්වීමට යෙදුම්වලට ඉඩ ලබා දේ."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ඔබට අවහිර කළ ලැදිකම් කිසිවක් නැත"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"යෙදුම් ඔබේ ලැදිකම් අනුමාන කර ඒවා තාවකාලිකව Android සමග සුරැකිය හැක. පසුව වෙනත් යෙදුමක් මෙම ලැදිකම්වලට අනුව ඔබට දැන්වීමක් පෙන්විය හැක.\n\nඔබ යෙදුමක් අවහිර කළහොත්, එය තවදුරටත් ලැදිකම් අනුමාන නොකරයි. ඔබ එය අනවහිර කරන තුරු එය මෙම යෙදුම් ලැයිස්තුවට එක් නොකෙරේ. දැනටමත් යෙදුම මගින් අනුමාන කර ඇති ලැදිකම් මකා දැමෙන නමුත් ඔබ තවදුරටත් යම් අදාළ දැන්වීම් දැකීමට ඉඩ ඇත."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"ඔබ අවහිර කර ඇති යෙදුම්"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"යෙදුම් මගින් අනුමාන කරන ලද ලැදිකම් යළි සකසන්න"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"පෞද්ගලිකත්ව වැලිපෙට්ටිය"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android හි දැන්වීම් පෞද්ගලිකත්ව බීටා යෙදුම් ඔබට ඔබ කැමති වීමට ඉඩ ඇති දැන්වීම් පෙන්වීමට භාවිත කළ හැකි නව විශේෂාංග සපයයි. මෙම තාක්ෂණයන් මගින් උපාංග හැඳුනුම්කාරක භාවිතා නොකරයි.\n\nයෙදුම්වලට ඔබ උනන්දුවක් දැක්විය හැකි වෙළෙඳ දැන්වීම් වර්ග අනුමාන කර, ඒවා තාවකාලිකව ඔබේ උපාංගයේ සුරැකිය හැක. මින් වෙනත් සංවර්ධකයන්ගේ වෙබ් අඩවි හරහා ඔබේ ක්‍රියාකාරකම් නිරීක්ෂණය නොකර ඔබට අදාළ වෙළෙඳ දැන්වීම් පෙන්වීමට යෙදුම්වලට ඉඩ ලබා දේ."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"ඔබට අවහිර කළ යෙදුම් නැත"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ඔබට අවහිර කළ ලැදිකම් කිසිවක් නැත"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"අවලංගු කරන්න"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"රහස්‍යතා සෑන්ඩ්බොක්ස් ක්‍රියාවිරහිත කරන්න ද?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"ඔබ ඔබේ අදහස වෙනස් කරන්නේ නම් හෝ Android හි දැන්වීම් රහස්‍යතා බීටා ගැන තව දැන ගැනීමට අවශ්‍ය නම්, ඔබේ රහස්‍යතා සැකසීම් වෙත යන්න"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ක්‍රියාවිරහිත කරන්න"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> අවහිර කරන්න ද?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"මෙම උනන්දුව අවහිර කරනු ලබන අතර ඔබ එය නැවත එක් කරන්නේ නම් මිස නැවත ඔබේ ලැයිස්තුවට එක් නොකරනු ඇත. ඔබ තවමත් යම් අදාළ දැන්වීම් දැකිය හැක."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"අවහිර කරන්න"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> අවහිර කිරීම ඉවත් කර ඇත"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android මෙම රුචිකත්වය නැවත ඔබේ ලැයිස්තුවට එක් කළ හැකි නමුත්, එය ක්ෂණිකව නොපෙන්වයි"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"හරි"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"ඔබේ සියලු රුචිකත්වයන් යළි සකසන්න ද?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"ඔබේ ලැයිස්තුව හිස් කර නව රුචිකත්වයන් ඉදිරියට යමින් ඇස්තමේන්තු කරනු ඇත. ඔබ තවමත් හිස් වූ රුචිකත්වයන්ට අදාළ සමහර දැන්වීම් දැකිය හැක."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"යළි සකසන්න"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> අවහිර කරන්න ද?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"මෙම යෙදුම රහස්‍යතා සෑන්ඩ්බොක්ස් සඳහා රුචිකත්වයන් තක්සේරු නොකරන අතර ඔබ එය අවහිර නොකරන්නේ නම් නැවත ඔබේ ලැයිස්තුවට එක් නොකරනු ඇත.\n\nමෙම යෙදුමෙන් දැනටමත් ඇස්තමේන්තු කර ඇති රුචිකත්වයන් මකා දමනු ඇති නමුත්, ඔබට තවමත් අදාළ දැන්වීම් කිහිපයක් දැකිය හැක."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> අවහිර කිරීම ඉවත් කර ඇත"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"මෙම යෙදුමට නැවත ඔබ සඳහා රුචිකත්වයන් තක්සේරු කළ හැකි නමුත්, එය වහාම ඔබේ ලැයිස්තුවේ දිස් නොවනු ඇත. ඔබට අදාළ දැන්වීම් බැලීමට යම් කාලයක් ගත විය හැක."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"යෙදුම් මගින් උත්පාදනය කරන ලද රුචිකත්වයන් යළි සකසන්න ද?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"ඔබේ ලැයිස්තුවෙහි ඇති යෙදුම් මගින් ඇස්තමේන්තු කර ඇති රුචිකත්වයන් රහස්‍යතා සෑන්ඩ්බොක්ස් වෙතින් මකනු ඇති අතර, යෙදුම් ඉදිරියට යමින් නව රුචිකත්වයන් තක්සේරු කරනු ඇත. ඔබ තවමත් යම් අදාළ දැන්වීම් දැකිය හැක."</string>
<string name="topic10001" msgid="1636806320891333775">"කලා සහ විනෝදාස්වාදය"</string>
<string name="topic10002" msgid="1226367977754287428">"රංගනය සහ රංග ශාලාව"</string>
<string name="topic10003" msgid="6949890838957814881">"සජීවන සහ මංගා"</string>
diff --git a/adservices/apk/res/values-sk/strings.xml b/adservices/apk/res/values-sk/strings.xml
index 1f12d39b4..77f16385d 100644
--- a/adservices/apk/res/values-sk/strings.xml
+++ b/adservices/apk/res/values-sk/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Ako sa zapojiť"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ak zapnete beta verziu, aplikácie budú môcť testovať tieto nové spôsoby zobrazovania reklám s vyššou ochranou súkromia. Beta verziu môžete kedykoľvek vypnúť v nastaveniach ochrany súkromia."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nie, vďaka"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Áno, pripojiť sa"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Zapnúť"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Viac"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Ďakujeme vám za účasť"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Ste súčasťou beta verzie ochrany súkromia pri reklamách v Androide. Technológie Privacy Sandbox sú vo vašom zariadení zapnuté.\n\nViac sa dozviete v nastaveniach ochrany súkromia, kde môžete aj kedykoľvek vypnúť beta verziu."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Rozhodli ste sa nezúčastniť"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Momentálne nie sú k dispozícii žiadne záujmy, ktoré by sme mohli zobraziť"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Beta verzia ochrany súkromia pri reklamách v Androide poskytuje nové funkcie pre aplikácie na zobrazovanie reklám, ktoré by sa vám mohli páčiť. Tieto technológie nepoužívajú identifikátory zariadenia.\n\nAndroid môže odhadnúť druhy reklám, ktoré by vás mohli zaujímať, a dočasne uložiť tieto záujmy vo vašom zariadení. Vďaka tomu vám môžu aplikácie zobrazovať relevantné reklamy bez sledovania vašej aktivity na weboch a v aplikáciách od iných vývojárov."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemáte žiadne blokované záujmy"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikácie môžu odhadnúť vaše záujmy a dočasne ich uložiť v Androide. Neskôr vám iná aplikácia môže zobraziť reklamu na základe týchto záujmov.\n\nAk nejakú aplikáciu zablokujete, nebude odhadovať žiadne ďalšie záujmy. Do zoznamu aplikácií bude znova pridaná iba vtedy, keď ju odblokujete. Záujmy, ktoré boli danou aplikáciou odhadnuté, budú odstránené, ale môžu sa vám naďalej zobrazovať určité súvisiace reklamy."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplikácie, ktoré ste zablokovali"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Resetovať záujmy odhadnuté aplikáciami"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Beta verzia ochrany súkromia pri reklamách v Androide poskytuje nové funkcie pre aplikácie na zobrazovanie reklám, ktoré by sa vám mohli páčiť. Tieto technológie nepoužívajú identifikátory zariadenia.\n\nAplikácie môžu odhadnúť druhy reklám, ktoré by vás mohli zaujímať, a dočasne uložiť tieto záujmy vo vašom zariadení. Vďaka tomu vám môžu aplikácie zobrazovať relevantné reklamy bez sledovania vašej aktivity na weboch a v aplikáciách od iných vývojárov."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nemáte žiadne blokované aplikácie"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nemáte žiadne blokované záujmy"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Zrušiť"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Chcete vypnúť Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ak zmeníte názor alebo sa budete chcieť o beta ochrane súkromia pri reklamách v Androide dozvedieť viac, prejdite do nastavení ochrany súkromia"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Vypnúť"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Chcete zablokovať tému <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Tento záujem bude blokovaný a nebude znova pridaný do vášho zoznamu, pokiaľ ho nepridáte späť. Naďalej sa vám môžu zobrazovať určité súvisiace reklamy."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokovať"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Téma <xliff:g id="TOPIC">%1$s</xliff:g> nie je blokovaná"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android môže pridať tento záujem znova do vášho zoznamu, ale nemusí sa zobraziť ihneď"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Chcete resetovať všetky záujmy?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Zoznam bude vymazaný a začnú sa odhadovať nové záujmy. Naďalej sa vám môžu zobrazovať určité reklamy súvisiace s vymazanými záujmami."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Resetovať"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Chcete zablokovať aplikáciu <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Táto aplikácia nebude odhadovať záujmy pre Privacy Sandbox a nebude znova pridaná do vášho zoznamu, pokiaľ ju neodblokujete.\n\nZáujmy, ktoré táto aplikácia už odhadla, budú odstránené, ale naďalej sa vám môžu zobrazovať určité súvisiace reklamy."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Aplikácia <xliff:g id="APP">%1$s</xliff:g> nie je blokovaná"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Táto aplikácia vám môže znova odhadovať záujmy, ale nemusí sa ihneď zobraziť vo vašom zozname. Môže chvíľu trvať, než sa vám začnú zobrazovať súvisiace reklamy."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Chcete resetovať záujmy generované aplikáciami?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Záujmy odhadnuté aplikáciami vo vašom zozname budú odstránené z technológií Privacy Sandbox a aplikácie začnú odhadovať nové záujmy. Naďalej sa vám môžu zobrazovať určité súvisiace reklamy."</string>
<string name="topic10001" msgid="1636806320891333775">"Umenie a zábava"</string>
<string name="topic10002" msgid="1226367977754287428">"Herectvo a divadlo"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime a manga"</string>
diff --git a/adservices/apk/res/values-sl/strings.xml b/adservices/apk/res/values-sl/strings.xml
index 52bc2df57..b7fc4502a 100644
--- a/adservices/apk/res/values-sl/strings.xml
+++ b/adservices/apk/res/values-sl/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Način sodelovanja"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Če vklopite različico beta, lahko aplikacije preizkusijo te nove, bolj zasebne načine prikazovanja oglasov. V nastavitvah zasebnosti lahko kadar koli izklopite različico beta."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ne, hvala"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Da, želim se pridružiti"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Vklopi"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Več"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Zahvaljujemo se vam za sodelovanje"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Sodelujete v različici beta Androidove zasebnosti pri oglaševanju. V vaši napravi je vklopljen Zasebni peskovnik.\n\nV nastavitvah zasebnosti si lahko preberete več ali kadar koli izklopite različico beta."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Odločili ste se, da ne želite sodelovati"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Trenutno ni zanimanj, ki bi jih lahko prikazali."</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Zasebni peskovnik"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Različica beta Androidove zasebnosti pri oglaševanju zagotavlja nove funkcije, s katerimi vam lahko aplikacije prikazujejo oglase, ki bi vam bili morda všeč. Te tehnologije ne uporabljajo identifikatorjev naprave.\n\nAndroid lahko predvidi, katere vrste oglasov bi vas morda zanimale, in ta zanimanja začasno shrani v vašo napravo. To aplikacijam omogoča, da vam prikazujejo ustrezne oglase, ne da bi spremljale vašo dejavnost na spletnih mestih in v aplikacijah drugih razvijalcev."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nimate blokiranih zanimanj."</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikacije lahko predvidijo vaša zanimanja in jih začasno shranijo v Android. Pozneje vam druga aplikacija lahko prikaže oglas na podlagi teh zanimanj.\n\nČe blokirate aplikacijo, ne bo predvidela nobenega zanimanja več. Aplikacija ne bo več dodana na ta seznam aplikacij, razen če jo odblokirate. Zanimanja, ki jih je aplikacija že predvidela, bodo izbrisana, vendar boste morda še vedno videli nekaj z njimi povezanih oglasov."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Blokirane aplikacije"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Ponastavi zanimanja, ki so jih predvidele aplikacije"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Zasebni peskovnik"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Različica beta Androidove zasebnosti pri oglaševanju zagotavlja nove funkcije, s katerimi vam lahko aplikacije prikazujejo oglase, ki bi vam bili morda všeč. Te tehnologije ne uporabljajo identifikatorjev naprave.\n\nAplikacije lahko predvidijo, katere vrste oglasov bi vas morda zanimale, in ta zanimanja začasno shranijo v vašo napravo. To aplikacijam omogoča, da vam prikazujejo ustrezne oglase, ne da bi spremljale vašo dejavnost na spletnih mestih in v aplikacijah drugih razvijalcev."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nimate blokiranih aplikacij."</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nimate blokiranih zanimanj."</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Prekliči"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Želite izklopiti Zasebni peskovnik?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Če si premislite ali želite izvedeti več o različici beta Androidove zasebnosti pri oglaševanju, odprite nastavitve zasebnosti."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Izklopi"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Želite blokirati temo »<xliff:g id="TOPIC">%1$s</xliff:g>«?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"To zanimanje bo blokirano in ne bo znova dodano na seznam, razen če ga znova dodate. Morda boste še vedno videli nekaj povezanih oglasov."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blokiraj"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Tema »<xliff:g id="TOPIC">%1$s</xliff:g>« je odblokirana"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android lahko to zanimanje znova doda na seznam, vendar morda ne bo takoj prikazano."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"V redu"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Želite ponastaviti vsa zanimanja?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Vaš seznam bo počiščen, v prihodnje pa bodo predvidena nova zanimanja. Morda boste še vedno videli nekaj oglasov, povezanih z izbrisanimi zanimanji."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Ponastavi"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Želite blokirati aplikacijo <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ta aplikacija ne bo predvidevala zanimanj za Zasebni peskovnik in ne bo znova dodana na seznam, razen če jo odblokirate.\n\nZanimanja, ki jih je ta aplikacija že predvidela, bodo izbrisana, vendar boste morda še vedno videli nekaj z njimi povezanih oglasov."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> je odblokirana"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Ta aplikacija lahko za vas znova predvideva zanimanja, vendar morda ne bodo takoj prikazana na seznamu. Traja lahko nekaj časa, da boste znova videli povezane oglase."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Želite ponastaviti zanimanja, ki so jih ustvarile aplikacije?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Zanimanja, ki jih predvidijo aplikacije na vašem seznamu, bodo izbrisana iz Zasebnega peskovnika, aplikacije pa bodo v prihodnje predvidele nova zanimanja. Morda boste še vedno videli nekaj povezanih oglasov."</string>
<string name="topic10001" msgid="1636806320891333775">"Umetnost in zabava"</string>
<string name="topic10002" msgid="1226367977754287428">"Igralstvo in gledališče"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime in manga"</string>
diff --git a/adservices/apk/res/values-sq/strings.xml b/adservices/apk/res/values-sq/strings.xml
index fbba965d0..60e37665e 100644
--- a/adservices/apk/res/values-sq/strings.xml
+++ b/adservices/apk/res/values-sq/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Si të marrësh pjesë"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Aktivizimi i versionit beta i lejon aplikacionet të testojnë këto mënyra të reja më private për të të shfaqur reklama. Mund ta çaktivizosh versionin beta në çdo kohë te cilësimet e privatësisë."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Jo, faleminderit"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Po, do të bashkohem"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Aktivizo"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Më shumë"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Faleminderit për pjesëmarrjen"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Je pjesë e versionit beta të Android për privatësinë e reklamave. Privacy Sandbox është aktivizuar për pajisjen tënde.\n\nMund të mësosh më shumë ose mund ta çaktivizosh versionin beta në çdo kohë te cilësimet e privatësisë."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Ke zgjedhur të mos marrësh pjesë"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Nuk ka interesa për të shfaqur për momentin"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Versioni beta i Android për privatësinë e reklamave ofron veçori të reja që aplikacionet mund t\'i përdorin për të të shfaqur reklama që mund të të pëlqejnë. Këto teknologji nuk përdorin identifikuesit e pajisjes.\n\nAndroid mund të përllogarisë llojet e reklamave që mund të të interesojnë dhe t\'i ruajë këta interesa përkohësisht në pajisjen tënde. Kjo i lejon aplikacionet që të të shfaqin reklama të përshtatshme, pa e monitoruar aktivitetin tënd nëpër sajtet e uebit dhe aplikacionet nga zhvilluesit e tjerë."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nuk ke asnjë interes të bllokuar"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Aplikacionet mund t\'i përllogarisin interesat e tu dhe t\'i ruajnë ata përkohësisht me Android. Më vonë, një aplikacion tjetër mund të të shfaqë një reklamë bazuar në këta interesa.\n\nNëse e bllokon një aplikacion, ai nuk do të përllogarisë më interesa. Ai nuk do të shtohet më në këtë listë aplikacionesh, përveçse nëse e zhbllokon ti. Interesat e përllogaritur tashmë nga aplikacioni do të fshihen, por mund të shikosh përsëri disa reklama në lidhje me to."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Aplikacionet që ke bllokuar"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Rivendos interesat e përllogaritur nga aplikacionet"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Versioni beta i Android për privatësinë e reklamave ofron veçori të reja që aplikacionet mund t\'i përdorin për të të shfaqur reklama që mund të të pëlqejnë. Këto teknologji nuk përdorin identifikuesit e pajisjes.\n\nAplikacionet mund të përllogarisin llojet e reklamave që mund të të interesojnë dhe t\'i ruajnë këta interesa përkohësisht në pajisjen tënde. Kjo i lejon aplikacionet që të të shfaqin reklama të përshtatshme, pa e monitoruar aktivitetin tënd nëpër sajtet e uebit dhe aplikacionet nga zhvilluesit e tjerë."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Nuk ke asnjë aplikacion të bllokuar"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Nuk ke asnjë interes të bllokuar"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Anulo"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Të çaktivizohet Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Nëse ndryshon mendje ose dëshiron të mësosh më shumë rreth versionit beta të Android për privatësinë e reklamave, shko te cilësimet e tua të privatësisë"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Çaktivizo"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Të bllokohet <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Ky interes do të bllokohet dhe nuk do të shtohet përsëri në listën tënde nëse ti nuk e shton përsëri. Mund të shohësh ende disa reklama të ngjashme."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blloko"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> është e zhbllokuar"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android mund ta shtojë këtë interes përsëri te lista jote por mund të mos shfaqet menjëherë"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Në rregull"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Të rivendosen të gjitha interesat e tua?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Lista jote do të pastrohet dhe më tej do të përllogariten interesa të rinj. Mund të shikosh përsëri disa reklama në lidhje me interesat e pastruar."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Rivendos"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Të bllokohet <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ky aplikacion nuk do të përllogarisë interesat për Privacy Sandbox dhe nuk do të shtohet përsëri në listën tënde nëse nuk e zhbllokon.\n\nInteresat e përllogaritura tashmë nga ky aplikacion do të fshihen por ti mund të shikosh ende disa reklama në lidhje me të."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> është i zhbllokuar"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Ky aplikacion mund të përllogarisë përsëri interesat për ty, por mund të mos shfaqet menjëherë në listën tënde. Mund të duhet pak kohë përpara se të shikosh reklama të ngjashme."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Të rivendosen interesat e gjeneruara nga aplikacionet?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Interesat e përllogaritur nga aplikacionet në listën tënde do të fshihen nga Privacy Sandbox dhe aplikacionet do të përllogarisin interesa të rinj më tej. Mund të shikosh përsëri disa reklama në lidhje me ta."</string>
<string name="topic10001" msgid="1636806320891333775">"Arte dhe argëtim"</string>
<string name="topic10002" msgid="1226367977754287428">"Aktrim dhe teatër"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime dhe manga"</string>
diff --git a/adservices/apk/res/values-sr/strings.xml b/adservices/apk/res/values-sr/strings.xml
index ea9efef06..971df9ae2 100644
--- a/adservices/apk/res/values-sr/strings.xml
+++ b/adservices/apk/res/values-sr/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Како се учествује"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Укључивање бета верзије омогућава апликацијама да тестирају ове нове приватније начине за приказивање огласа. Увек можете да искључите бета верзију у подешавањима приватности."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Не, хвала"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Да, придружићу се"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Укључи"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Још"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Хвала вам на учешћу"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Део сте бета верзије Android приватности за огласе. Заштићено окружење приватности је укључено за уређај.\n\nМожете да сазнате више или искључите бета верзију у било ком тренутку у подешавањима приватности."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Одабрали сте да не учествујете"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Тренутно нема интересовања за приказ"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Заштићено окружење приватности"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Бета верзија Android приватности за огласе пружа нове функције које апликације могу да користе да би вам приказивале огласе који ће вам се можда допасти. Ове технологије не користе идентификаторе уређаја.\n\nAndroid може да процењује врсте огласа који ће вас можда занимати и чува интересовања привремено на уређају. То омогућава апликацијама да вам приказују релевантне огласе, без праћења ваших активности на веб-сајтовима и у апликацијама других програмера."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Немате ниједно блокирано интересовање"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Апликације могу да процењују интересовања и привремено их чувају помоћу Android-а. Касније друга апликација може да вам прикаже оглас на основу тих интересовања.\n\nАко блокирате апликацију, она више неће процењивати интересовања. Неће бити додата на ову листу апликација поново ако је не одблокирате. Интересовања која је апликација већ проценила биће избрисана, али ћете можда и даље видети неке повезане огласе."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Апликације које сте блокирали"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Ресетујте интересовања која су процениле апликације"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Заштићено окружење приватности"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Бета верзија Android приватности за огласе пружа нове функције које апликације могу да користе да би вам приказивале огласе који ће вам се можда допасти. Ове технологије не користе идентификаторе уређаја.\n\nАпликације могу да процењују врсте огласа који ће вас можда занимати и чувају интересовања привремено на уређају. То омогућава апликацијама да вам приказују релевантне огласе, без праћења ваших активности на веб-сајтовима и у апликацијама других програмера."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Немате ниједну блокирану апликацију"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Немате ниједно блокирано интересовање"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Откажи"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Желите да искључите Заштићено окружење приватности?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Ако се предомислите или желите да сазнате више о Android приватности са огласима бета, идите у подешавања приватности"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Искључи"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Желите да блокирате тему <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Ово интересовање ће бити блокирано и неће поново бити додато на листу ако га ви лично поново не додате. И даље могу да вам се приказују неки сродни огласи."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Блокирај"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Тема <xliff:g id="TOPIC">%1$s</xliff:g> је одблокирана"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android може поново да дода ово интересовање на листу, али се оно можда неће одмах појавити"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Потврди"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Желите да ресетујете сва интересовања?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Листа ће се обрисати и убудуће ће се процењивати нова интересовања. И даље могу да вам се приказују неки огласи повезани са обрисаним интересовањима."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Ресетуј"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Желите да блокирате апликацију <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ова апликација неће процењивати интересовања за Заштићено окружење приватности и неће бити поново додата на листу.\n\nИнтересовања која је ова апликација већ проценила биће избрисана, али ћете можда и даље видети неке сродне огласе."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Апликација <xliff:g id="APP">%1$s</xliff:g> је одблокирана"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Ова апликација може поново да процени интересовања за вас, али се можда неће одмах појавити на листи. Можда ће проћи неко време док не будете видели сродне огласе."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Желите да ресетујете интересовања која су генерисале апликације?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Интересовања која су процениле апликације на листи ће се избрисати из Заштићеног окружења приватности, а апликације ће убудуће процењивати нова интересовања. И даље могу да вам се приказују неки сродни огласи."</string>
<string name="topic10001" msgid="1636806320891333775">"Уметност и забава"</string>
<string name="topic10002" msgid="1226367977754287428">"Глума и позориште"</string>
<string name="topic10003" msgid="6949890838957814881">"Аниме и манга"</string>
diff --git a/adservices/apk/res/values-sv/strings.xml b/adservices/apk/res/values-sv/strings.xml
index bb29b3e37..e230051b3 100644
--- a/adservices/apk/res/values-sv/strings.xml
+++ b/adservices/apk/res/values-sv/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Så här deltar du"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Om du aktiverar betaprogrammet kan appar testa dessa nya, privatare metoderna för att visa annonser. Du kan inaktivera betaprogammet när du vill i integritetsinställningarna."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Nej tack"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ja, jag vill gå med"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Aktivera"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Mer"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Tack för ditt deltagande"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Du deltar i Androids betaprogram för annonsintegritet. Privacy Sandbox har aktiverats för enheten.\n\nDu kan läsa mer eller inaktivera betaprogrammet när du vill i integritetsinställningarna."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Du har valt att inte delta"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Det finns inga intressen att visa just nu"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androids betaprogram för annonsintegritet tillhandahåller nya funktioner som appar kan använda för att visa annonser som kan intressera dig. Med dessa funktioner används inte enhetsidentifierare.\n\nAndroid kan uppskatta vilka typer av annonser du kan vara intresserad av och spara dessa intressen tillfälligt på enheten. Det innebär att appar kan visa relevanta annonser, utan att spåra dina aktivitet på webbplatser och i appar från andra utvecklare."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Du har inga blockerade intressen"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Appar kan uppskatta dina intressen och spara dem tillfälligt med Android. Sedan kan en annan app visa annonser utifrån dessa intressen.\n\nOm du blockerar en app uppskattar den inte längre dina intressen. Den läggs inte till i den här applistan igen såvida du inte tar bort blockeringen. Intressen som redan uppskattats av appen raderas, men du kan fortfarande se relaterade annonser."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Appar som du har blockerat"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Återställ intressen som uppskattats av appar"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androids betaprogram för annonsintegritet tillhandahåller nya funktioner som appar kan använda för att visa annonser som kan intressera dig. Med dessa funktioner används inte enhetsidentifierare.\n\nAndroid kan uppskatta vilka typer av annonser du kan vara intresserad av och spara dessa intressen tillfälligt på enheten. Det innebär att appar kan visa relevanta annonser, utan att spåra dina aktivitet på webbplatser och i appar från andra utvecklare."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Du har inga blockerade appar"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Du har inga blockerade intressen"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Avbryt"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Vill du inaktivera Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Om du ändrar dig eller vill läsa mer om Androids betaprogram för annonsintegritet, kan du gå till dina integritetsinställningar"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Inaktivera"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Vill du blockera <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Intresset blockeras och läggs inte till i listan igen såvida du inte lägger till det själv. Du kanske fortfarande ser relaterade annonser."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Blockera"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Blockeringen av <xliff:g id="TOPIC">%1$s</xliff:g> har tagits bort"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android kan lägga till det här intresset i listan igen, men det dyker kanske inte upp omedelbart"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Vill du återställa alla dina intressen?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Listan rensas och nya intressen uppskattas från och med nu. Du kanske fortfarande ser en del annonser som är relaterade till de rensade intressena."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Återställ"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Vill du blockera <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Appen kommer inte att uppskatta intressen för Privacy Sandbox eller läggas till i listan igen såvida du inte tar bort blockeringen.\n\nIntressen som redan har uppskattats av appen raderas, men du kanske fortfarande ser relaterade annonser."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Blockeringen av <xliff:g id="APP">%1$s</xliff:g> har tagits bort"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Appen kan uppskatta intressen för dig igen, men de dyker kanske inte upp i listan omedelbart. Det kan ta ett tag innan du ser relaterade annonser."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Vill du återställa intressen som genererats av appar?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Intressen som uppskattats av appar på listan raderas från Privacy Sandbox. Apparna uppskattar nya intressen från och med nu. Du kanske fortfarande ser en del relaterade annonser."</string>
<string name="topic10001" msgid="1636806320891333775">"Konst och underhållning"</string>
<string name="topic10002" msgid="1226367977754287428">"Skådespeleri och teater"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime och manga"</string>
diff --git a/adservices/apk/res/values-sw/strings.xml b/adservices/apk/res/values-sw/strings.xml
index 51def5f2c..88d96317d 100644
--- a/adservices/apk/res/values-sw/strings.xml
+++ b/adservices/apk/res/values-sw/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Jinsi ya kushiriki"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Kuwasha toleo la beta huwezesha programu kujaribu njia hizi mpya zenye faragha zaidi ili zikuonyeshe matangazo. Unaweza kuzima toleo la beta wakati wowote kwenye mipangilio ya faragha."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Hapana"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ndiyo, nitajiunga"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Washa"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Zaidi"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Asante kwa kushiriki"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Umejiunga kwenye toleo la beta la faragha ya matangazo kwenye Android. Kipengele cha Mazingira ya Faragha kimewashwa kwenye kifaa chako.\n\nUnaweza kupata maelezo zaidi au kuzima toleo la beta wakati wowote katika mipangilio ya faragha."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Umechagua kutoshiriki"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Hakuna mambo yanayokuvutia ya kuonyesha kwa sasa"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Mazingira ya Faragha"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Toleo la beta la faragha ya matangazo kwenye Android hutoa vipengele vipya ambavyo programu zinaweza kutumia ili zikuonyeshe matangazo ambayo huenda ukayapenda. Teknolojia hizi hazitumii vitambulishi vya kifaa.\n\nAndroid inaweza kukadiria aina ya matangazo ambayo huenda yakakuvutia na kuhifadhi mambo haya yanayokuvutia kwa muda kwenye kifaa chako. Hali hii huwezesha programu kukuonyesha matangazo yanayokufaa, bila kufuatilia shughuli zako kwenye tovuti na programu za wasanidi programu wengine."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Huna mambo yanayokuvutia uliyozuia"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Programu zinaweza kukadiria mambo yanayokuvutia na kuyahifadhi kwa muda kupitia Android. Hapo baadaye, programu tofauti inaweza kukuonyesha tangazo kulingana na mambo haya yanayokuvutia.\n\nIwapo utazuia programu, haitakadiria mambo yanayokuvutia. Haitawekwa tena kwenye orodha hii ya programu isipokuwa uiondolee kizuizi. Mambo yanayokuvutia yaliyoakadiriwa na programu hiyo yatafutwa, ila huenda bado ukaona baadhi ya matangazo yanayohusiana."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Programu ulizozizuia"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Weka upya mambo yanayokuvutia yaliyokadiriwa na programu"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Mazingira ya Faragha"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Toleo la beta la faragha ya matangazo kwenye Android hutoa vipengele vipya ambavyo programu zinaweza kutumia ili zikuonyeshe matangazo ambayo huenda ukayapenda. Teknolojia hizi hazitumii vitambulishi vya kifaa.\n\nProgramu zako zinaweza kukadiria aina ya matangazo ambayo huenda yakakuvutia na kuhifadhi mambo haya yanayokuvitia kwa muda kwenye kifaa chako. Hali hii huwezesha programu kukuonyesha matangazo yanayokufaa, bila kufuatilia shughuli zako kwenye tovuti na programu za wasanidi programu wengine."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Huna programu ulizozuia"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Huna mambo yanayokuvutia uliyozuia"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Ghairi"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Ungependa kuzima kipengele cha Mazingira ya Faragha?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Iwapo utabadili mawazo yako au utataka kupata maelezo zaidi kuhusu toleo la beta la faragha ya matangazo kwenye Android, nenda kwenye mipangilio ya faragha"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Zima"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Ungependa kuzuia mada ya <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Mada hii inayokuvutia itazuiwa na haitawekwa kwenye orodha yako tena, isipokuwa ukiiweka mwenyewe. Huenda bado ukaona baadhi ya matangazo yanayohusiana."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Zuia"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Umeiondolea kizuizi mada ya <xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Huenda Android ikaweka tena mada hii inayokuvutia kwenye orodha yako, lakini inaweza isionekane papo hapo"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Sawa"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Ungependa kuweka upya mambo yote yanayokuvutia?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Orodha yako itafutwa na mambo mapya yanayokuvutia yatakadiriwa kuanzia sasa. Bado unaweza kuona baadhi ya matangazo yanayohusiana na mambo yanayokuvutia yaliyofutwa."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Weka upya"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Ungependa kuzuia programu ya <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Programu hii haitakadiria mambo yanayokuvutia kwa ajili ya kipengele cha Mazingira ya faragha na haitawekwa kwenye orodha yako tena isipokuwa ukiondoa kizuizi.\n\nMambo yanayokuvutia yaliyokadiriwa na programu hii yatafutwa, ila huenda bado ukaona baadhi ya matangazo yanayohusiana."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Umeiondolea kizuizi programu ya <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Programu hii inaweza kukadiria tena mambo yanayokuvutia lakini inaweza isionekane kwenye orodha yako papo hapo. Huenda ikachukua muda hadi uone matangazo yanayohusiana."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Ungependa kuweka upya mambo yanayokuvutia yaliyokadiriwa na programu?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Mambo yanayokuvutia yanayokadiriwa na programu kwenye orodha yako yatafutwa kwenye Mazingira ya faragha na programu zitakadiria mambo mapya yanayokuvutia kuanzia sasa. Bado unaweza kuona baadhi ya matangazo yanayohusiana."</string>
<string name="topic10001" msgid="1636806320891333775">"Sanaa na Burudani"</string>
<string name="topic10002" msgid="1226367977754287428">"Uigizaji na Ukumbi wa Maonyesho"</string>
<string name="topic10003" msgid="6949890838957814881">"Vibonzo na Manga"</string>
diff --git a/adservices/apk/res/values-ta/strings.xml b/adservices/apk/res/values-ta/strings.xml
index 8e6c12177..e7ffaf755 100644
--- a/adservices/apk/res/values-ta/strings.xml
+++ b/adservices/apk/res/values-ta/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"எப்படிப் பங்கேற்பது?"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"பீட்டாவை இயக்கினால், உங்களுக்கு விளம்பரங்களைக் காட்டுவதற்காக இந்தப் புதிய மற்றும் கூடுதலான தனிப்பட்ட வழிகளைப் பரிசோதனை செய்ய ஆப்ஸ் அனுமதிக்கப்படும். தனியுரிமை அமைப்புகளுக்குச் சென்று எப்போது வேண்டுமானாலும் பீட்டாவை முடக்கிக்கொள்ளலாம்."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"வேண்டாம்"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ஆம். இணைகிறேன்"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"இயக்கு"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"மேலும் காட்டு"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"பங்கேற்பதற்கு நன்றி"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"நீங்கள் Androidன் விளம்பரத் தனியுரிமை பீட்டா பதிப்பின் அங்கமாக உள்ளீர்கள். உங்கள் சாதனத்திற்குத் தனியுரிமை சாண்ட்பாக்ஸ் இயக்கப்பட்டுள்ளது.\n\nநீங்கள் மேலும் அறியலாம் அல்லது எப்போது வேண்டுமானாலும் தனியுரிமை அமைப்புகளுக்குச் சென்று பீட்டாவை முடக்கலாம்."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"பங்கேற்க வேண்டாம் எனத் தேர்வுசெய்துள்ளீர்கள்"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"தற்சமயம் காட்டுவதற்கு ஆர்வங்கள் எதுவும் இல்லை"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"தனியுரிமை சாண்ட்பாக்ஸ்"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Androidன் விளம்பரத் தனியுரிமை பீட்டா பதிப்பு புதிய அம்சங்களை வழங்குகிறது. அவற்றைப் பயன்படுத்தி நீங்கள் விரும்பக்கூடிய விளம்பரங்களை ஆப்ஸால் வழங்க முடியும். இந்தத் தொழில்நுட்பங்கள் சாதன அடையாளங்காட்டிகளைப் பயன்படுத்தாது.\n\nAndroid நீங்கள் விரும்பக்கூடிய விளம்பர வகைகளை மதிப்பீடு செய்து அவற்றைத் தற்காலிகமாக உங்கள் சாதனத்தில் சேமித்து வைக்கும். இதன் மூலம், இணையதளங்களிலும் பிற டெவெலப்பர்களின் ஆப்ஸிலும் உங்கள் செயல்பாடுகளைக் கண்காணிக்காமலே தொடர்புடைய விளம்பரங்களை ஆப்ஸால் காட்ட முடியும்."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"தடுக்கப்பட்ட ஆர்வங்கள் எதுவுமில்லை"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"ஆப்ஸால் உங்கள் ஆர்வங்களை மதிப்பீடு செய்து அவற்றை Androidல் தற்காலிகமாகச் சேமிக்க முடியும். பிறகு, வேறொரு ஆப்ஸ் இந்த ஆர்வங்களின் அடிப்படையில் விளம்பரங்களைக் காட்டலாம்.\n\nநீங்கள் ஓர் ஆப்ஸைத் தடுத்தால் அதன் பிறகு அந்த ஆப்ஸ் உங்கள் ஆர்வங்களை மதிப்பீடு செய்யாது. நீங்கள் தடுப்பை நீக்காதவரை அது இந்த ஆப்ஸ் பட்டியலில் மீண்டும் சேர்க்கப்படாது. ஆப்ஸால் ஏற்கெனவே மதிப்பீடு செய்யப்பட்ட ஆர்வங்கள் நீக்கப்படும். இருப்பினும் சில தொடர்புடைய விளம்பரங்கள் காட்டப்படலாம்."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"நீங்கள் தடுத்த ஆப்ஸ்"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ஆப்ஸால் மதிப்பீடு செய்யப்பட்ட ஆர்வங்களை மீட்டமை"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"தனியுரிமை சாண்ட்பாக்ஸ்"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Androidன் விளம்பரத் தனியுரிமை பீட்டா பதிப்பு புதிய அம்சங்களை வழங்குகிறது. அவற்றைப் பயன்படுத்தி நீங்கள் விரும்பக்கூடிய விளம்பரங்களை ஆப்ஸால் வழங்க முடியும். இந்தத் தொழில்நுட்பங்கள் சாதன அடையாளங்காட்டிகளைப் பயன்படுத்தாது.\n\nஆப்ஸ் நீங்கள் விரும்பக்கூடிய விளம்பர வகைகளை மதிப்பீடு செய்து அவற்றைத் தற்காலிகமாக உங்கள் சாதனத்தில் சேமித்து வைக்கும். இதன் மூலம், இணையதளங்களிலும் பிற டெவெலப்பர்களின் ஆப்ஸிலும் உங்கள் செயல்பாடுகளைக் கண்காணிக்காமலே தொடர்புடைய விளம்பரங்களை ஆப்ஸால் காட்ட முடியும்."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"தடுக்கப்பட்ட ஆப்ஸ் எதுவுமில்லை"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"தடுக்கப்பட்ட ஆர்வங்கள் எதுவுமில்லை"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"ரத்துசெய்"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"தனியுரிமை சாண்ட்பாக்ஸை முடக்கவா?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"உங்கள் மனதை மாற்றிக்கொண்டாலோ Androidன் விளம்பரத் தனியுரிமை பீட்டா பற்றி மேலும் அறிய விரும்பினாலோ தனியுரிமை அமைப்புகளுக்குச் செல்லவும்"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"முடக்கு"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> ஐத் தடுக்கவா?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"இந்த ஆர்வம் தடுக்கப்படும் மற்றும் நீங்கள் திரும்பச் சேர்க்கும் வரை உங்கள் பட்டியலில் அது மீண்டும் சேர்க்கப்படாது. எனினும், தொடர்புடைய சில விளம்பரங்களை நீங்கள் தொடர்ந்து பார்க்கக்கூடும்."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"தடு"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> ஆப்ஸ் தடுப்பு நீக்கப்பட்டது"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"உங்கள் பட்டியலில் இந்த ஆர்வத்தை Android மீண்டும் சேர்க்கலாம், எனினும் அது உடனே காட்டப்படாது"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"சரி"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"உங்களின் அனைத்து ஆர்வங்களையும் மீட்டமைக்கவா?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"உங்கள் பட்டியல் நீக்கப்பட்டு, புதிய விருப்பங்கள் இனி மதிப்பிடப்படும். நீக்கப்பட்ட விருப்பங்களுக்குத் தொடர்புடைய சில விளம்பரங்களை நீங்கள் தொடர்ந்து பார்க்கக்கூடும்."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"மீட்டமை"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> ஆப்ஸைத் தடுக்கவா?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"தனியுரிமை சாண்ட்பாக்ஸுக்கான ஆர்வங்களை இந்த ஆப்ஸ் கணக்கிடாது மற்றும் நீங்கள் அதற்கான தடையை நீக்கும் வரை பட்டியலில் அது மீண்டும் சேர்க்கப்படாது.\n\nஇந்த ஆப்ஸ் மூலம் ஏற்கெனவே கணக்கிடப்பட்ட ஆர்வங்கள் நீக்கப்படும், எனினும் தொடர்புடைய சில விளம்பரங்களை நீங்கள் பார்க்கக்கூடும்."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> ஆப்ஸ் தடுப்பு நீக்கப்பட்டது"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"இந்த ஆப்ஸ் உங்களுக்காக ஆர்வங்களை மீண்டும் கணக்கிடலாம், எனினும் அவை உங்கள் பட்டியலில் உடனே காட்டப்படாது. தொடர்புடைய விளம்பரங்களைப் பார்க்க சிறிதுநேரம் ஆகக்கூடும்."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"ஆப்ஸ் மூலம் உருவாக்கப்பட்ட ஆர்வங்களை மீட்டமைக்கவா?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"உங்கள் பட்டியலில் உள்ள ஆப்ஸ் மதிப்பிட்ட உங்களின் விருப்பங்கள் தனியுரிமை சாண்ட்பாக்ஸிலிருந்து நீக்கப்படும். மேலும் ஆப்ஸ் இனி உங்கள் புதிய விருப்பங்களை மதிப்பிடும். எனினும் தொடர்புடைய சில விளம்பரங்களை நீங்கள் தொடர்ந்து பார்க்கக்கூடும்."</string>
<string name="topic10001" msgid="1636806320891333775">"கலை &amp; பொழுதுபோக்கு"</string>
<string name="topic10002" msgid="1226367977754287428">"நடிப்பு &amp; திரையரங்கு"</string>
<string name="topic10003" msgid="6949890838957814881">"அனிமே &amp; மங்கா"</string>
diff --git a/adservices/apk/res/values-te/strings.xml b/adservices/apk/res/values-te/strings.xml
index 9a023e9b7..f844d3272 100644
--- a/adservices/apk/res/values-te/strings.xml
+++ b/adservices/apk/res/values-te/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"ఎలా పాల్గొనాలి"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"బీటాను ఆన్ చేయడం వలన మీకు యాడ్‌లను చూపించడానికి ఈ కొత్త మరిన్ని ప్రైవేట్ మార్గాలను పరీక్షించడానికి యాప్‌లను అనుమతిస్తుంది. మీరు మీ గోప్యతా సెట్టింగ్‌లలో ఎప్పుడైనా బీటాను ఆఫ్ చేయవచ్చు."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"వద్దు"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"అవును, నేను చేరతాను"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"ఆన్ చేయండి"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"మరిన్ని"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"మీరు పాల్గొన్నందుకు థ్యాంక్స్"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"మీరు Android యాడ్‌ల గోప్యతా బీటాలో భాగం. మీ పరికరం కోసం గోప్యతా పరిరక్షణ టెక్నాలజీల సెట్ ఆన్ చేయబడింది.\n\nమీరు మీ గోప్యతా సెట్టింగ్‌లలో ఎప్పుడైనా మరింత సమాచారం తెలుసుకోవచ్చు లేదా బీటాను ఆఫ్ చేయవచ్చు."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"మీరు పాల్గొనకూడదని ఎంచుకున్నారు"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ప్రస్తుతం చూపించడానికి ఆసక్తులు ఏవీ లేవు"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"గోప్యతా పరిరక్షణ టెక్నాలజీల సెట్"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android యాడ్‌ల గోప్యతా బీటా వెర్షన్ మీకు నచ్చే యాడ్‌లను చూపడానికి యాప్‌లు ఉపయోగించగల కొత్త ఫీచర్‌లను అందిస్తుంది. ఈ టెక్నాలజీలు పరికర ఐడెంటిఫైయర్‌లను ఉపయోగించవు.\n\nAndroid పరికరం మీకు ఆసక్తి కలిగించే యాడ్‌ల రకాలను అంచనా వేయగలదు, అలాగే ఈ ఆసక్తులను మీ పరికరంలో తాత్కాలికంగా సేవ్ చేయగలవు. ఇతర డెవలపర్‌ల నుండి వెబ్‌సైట్‌లు, యాప్‌లలో మీ యాక్టివిటీని ట్రాక్ చేయకుండానే, సంబంధిత యాడ్‌లను మీకు చూపడానికి ఇది యాప్‌లను అనుమతిస్తుంది."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"మీకు బ్లాక్ చేయబడిన ఆసక్తులు లేవు"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"యాప్‌లు మీ ఆసక్తులను అంచనా వేయగలవు, వీటిని Android పరికరంతో తాత్కాలికంగా సేవ్ చేయవచ్చు. తర్వాత, ఈ ఆసక్తుల ఆధారంగా వేరే యాప్ మీకు యాడ్‌ను చూపుతుంది.\n\nమీరు యాప్‌ను బ్లాక్ చేస్తే, అది మరిన్ని ఇతర ఆసక్తులను అంచనా వేయదు. మీరు దీన్ని అన్‌బ్లాక్ చేస్తే తప్ప ఇది మళ్లీ ఈ యాప్‌ల లిస్ట్‌కు జోడించబడదు. యాప్ ద్వారా ఇప్పటికే అంచనా వేసిన ఆసక్తులు తొలగించబడతాయి, కానీ మీరు ఇప్పటికీ కొన్ని సంబంధిత యాడ్‌లను చూడవచ్చు."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"మీరు బ్లాక్ చేసిన యాప్‌లు"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"యాప్‌ల ద్వారా అంచనా వేయబడిన ఆసక్తులను రీసెట్ చేయండి"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"గోప్యతా పరిరక్షణ టెక్నాలజీల సెట్"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android యాడ్‌ల గోప్యతా బీటా వెర్షన్ మీకు నచ్చే యాడ్‌లను చూపడానికి యాప్‌లు ఉపయోగించగల కొత్త ఫీచర్‌లను అందిస్తుంది. ఈ టెక్నాలజీలు పరికర ఐడెంటిఫైయర్‌లను ఉపయోగించవు.\n\nయాప్‌లు మీకు ఆసక్తి కలిగించే యాడ్‌ల రకాలను అంచనా వేయగలవు, అలాగే ఈ ఆసక్తులను మీ పరికరంలో తాత్కాలికంగా సేవ్ చేయగలవు. ఇతర డెవలపర్‌ల నుండి వెబ్‌సైట్‌లు, యాప్‌లలో మీ యాక్టివిటీని ట్రాక్ చేయకుండానే, సంబంధిత యాడ్‌లను మీకు చూపడానికి ఇది యాప్‌లను అనుమతిస్తుంది."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"మీకు బ్లాక్ చేయబడిన యాప్‌లు ఏవీ లేవు"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"మీకు బ్లాక్ చేయబడిన ఆసక్తులు లేవు"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"రద్దు చేయండి"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"గోప్యతా పరిరక్షణ టెక్నాలజీల సెట్‌ను ఆఫ్ చేయాలా?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"మీరు మీ మనసు మార్చుకున్నట్లయితే లేదా Android యాడ్‌ల గోప్యతా బీటా వెర్షన్ గురించి మరింత తెలుసుకోవాలనుకుంటే, మీ గోప్యతా సెట్టింగ్‌లకు వెళ్లండి"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ఆఫ్ చేయండి"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g>ను బ్లాక్ చేయాలా?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ఈ ఆసక్తి బ్లాక్ చేయబడుతుంది ఇంకా మీరు దీన్ని తిరిగి జోడించే వరకు మళ్లీ మీ లిస్ట్‌కు జోడించబడదు. మీరు ఇప్పటికీ కొన్ని సంబంధిత యాడ్‌లను చూడవచ్చు."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"బ్లాక్ చేయండి"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> అన్‌బ్లాక్ చేయబడింది"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ఈ ఆసక్తిని మీ లిస్ట్‌కు మళ్లీ జోడించవచ్చు, కానీ అది వెంటనే కనిపించకపోవచ్చు"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"సరే"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"మీ ఆసక్తులు అన్నీ రీసెట్ చేయాలా?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"మీ లిస్ట్ క్లియర్ చేయబడుతుంది, ఇంకా ఇకపై కొత్త ఆసక్తులు అంచనా వేయబడతాయి. మీకు ఇప్పటికీ క్లియర్ చేయబడిన ఆసక్తులకు సంబంధించిన కొన్ని యాడ్‌లు కనిపించవచ్చు."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"రీసెట్ చేయండి"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g>ని బ్లాక్ చేయాలా?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"ఈ యాప్ గోప్యతా పరిరక్షణ టెక్నాలజీల సెట్ కోసం ఆసక్తులను అంచనా వేయదు, అలాగే మీరు దీన్ని అన్‌బ్లాక్ చేస్తే తప్ప మళ్లీ మీ లిస్ట్‌కు జోడించబడదు.\n\nఈ యాప్ ద్వారా ఇప్పటికే అంచనా వేసిన ఆసక్తులు తొలగించబడతాయి, కానీ మీరు ఇప్పటికీ కొన్ని సంబంధిత యాడ్‌లను చూడవచ్చు."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> అన్‌బ్లాక్ చేయబడింది"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"ఈ యాప్ మీ కోసం మళ్లీ ఆసక్తులను అంచనా వేయగలదు, అయితే అది మీ లిస్ట్‌లో వెంటనే కనిపించకపోవచ్చు. మీకు, సంబంధిత యాడ్‌లు కనిపించడానికి కొంత సమయం పట్టవచ్చు."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"యాప్‌ల ద్వారా జెనరేట్ చేయబడిన ఆసక్తులను రీసెట్ చేయాలా?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"మీ లిస్ట్‌లోని యాప్‌ల ద్వారా అంచనా వేయబడిన ఆసక్తులు గోప్యతా పరిరక్షణ టెక్నాలజీల సెట్ నుండి తొలగించబడతాయి, అలాగే యాప్‌లు ఇకపై కొత్త ఆసక్తులను అంచనా వేస్తాయి. మీరు ఇప్పటికీ కొన్ని సంబంధిత యాడ్‌లను చూడవచ్చు."</string>
<string name="topic10001" msgid="1636806320891333775">"కళలు &amp; వినోదం"</string>
<string name="topic10002" msgid="1226367977754287428">"నటన &amp; థియేటర్"</string>
<string name="topic10003" msgid="6949890838957814881">"యానిమే &amp; మాంగా"</string>
diff --git a/adservices/apk/res/values-th/strings.xml b/adservices/apk/res/values-th/strings.xml
index 43c733d55..ccba1835d 100644
--- a/adservices/apk/res/values-th/strings.xml
+++ b/adservices/apk/res/values-th/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"วิธีเข้าร่วม"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"การใช้รุ่นเบต้าช่วยให้แอปทดสอบวิธีการแสดงโฆษณาแบบใหม่ที่มีความเป็นส่วนตัวมากขึ้นเหล่านี้ได้ คุณหยุดใช้รุ่นเบต้าได้ทุกเมื่อในการตั้งค่าความเป็นส่วนตัว"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"ไม่เป็นไร"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ตกลง ฉันจะเข้าร่วม"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"เปิด"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"เพิ่มเติม"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"ขอขอบคุณที่เข้าร่วม"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"คุณเป็นส่วนหนึ่งของโปรแกรมความเป็นส่วนตัวสำหรับโฆษณาใน Android (เบต้า) Privacy Sandbox เปิดใช้อยู่บนอุปกรณ์ของคุณ\n\nคุณดูข้อมูลเพิ่มเติมหรือหยุดใช้รุ่นเบต้าได้ทุกเมื่อในการตั้งค่าความเป็นส่วนตัว"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"คุณเลือกที่จะไม่เข้าร่วม"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"ไม่มีความสนใจที่จะแสดงในขณะนี้"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"โปรแกรมความเป็นส่วนตัวสำหรับโฆษณาใน Android (เบต้า) มีฟีเจอร์ใหม่ๆ ที่แอปสามารถใช้เพื่อแสดงโฆษณาที่คุณอาจชอบ เทคโนโลยีเหล่านี้ไม่ใช้ตัวระบุอุปกรณ์\n\nAndroid สามารถคาดการณ์ประเภทของโฆษณาที่คุณอาจสนใจ และบันทึกความสนใจเหล่านี้ไว้บนอุปกรณ์ของคุณชั่วคราว วิธีนี้ช่วยให้แอปต่างๆ แสดงโฆษณาที่เกี่ยวข้องได้โดยที่ไม่ต้องติดตามกิจกรรมของคุณบนเว็บไซต์และแอปจากนักพัฒนารายอื่น"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ไม่มีความสนใจที่คุณบล็อกไว้"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"แอปสามารถคาดการณ์ความสนใจของคุณและบันทึกสิ่งเหล่านี้ไว้กับ Android ชั่วคราวได้ จากนั้น แอปอื่นๆ ก็จะแสดงโฆษณาตามความสนใจเหล่านี้ต่อคุณได้\n\nหากบล็อกแอป แอปดังกล่าวก็จะไม่คาดการณ์ความสนใจอีก ระบบจะไม่เพิ่มความสนใจลงในรายการของแอปอีก เว้นแต่คุณจะเลิกบล็อก ระบบจะลบข้อมูลความสนใจที่แอปคาดการณ์แล้ว แต่คุณอาจยังเห็นโฆษณาที่เกี่ยวข้องอยู่บ้าง"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"แอปที่คุณบล็อก"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"รีเซ็ตความสนใจที่คาดการณ์โดยแอป"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"โปรแกรมความเป็นส่วนตัวสำหรับโฆษณาใน Android (เบต้า) มีฟีเจอร์ใหม่ๆ ที่แอปสามารถใช้เพื่อแสดงโฆษณาที่คุณอาจชอบ เทคโนโลยีเหล่านี้ไม่ใช้ตัวระบุอุปกรณ์\n\nแอปสามารถคาดการณ์ประเภทของโฆษณาที่คุณอาจสนใจ และบันทึกความสนใจเหล่านี้ไว้บนอุปกรณ์ของคุณชั่วคราว วิธีนี้ช่วยให้แอปต่างๆ แสดงโฆษณาที่เกี่ยวข้องได้โดยที่ไม่ต้องติดตามกิจกรรมของคุณบนเว็บไซต์และแอปจากนักพัฒนารายอื่น"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"คุณไม่มีแอปที่ถูกบล็อก"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"ไม่มีความสนใจที่คุณบล็อกไว้"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"ยกเลิก"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"ปิด Privacy Sandbox ไหม"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"หากคุณเปลี่ยนใจหรือต้องการดูข้อมูลเพิ่มเติมสำหรับโปรแกรมความเป็นส่วนตัวเกี่ยวกับโฆษณาใน Android (เบต้า) ให้ไปที่การตั้งค่าความเป็นส่วนตัว"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"ปิด"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"บล็อก \"<xliff:g id="TOPIC">%1$s</xliff:g>\" ใช่ไหม"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"ระบบจะบล็อกความสนใจนี้และไม่เพิ่มลงในรายการอีกครั้ง เว้นแต่คุณจะเพิ่มกลับเข้าไป คุณอาจยังเห็นโฆษณาที่เกี่ยวข้องอยู่บ้าง"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"บล็อก"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"เลิกบล็อก \"<xliff:g id="TOPIC">%1$s</xliff:g>\" แล้ว"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android อาจเพิ่มความสนใจนี้ลงในรายการอีกครั้ง แต่อาจไม่ปรากฏโดยทันที"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ตกลง"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"รีเซ็ตความสนใจทั้งหมดไหม"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"ระบบจะล้างรายการของคุณและจะประเมินความสนใจใหม่ของคุณในอนาคต คุณอาจยังคงเห็นโฆษณาที่เกี่ยวข้องกับความสนใจที่ระบบล้างไปแล้วอยู่บ้าง"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"รีเซ็ต"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"บล็อก \"<xliff:g id="APP">%1$s</xliff:g>\" ใช่ไหม"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"แอปนี้จะไม่คาดการณ์ความสนใจสำหรับ Privacy Sandbox และไม่เพิ่มลงในรายการอีกครั้ง เว้นแต่คุณจะเลิกบล็อก\n\nระบบจะลบความสนใจที่แอปนี้คาดการณ์แล้ว แต่คุณอาจยังเห็นโฆษณาที่เกี่ยวข้องอยู่บ้าง"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"เลิกบล็อก \"<xliff:g id="APP">%1$s</xliff:g>\" แล้ว"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"แอปนี้คาดการณ์ความสนใจให้คุณอีกครั้งได้ แต่อาจไม่ปรากฏในรายการโดยทันที อาจใช้เวลาสักครู่จึงจะเห็นโฆษณาที่เกี่ยวข้อง"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"รีเซ็ตความสนใจที่สร้างโดยแอปไหม"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"ระบบจะลบความสนใจที่คาดการณ์โดยแอปในรายการของคุณออกจาก Privacy Sandbox โดยแอปจะคาดการณ์ความสนใจใหม่ในอนาคต คุณอาจยังคงเห็นโฆษณาที่เกี่ยวข้องอยู่บ้าง"</string>
<string name="topic10001" msgid="1636806320891333775">"ศิลปะและบันเทิง"</string>
<string name="topic10002" msgid="1226367977754287428">"การแสดงและโรงละคร"</string>
<string name="topic10003" msgid="6949890838957814881">"อะนิเมะและการ์ตูนญี่ปุ่น"</string>
diff --git a/adservices/apk/res/values-tl/strings.xml b/adservices/apk/res/values-tl/strings.xml
index ab0845d89..d2356d8ab 100644
--- a/adservices/apk/res/values-tl/strings.xml
+++ b/adservices/apk/res/values-tl/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Paano makilahok"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Kapag na-on ang beta, magbibigay-daan ito sa mga app na subukan ang mga bago at karagdagang pribadong paraang ito para magpakita sa iyo ng mga ad. Puwede mong i-off ang beta anumang oras sa iyong mga setting ng privacy."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Huwag na lang"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Oo, sasali ako"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"I-on"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Higit pa"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Salamat sa paglahok"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Bahagi ka ng beta ng privacy sa mga ad ng Android. Naka-on ang Privacy Sandbox para sa device mo.\n\nPuwede kang matuto pa o puwede mong i-off ang beta kahit kailan sa iyong mga setting ng privacy."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Pinili mong hindi makilahok"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Walang interes na maipapakita sa ngayon"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Nagbibigay ang beta ng privacy sa mga ad ng Android ng mga bagong feature na magagamit ng mga app para magpakita sa iyo ng mga ad na posibleng magustuhan mo. Hindi gumagamit ang mga teknolohiyang ito ng mga identifier ng device.\n\nMatatantya ng Android ang mga uri ng mga ad kung saan ka posibleng may interes, at pansamantalang sine-save ang mga interes sa device mo. Nagbibigay-daan ito sa mga app na magpakita sa iyo ng mga may kaugnayang ad, nang hindi sinusubaybayan ang aktibidad mo sa mga website at app mula sa iba pang developer."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Wala kang naka-block na interes"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Puwedeng tantyahin ng mga app ang iyong mga interes at pansamantalang i-save ang mga ito sa Android. Sa pagtagal, puwedeng may ibang app na magpakita sa iyo ng ad batay sa mga interes na ito.\n\nKung iba-block mo ang isang app, hindi na ito magtatantya ng mga interes. Hindi ito idaragdag ulit sa listahan ng mga app na ito maliban na lang kung i-unblock mo ito. Made-delete ang mga interes na tinatantya na ng app, pero posibleng makakita ka pa rin ng ilang nauugnay na ad."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Mga app na na-block mo"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"I-reset ang mga interes na tinatantya ng mga app"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Nagbibigay ang beta ng privacy sa mga ad ng Android ng mga bagong feature na magagamit ng mga app para magpakita sa iyo ng mga ad na posibleng magustuhan mo. Hindi gumagamit ang mga teknolohiyang ito ng mga identifier ng device.\n\nMatatantya ng mga app ang mga uri ng mga ad kung saan posibleng interesado ka, at pansamantalang sine-save ang mga interes sa device mo. Nagbibigay-daan ito sa mga app na magpakita sa iyo ng mga may kaugnayang ad, nang hindi sinusubaybayan ang aktibidad mo sa mga website at app mula sa iba pang developer."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Wala kang naka-block na app"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Wala kang naka-block na interes"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Kanselahin"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"I-off ang Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Kung magbabago ka ng isip o kung gusto mong matuto pa tungkol sa beta ng privacy sa mga ad ng Android, pumunta sa iyong mga setting ng privacy"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"I-off"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"I-block ang <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Iba-block ang interes na ito at hindi na ito idaragdag ulit sa iyong listahan, maliban kung idaragdag mo ito ulit. Posibleng makakita ka pa rin ng ilang nauugnay na ad."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"I-block"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Na-unblock ang <xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Puwedeng idagdag ulit ng Android ang interes na ito sa iyong listahan, pero posibleng hindi ito kaagad lumabas."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"I-reset ang lahat ng iyong interes?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Iki-clear ang iyong listahan at magtatantya ng mga bagong interes mula ngayon. Posibleng makakita ka pa rin ng ilang ad na nauugnay sa mga na-clear na interes."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"I-reset"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"I-block ang <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Hindi tatantyahin ng app na ito ang mga interes para sa Privacy Sandbox at hindi na ito idaragdag ulit sa iyong listahan, maliban kung ia-unblock mo ito.\n\nIde-delete ang mga interes na natantya na ng app na ito, pero posibleng makakita ka pa rin ng ilang nauugnay na ad."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Na-unblock ang <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Makakapagtantya ulit ang app na ito ng mga interes para sa iyo, pero posibleng hindi ito kaagad lumabas sa listahan mo. Posibleng matagalan bago ka makakita ng mga nauugnay na ad."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"I-reset ang mga interes na nabuo ng mga app?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Ide-delete sa Privacy Sandbox ang mga interes na tinantya ng mga app sa iyong listahan, at magtatantya ang mga app ng mga bagong interes mula ngayon. Posibleng makakita ka pa rin ng ilang nauugnay na ad."</string>
<string name="topic10001" msgid="1636806320891333775">"Sining at Entertainment"</string>
<string name="topic10002" msgid="1226367977754287428">"Pag-arte at Teatro"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime at Manga"</string>
diff --git a/adservices/apk/res/values-tr/strings.xml b/adservices/apk/res/values-tr/strings.xml
index e319fd76a..1e9977745 100644
--- a/adservices/apk/res/values-tr/strings.xml
+++ b/adservices/apk/res/values-tr/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Katılma yöntemi"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Beta programını açarsanız uygulamalar size reklam göstermek için daha gizlilik odaklı bu yeni yöntemleri test edebilir. Beta programını istediğiniz zaman gizlilik ayarlarınızdan kapatabilirsiniz."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Hayır, teşekkürler"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Evet, katılmak istiyorum"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Etkinleştir"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Daha fazla"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Katıldığınız için teşekkür ederiz"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Android reklam gizliliği beta programına dahil edildiniz. Özel Korumalı Alan, cihazınızda açıldı.\n\nDaha fazla bilgi edinebilir veya beta programını istediğiniz zaman gizlilik ayarlarınızdan kapatabilirsiniz."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Katılmamayı seçtiniz."</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Şu anda gösterilecek ilgi alanı yok"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Özel Korumalı Alan"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android reklam gizliliği beta programı, uygulamaların size beğenebileceğiniz reklamları göstermek için kullanabileceği yeni özellikler sunar. Bu teknolojiler, cihaz tanımlayıcılarını kullanmaz.\n\nAndroid, ilginizi çekebilecek reklam türlerini tahmin edebilir ve bu ilgi alanlarını geçici olarak cihazınıza kaydedebilir. Böylece, uygulamalar, web sitelerindeki ve diğer geliştiricilerin uygulamalarındaki etkinliklerinizi izlemeden size alakalı reklamlar gösterebilir."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Engellenmiş ilgi alanınız yok"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Uygulamalar, ilgi alanlarınızı tahmin ederek geçici bir süreliğine Android\'e kaydedebilir. Daha sonra farklı bir uygulama size bu ilgi alanlarına dayalı bir reklam gösterebilir.\n\nEngellediğiniz uygulamalar artık ilgi alanlarınızı tahmin edemez. Bu uygulamalar, engellemeyi kaldırmadığınız sürece bu uygulama listesine tekrar eklenmez. Uygulamanın daha önce tahmin ettiği ilgi alanları silinse de bunlarla alakalı reklamlar görmeye devam edebilirsiniz."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Engellediğiniz uygulamalar"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Uygulamaların tahmin ettiği ilgi alanlarını sıfırla"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Özel Korumalı Alan"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android reklam gizliliği beta programı, uygulamaların size beğenebileceğiniz reklamları göstermek için kullanabileceği yeni özellikler sunar. Bu teknolojiler, cihaz tanımlayıcılarını kullanmaz.\n\nUygulamalar, ilginizi çekebilecek reklam türlerini tahmin edebilir ve bu ilgi alanlarını geçici olarak cihazınıza kaydedebilir. Böylece, uygulamalar, web sitelerindeki ve diğer geliştiricilerin uygulamalarındaki etkinliklerinizi izlemeden size alakalı reklamlar gösterebilir."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Engellenmiş uygulamanız yok"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Engellenmiş ilgi alanınız yok"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"İptal"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Özel Korumalı Alan kapatılsın mı?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Fikrinizi değiştirirseniz veya Android reklam gizliliği beta sürümü hakkında daha fazla bilgi edinmek isterseniz gizlilik ayarlarınıza gidebilirsiniz"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Kapat"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> engellensin mi?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Tekrar eklemediğiniz sürece bu ilgi alanı engellenecek ve bir daha listenize eklenmeyecektir. Ancak, ilgi alanıyla alakalı bazı reklamları görmeye devam edebilirsiniz."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Engelle"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> engellemesi kaldırıldı"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android bu ilgi alanını tekrar listenize ekleyebilir ancak ilgi alanı hemen görünmeyebilir"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"Tamam"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Tüm ilgi alanlarınız sıfırlansın mı?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Listeniz temizlenir ve gelecekte yeni ilgi alanları tahmin edilir. Ancak, silinen ilgi alanlarıyla alakalı bazı reklamları görmeye devam edebilirsiniz."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Sıfırla"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> engellensin mi?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Engellemesini kaldırmadığınız sürece bu uygulama Özel Korumalı Alan için ilgi alanlarını tahmin etmeyecek ve bir daha listenize eklenmeyecektir.\n\nBu uygulamanın daha önce tahmin ettiği ilgi alanları silinecek olsa da bazı alakalı reklamları görmeye devam edebilirsiniz."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> engellemesi kaldırıldı"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Bu uygulama, ilgi alanlarınızı tahmin etmeye başlayacak olsa da ilgi alanları, listenizde hemen görünmeyebilir. Alakalı reklamları görmeye başlamanız biraz zaman alabilir."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Uygulamaların oluşturduğu ilgi alanları sıfırlansın mı?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Listenizdeki uygulamalar tarafından tahmin edilen ilgi alanları Özel Korumalı Alan\'dan silinir ve uygulamalar gelecekte yeni ilgi alanları tahmin eder. Ancak, ilgi alanıyla alakalı bazı reklamları görmeye devam edebilirsiniz."</string>
<string name="topic10001" msgid="1636806320891333775">"Sanat ve Eğlence"</string>
<string name="topic10002" msgid="1226367977754287428">"Oyunculuk ve Tiyatro"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime ve Manga"</string>
diff --git a/adservices/apk/res/values-uk/strings.xml b/adservices/apk/res/values-uk/strings.xml
index fc70e24d1..cfa72bb9d 100644
--- a/adservices/apk/res/values-uk/strings.xml
+++ b/adservices/apk/res/values-uk/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Як узяти участь"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Якщо ввімкнути бета-версію, додатки зможуть тестувати нові, більш захищені способи показу вам реклами. Ви будь-коли можете вимкнути бета-версію в налаштуваннях конфіденційності."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Ні, дякую"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Так, приєднатися"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Увімкнути"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Більше"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Дякуємо за участь"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Ви берете участь у програмі \"Конфіденційність у сфері реклами Android (бета-версія)\". Privacy Sandbox увімкнено для вашого пристрою.\n\nДізнатися більше або вимкнути бета-версію можна будь-коли в налаштуваннях конфіденційності."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Ви відмовилися від участі"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Наразі немає інтересів"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Програма \"Конфіденційність у сфері реклами Android (бета-версія)\" надає додаткам нові можливості для показу реклами, яка має вам сподобатися. Ці технології не використовують ідентифікатори пристроїв.\n\nСистема Android може визначати типи оголошень, які мають вас зацікавити, і тимчасово зберігає дані про такі інтереси на вашому пристрої. Завдяки цьому додатки, не відстежуючи ваші дії на веб-сайтах і в додатках інших розробників, показують вам доречну рекламу."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"У вас немає заблокованих інтересів"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Додатки можуть визначати ваші інтереси та тимчасово зберігати їх у системі Android. Згодом інший додаток може показувати вам рекламу на основі цих інтересів.\n\nЯкщо ви заблокуєте додаток, він більше не визначатиме ваші інтереси. Доки ви не розблокуєте додаток, він більше не з’явиться в цьому списку. Інтереси, визначені додатком раніше, буде видалено, але деякі пов’язані оголошення можуть показуватися й надалі."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Додатки, які ви заблокували"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Скинути інтереси, визначені додатками"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Програма \"Конфіденційність у сфері реклами Android (бета-версія)\" надає додаткам нові можливості для показу реклами, яка має вам сподобатися. Ці технології не використовують ідентифікатори пристроїв.\n\nДодатки можуть визначати типи оголошень, які мають вас зацікавити, і тимчасово зберігають дані про такі інтереси на вашому пристрої. Завдяки цьому додатки, не відстежуючи ваші дії на веб-сайтах і в додатках інших розробників, показують вам доречну рекламу."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"У вас немає заблокованих додатків"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"У вас немає заблокованих інтересів"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Скасувати"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Вимкнути Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Якщо ви передумаєте або захочете дізнатися більше про конфіденційність у сфері реклами Android (бета-версія), перейдіть у налаштування конфіденційності."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Вимкнути"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Заблокувати тему \"<xliff:g id="TOPIC">%1$s</xliff:g>\"?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Цей інтерес буде заблоковано. Він більше не відображатиметься в списку, доки ви знову його не додасте. Деякі пов’язані оголошення можуть показуватися й надалі."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Заблокувати"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Тему \"<xliff:g id="TOPIC">%1$s</xliff:g>\" розблоковано"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android може знову додати цей інтерес до вашого списку, однак, можливо, він з’явиться в ньому не відразу."</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Скинути всі ваші інтереси?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Ваш список буде очищено, і надалі визначатимуться нові інтереси. Деякі оголошення, пов’язані з видаленими інтересами, можуть показуватися й надалі."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Скинути"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Заблокувати додаток <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Цей додаток не визначатиме інтереси для Privacy Sandbox і більше не включатиметься в список, доки ви його не розблокуєте.\n\nІнтереси, визначені додатком раніше, буде видалено, але деякі пов’язані оголошення можуть показуватися й надалі."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Додаток <xliff:g id="APP">%1$s</xliff:g> розблоковано"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Цей додаток може знову визначати ваші інтереси, однак, можливо, він не відразу з’явиться у вашому списку. Перш ніж почнуть відображатися пов’язані оголошення, може пройти певний час."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Скинути інтереси, визначені додатками?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Інтереси, визначені додатками з вашого списку, буде видалено з Privacy Sandbox, і надалі додатки визначатимуть нові інтереси. Деякі пов’язані оголошення можуть показуватися й надалі."</string>
<string name="topic10001" msgid="1636806320891333775">"Мистецтво та розваги"</string>
<string name="topic10002" msgid="1226367977754287428">"Акторська майстерність і театр"</string>
<string name="topic10003" msgid="6949890838957814881">"Аніме та манга"</string>
diff --git a/adservices/apk/res/values-ur/strings.xml b/adservices/apk/res/values-ur/strings.xml
index 172f65d4f..10d95d4c2 100644
--- a/adservices/apk/res/values-ur/strings.xml
+++ b/adservices/apk/res/values-ur/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"شرکت کرنے کا طریقہ"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"بی ٹا کو آن کرنے سے، ایپس کو آپ کو اشتہارات دکھانے کیلئے ان مزید نئے طریقوں کو ٹیسٹ کرنے کی سہولت حاصل ہوتی ہے۔ آپ اپنی رازداری کی ترتیبات میں بی ٹا کو کسی بھی وقت آف کر سکتے ہیں۔"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"نہیں شکریہ"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"ہاں، میں شرکت کروں گا"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"آن کریں"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"مزید"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"شرکت کرنے کیلئے آپ کا شکریہ"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"‏آپ Android کے اشتہارات کی رازداری کے بی ٹا ورژن کا حصہ ہیں۔ آپ کے آلے کیلئے رازداری سینڈ باکس آن ہے۔\n\nآپ مزید جان سکتے ہیں یا اپنی رازداری کی ترتیبات میں بی ٹا کو کسی بھی وقت آف کر سکتے ہیں۔"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"آپ نے شامل نہ ہونا منتخب کیا ہے"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"فی الوقت دکھانے کے لیے کوئی دلچسپی نہیں ہے"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"رازداری سینڈ باکس"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"‏Android کے اشتہارات کی رازداری کا بی ٹا ورژن ایسی نئی خصوصیات فراہم کرتا ہے جن کا استعمال کر کے ایپس آپ کو پسند آ سکنے والے اشتہارات دکھا سکتی ہیں۔ یہ ٹیکنالوجیز آلے کے شناخت کاران کا استعمال نہیں کرتی ہیں۔\n\nAndroid یہ تخمینہ لگا سکتا ہے کہ آپ کو کن اقسام کے اشتہارات میں دلچسپی ہو سکتی ہے اور وہ آپ کے آلے پر عارضی طور پر دلچسپیوں کو محفوظ کر سکتا ہے۔ اس سے ایپس کو ویب سائٹس پر آپ کی سرگرمی اور دیگر ڈیولپرز کی ایپس کو ٹریک کئے بغیر آپ کو متعلقہ اشتہارات دکھانے کی سہولت حاصل ہوتی ہے۔"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"آپ کے پاس کوئی مسدود کردہ دلچسپی نہیں ہے"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"‏ایپس آپ کی دلچسپیوں کا تخمینہ لگا سکتی ہیں اور Android کی مدد سے انہیں عارضی طور پر محفوظ کر سکتی ہیں۔ بعد میں، کوئی مختلف ایپ ان دلچسپیوں کی بنیاد پر آپ کو ایک اشتہار دکھا سکتی ہے۔\n\nاگر آپ کسی ایپ کو مسدود کریں گے تو وہ مزید کسی دلچسپی کا تخمینہ نہیں لگائے گی۔ اسے اس وقت تک دوبارہ فہرست میں شامل نہیں کیا جائے گا جب تک آپ اسے غیر مسدود نہ کر دیں۔ ایپ کے ذریعے پہلے ہی سے تخمینہ شدہ دلچسپیاں حذف کر دی جائیں گی، لیکن ہو سکتا ہے کہ آپ کو اس کے باوجود کچھ متعلقہ اشتہارات دکھائی دیں۔"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"آپ کی مسدود کردہ ایپس"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"ایپس کے ذریعے تخمینہ شدہ دلچسپیوں کو ری سیٹ کریں"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"رازداری سینڈ باکس"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"‏Android کے اشتہارات کی رازداری کا بی ٹا ورژن ایسی نئی خصوصیات فراہم کرتا ہے جن کا استعمال کر کے ایپس آپ کو پسند آ سکنے والے اشتہارات دکھا سکتی ہیں۔ یہ ٹیکنالوجیز آلے کے شناخت کاران کا استعمال نہیں کرتی ہیں۔\n\nایپس یہ تخمینہ لگا سکتی ہیں کہ آپ کو کن اقسام کے اشتہارات میں دلچسپی ہو سکتی ہے اور وہ آپ کے آلے پر عارضی طور پر دلچسپیوں کو محفوظ کر سکتی ہیں۔ اس سے ایپس کو ویب سائٹس پر آپ کی سرگرمی اور دیگر ڈیولپرز کی ایپس کو ٹریک کئے بغیر آپ کو متعلقہ اشتہارات دکھانے کی سہولت حاصل ہوتی ہے۔"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"آپ کے پاس کوئی مسدود کردہ ایپ نہیں ہے"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"آپ کے پاس کوئی مسدود کردہ دلچسپی نہیں ہے"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"منسوخ کریں"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"رازداری سینڈ باکس آف کریں؟"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"‏اگر آپ اپنا ارادہ تبدیل کرتے ہیں یا Android کے اشتہارات کی رازداری کے بی ٹا کے بارے میں جاننا چاہتے ہیں تو اپنی رازداری کی ترتیبات میں جائیں"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"آف کریں"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> کو مسدود کریں؟"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"یہ دلچسپی حذف کر دی جائے گی اور جب تک آپ اسے واپس شامل نہیں کرتے ہیں، یہ آپ کی فہرست میں دوبارہ شامل نہیں کی جائے گی۔ آپ کو ابھی بھی کچھ متعلقہ اشتہارات نظر آ سکتے ہیں۔"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"مسدود کریں"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> غیر مسدود ہے"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"‏Android اس دلچسپی کو آپ کی فہرست میں دوبارہ شامل کر سکتی ہے لیکن ممکن ہے یہ فوری طور پر نظر نہ آئے"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"ٹھیک ہے"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"آپ کی تمام دلچسپیوں کو ری سیٹ کریں؟"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"آپ کی فہرست صاف کر دی جائے گی اور مستقبل میں نئی دلچسپیوں کا اندازہ لگایا جائے گا۔ آپ اب بھی صاف کردہ دلچسپیوں سے متعلق کچھ اشتہارات دیکھ سکتے ہیں۔"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"ری سیٹ کریں"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> کو مسدود کریں؟"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"یہ ایپ رازداری سینڈ باکس کے لئے دلچسپیوں کا اندازہ لگائے گی اور جب تک آپ اسے غیر مسدود نہیں کرتے ہیں، یہ آپ کی فہرست میں دوبارہ شامل نہیں کی جائے گی۔\n\nاس ایپ کے ذریعے پہلے سے اندازہ کردہ دلچسپیوں کو حذف کر دیا جائے گا لیکن آپ کو ابھی بھی کچھ متعلقہ اشتہارات نظر آ سکتے ہیں۔"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> غیر مسدود ہے"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"یہ ایپ آپ کے لئے دلچسپیوں کا پھر سے اندازہ لگا سکتی ہے لیکن ممکن ہے یہ فوری طور پر آپ کی فہرست پر نظر نہ آئے۔ آپ کو متعلقہ اشتہارات نظر آنے میں وقت لگ سکتا ہے۔"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"ایپس کے ذریعے تخلیق کی گئی دلچسپیوں کو ری سیٹ کریں؟"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"آپ کی فہرست میں موجود ایپس کے ذریعے تخمینہ کردہ دلچسپیوں کو رازداری سینڈ باکس سے حذف کر دیا جائے گا اور ایپس مستقبل میں مزید دلچسپیوں کا اندازہ لگائیں گی۔ آپ اب بھی کچھ متعلقہ اشتہارات دیکھ سکتے ہیں۔"</string>
<string name="topic10001" msgid="1636806320891333775">"آرٹس اور تفریح"</string>
<string name="topic10002" msgid="1226367977754287428">"اداکاری اور تھیٹر"</string>
<string name="topic10003" msgid="6949890838957814881">"اینیمی اور مانگا"</string>
diff --git a/adservices/apk/res/values-uz/strings.xml b/adservices/apk/res/values-uz/strings.xml
index a58b2d3cd..0112d7bf8 100644
--- a/adservices/apk/res/values-uz/strings.xml
+++ b/adservices/apk/res/values-uz/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Qanday qatnashish mumkin"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Beta versiya yoqilsa, ilovalar sizga reklama chiqarishning yanada maxfiy usullarini sinay oladi. Beta versiyani istalgan vaqt maxfiylik sozlamalari orqali faolsizlashtirish mumkin."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Kerak emas"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Ha, ishtirok etmoqchiman"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Yoqish"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Yana"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Ishtirokingiz uchun rahmat"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Siz Android ichida chiqadigan reklamalar maxfiyligi beta dasturidasiz. Qurilmangiz uchun Privacy Sandbox funksiyasi yoqilgan.\n\nMaxfiylik sozlamalari orqali istalgan vaqtda beta versiya haqida batafsil axborot olish yoki uni faolsizlantirish mumkin."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Siz dasturda qatnashishni rad etdingiz"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Hozircha hech qanday qiziqish mavjud emas"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android ichida chiqadigan reklamalar maxfiyligi beta dasturining yangi funksiyalari yordamida ilovalar sizga yoqishi mumkin reklama chiqara oladi. Bu texnologiyalar qurilma identifikatorlarini ishlatmaydi.\n\nAndroid sizga qiziq boʻlishi mumkin reklamalarni taxmin qiladi, qiziqishlar axborotini vaqtincha qurilmada saqlaydi. Bunda ilovalar boshqa ishlab chiquvchilarning saytlari va ilovalaridagi faoliyatingizni kuzatmagan holda sizga yanada mos reklamalarni chiqarishi mumkin."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Hech qaysi qiziqish bloklanmagan"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Android orqali ilovalar qiziqishlarni taxmin qilishi va vaqtincha saqlashi mumkin. Keyin boshqa ilovalar bu qiziqishlar asosida reklama chiqarishi mumkin.\n\nIlova bloklansa, u boshqa qiziqishlarni taxmin qila olmaydi. Ilova blokdan chiqarilmaguncha bu ilovalar roʻyxatiga qayta kiritilmaydi. Qiziqishlarni taxmin qilgan ilova oʻchiriladi, lekin hali ham ayrim tegishli reklamalar chiqib qolishi mumkin."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Siz bloklagan ilovalar"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Ilovalar taxmin qilgan qiziqishlarni tozalash"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android ichida chiqadigan reklamalar maxfiyligi beta dasturining yangi funksiyalari yordamida ilovalar sizga yoqishi mumkin reklama chiqara oladi. Bu texnologiyalar qurilma identifikatorlarini ishlatmaydi.\n\nIlovalar sizga qiziq boʻlishi mumkin reklamalarni taxmin qiladi, qiziqishlar axborotini vaqtincha qurilmada saqlaydi. Bunda ilovalar boshqa ishlab chiquvchilarning saytlari va ilovalaridagi faoliyatingizni kuzatmagan holda sizga yanada mos reklamalarni chiqarishi mumkin."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Hech qaysi ilova bloklanmagan"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Hech qaysi qiziqish bloklanmagan"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Bekor qilish"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Privacy Sandbox faolsizlantirilsinmi?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Fikringizni oʻzgartirsangiz yoki Android reklamalarining maxfiylik beta versiyasi haqida koʻproq bilmoqchi boʻlsangiz, maxfiylik sozlamalariga oʻting."</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Faolsizlantirish"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"<xliff:g id="TOPIC">%1$s</xliff:g> bloklansinmi?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Bu qiziqish bloklanadi va siz uni qayta qoʻshmasangiz roʻyxatingizga qoʻshilmaydi Aloqador ayrim reklamalar chiqib turishi mumkin."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Bloklash"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"<xliff:g id="TOPIC">%1$s</xliff:g> blokdan chiqarildi"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android ushbu qiziqishni yana roʻyxatingizga kiritishi, lekin u darhol chiqmasligi mumkin"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Barcha qiziqishlar asliga tiklansinmi?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Roʻyxatingiz tozalanadi va kelgusida yangi qiziqishlar taxmin qilinadi. Tozalangan qiziqishlarga aloqador ayrim reklamalar hamon chiqib turishi mumkin."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Tiklash"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"<xliff:g id="APP">%1$s</xliff:g> bloklansinmi?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Bu ilova Privacy Sandbox uchun qiziqishlarni hisoblamaydi va uni blokdan chiqarmaguningizcha roʻyxatingizga qayta qoʻshilmaydi.\n\nUshbu ilova tomonidan oldindan taxmin qilingan qiziqishlar oʻchirib tashlanadi, lekin shunga qaramay, ayrim aloqador reklamalarni koʻrishingiz mumkin."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"<xliff:g id="APP">%1$s</xliff:g> blokdan chiqarildi"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Bu ilova siz uchun yana qiziqishlarni taxmin qilishi mumkin, lekin u darhol roʻyxatingizda koʻrinmasligi mumkin. Tegishli reklamalarni koʻrish uchun biroz vaqt ketishi mumkin."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Ilovalar yaratgan qiziqishlar qayta tiklansinmi?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Roʻyxatingizdagi ilovalar tomonidan hisoblangan qiziqishlar Privacy Sandbox tizimidan oʻchiriladi va ilovalar kelgusida yangi qiziqishlarni taxmin qiladi. Aloqador ayrim reklamalar chiqib turishi mumkin."</string>
<string name="topic10001" msgid="1636806320891333775">"Sanʼat va hordiq"</string>
<string name="topic10002" msgid="1226367977754287428">"Aktyorlik va teatr"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime va manga"</string>
diff --git a/adservices/apk/res/values-vi/strings.xml b/adservices/apk/res/values-vi/strings.xml
index 07b41cd77..b915341f3 100644
--- a/adservices/apk/res/values-vi/strings.xml
+++ b/adservices/apk/res/values-vi/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Cách tham gia"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Khi bạn bật bản beta, các ứng dụng có thể thử nghiệm những cách mới, riêng tư hơn để hiển thị quảng cáo cho bạn. Bạn có thể tắt bản beta bất kỳ lúc nào trong chế độ cài đặt quyền riêng tư."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Không, cảm ơn"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Có, tôi sẽ tham gia"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Bật"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Xem thêm"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Cảm ơn bạn đã tham gia."</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Bạn là một phần của bản beta về quyền riêng tư trong quảng cáo của Android. Hộp cát về quyền riêng tư được bật cho thiết bị của bạn.\n\nBạn có thể tìm hiểu thêm hoặc tắt bản beta bất kỳ lúc nào trong mục cài đặt quyền riêng tư của mình."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Bạn đã chọn không tham gia"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Hiện bạn không có mối quan tâm nào"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Hộp cát về quyền riêng tư"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Bản beta về quyền riêng tư trong quảng cáo của Android cung cấp các tính năng mới mà ứng dụng có thể dùng để hiển thị những quảng cáo bạn có thể thích. Các công nghệ này không sử dụng số nhận dạng thiết bị.\n\nAndroid có thể ước tính các loại quảng cáo mà bạn có thể quan tâm và tạm thời lưu những sở thích này trên thiết bị của bạn. Điều này cho phép các ứng dụng hiển thị những quảng cáo có liên quan mà không cần theo dõi hoạt động của bạn trên các trang web cũng như ứng dụng từ nhà phát triển khác."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Bạn không có sở thích nào bị chặn"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Các ứng dụng có thể ước tính sở thích của bạn và tạm thời lưu những sở thích này trong Android. Sau đó, một ứng dụng khác có thể hiển thị cho bạn quảng cáo dựa trên những sở thích này.\n\nNếu bạn chặn một ứng dụng, ứng dụng đó sẽ không ước tính thêm sở thích nào nữa. Ứng dụng bị chặn sẽ không được thêm lại vào danh sách ứng dụng này trừ khi bạn bỏ chặn. Các sở thích đã được ứng dụng ước tính sẽ bị xóa, nhưng bạn vẫn có thể thấy một số quảng cáo có liên quan."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Các ứng dụng bạn đã chặn"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Đặt lại sở thích do ứng dụng ước tính"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Hộp cát về quyền riêng tư"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Bản beta về quyền riêng tư trong quảng cáo của Android cung cấp các tính năng mới mà ứng dụng có thể dùng để hiển thị những quảng cáo bạn có thể thích. Các công nghệ này không sử dụng số nhận dạng thiết bị.\n\nỨng dụng có thể ước tính các loại quảng cáo mà bạn có thể quan tâm và tạm thời lưu những sở thích này trên thiết bị của bạn. Điều này cho phép các ứng dụng hiển thị những quảng cáo có liên quan mà không cần theo dõi hoạt động của bạn trên các trang web cũng như ứng dụng từ nhà phát triển khác."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Bạn không có ứng dụng nào bị chặn"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Bạn không có sở thích nào bị chặn"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Huỷ"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Tắt Hộp cát về quyền riêng tư?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Nếu bạn đổi ý hoặc muốn tìm hiểu thêm về bản beta về quyền riêng tư trong quảng cáo của Android, hãy chuyển đến phần cài đặt quyền riêng tư"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Tắt"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Chặn chủ đề <xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Mối quan tâm này sẽ bị chặn và không được thêm lại vào danh sách của bạn, trừ phi bạn thêm lại mối quan tâm đó. Bạn có thể vẫn thấy một số quảng cáo liên quan."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Chặn"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"Đã bỏ chặn chủ đề <xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android có thể thêm lại mối quan tâm này vào danh sách của bạn, nhưng mối quan tâm có thể không xuất hiện ngay lập tức"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"OK"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Đặt lại tất cả các mối quan tâm của bạn?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Danh sách của bạn sẽ bị xoá và từ nay trở đi, các sở thích mới sẽ được dự đoán. Bạn vẫn có thể thấy một số quảng cáo liên quan đến các sở thích đã bị xoá."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Đặt lại"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Chặn ứng dụng <xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Ứng dụng này sẽ không dự đoán mối quan tâm của bạn khi bạn bật Hộp cát về quyền riêng tư. Ứng dụng này cũng sẽ không được thêm lại vào danh sách của bạn, trừ phi bạn bỏ chặn ứng dụng này.\n\nCác mối quan tâm mà ứng dụng này đã dự đoán sẽ bị xoá, nhưng bạn có thể vẫn thấy một số quảng cáo liên quan."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"Đã bỏ chặn ứng dụng <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Ứng dụng này có thể dự đoán lại mối quan tâm cho bạn, nhưng mối quan tâm có thể không xuất hiện ngay trong danh sách của bạn. Có thể mất chút thời gian thì bạn mới thấy quảng cáo liên quan."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Đặt lại mối quan tâm do các ứng dụng tạo?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Sở thích do các ứng dụng trong danh sách của bạn dự đoán sẽ bị xoá khỏi Hộp cát về quyền riêng tư và từ nay trở đi, các ứng dụng đó sẽ dự đoán sở thích mới. Bạn có thể vẫn thấy một số quảng cáo liên quan."</string>
<string name="topic10001" msgid="1636806320891333775">"Nghệ thuật &amp; Giải trí"</string>
<string name="topic10002" msgid="1226367977754287428">"Diễn xuất &amp; Kịch nghệ"</string>
<string name="topic10003" msgid="6949890838957814881">"Anime &amp; Manga"</string>
diff --git a/adservices/apk/res/values-zh-rCN/strings.xml b/adservices/apk/res/values-zh-rCN/strings.xml
index cd497d5bf..75f73cbec 100644
--- a/adservices/apk/res/values-zh-rCN/strings.xml
+++ b/adservices/apk/res/values-zh-rCN/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"参与方式"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"开启 Beta 版会允许应用测试这些更注重保护隐私的新方式以向您展示广告。您随时可在隐私设置中关闭 Beta 版。"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"不用了"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"是的,我加入"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"开启"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"更多"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"感谢您的加入"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"您已加入 Android 广告隐私权 Beta 版测试计划。我们已为您的设备开启 Privacy Sandbox。\n\n您随时可在隐私设置中了解详情或关闭 Beta 版。"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"您已选择不加入"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"目前没有可显示的感兴趣主题"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android 广告隐私权 Beta 版提供了新功能,以便应用向您展示您可能会喜欢的广告。这些技术不使用标识符。\n\nAndroid 可推测您可能会对哪些类型的广告感兴趣,并将这些兴趣暂时存储在您的设备上。这样一来,应用不必追踪您在其他开发者的网站和应用上的活动便能向您展示与您相关的广告。"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"您未屏蔽任何兴趣"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"应用可推测您的兴趣并将这些兴趣暂时存储在 Android 上。之后,另一个应用可基于这些兴趣向您展示广告。\n\n如果您屏蔽了某个应用,它便不会再推测您的兴趣。除非您取消屏蔽该应用,否则它不会再添加到这个应用列表中。该应用已推测出的兴趣将被删除,但您可能仍会看到一些与您相关的广告。"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"您屏蔽的应用"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"重置应用推测出的兴趣"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android 广告隐私权 Beta 版提供了新功能,以便应用向您展示您可能会喜欢的广告。这些技术不使用标识符。\n\n应用可推测您可能会对哪些类型的广告感兴趣,并将这些兴趣暂时存储在您的设备上。这样一来,应用不必追踪您在其他开发者的网站和应用上的活动便能向您展示与您相关的广告。"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"您未屏蔽任何应用"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"您未屏蔽任何兴趣"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"取消"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"要关闭 Privacy Sandbox 吗?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"如果您改变了主意,或者想要详细了解 Android 广告隐私权 Beta 版,请前往您的隐私设置"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"关闭"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"要屏蔽<xliff:g id="TOPIC">%1$s</xliff:g>吗?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"此感兴趣主题将被屏蔽,并且不会再次添加到您的列表中,除非您重新添加此主题。您可能仍会看到一些相关广告。"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"屏蔽"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"已取消屏蔽<xliff:g id="TOPIC">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android 可能还会将此感兴趣主题再次添加到您的列表中,但该主题可能不会立即显示"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"确定"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"要重置所有感兴趣主题吗?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"系统将清除兴趣喜好列表,之后会重新推测您的兴趣喜好。您可能还是会看到一些与清除的兴趣喜好相关的广告。"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"重置"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"要屏蔽“<xliff:g id="APP">%1$s</xliff:g>”吗?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"启用 Privacy Sandbox 后,此应用将不会预测您的感兴趣主题,也不会将这些主题再次添加到您的列表中,除非您取消屏蔽该应用。\n\n此应用已预测的感兴趣主题将被删除,但您仍可能会看到一些相关广告。"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"已取消屏蔽<xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"此应用可能会再次推测您感兴趣的主题,但这些主题可能不会立即在您的列表中显示。您可能需要过段时间才能看到相关广告。"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"要重置应用生成的感兴趣主题吗?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"系统将从 Privacy Sandbox 中删除应用推测的兴趣喜好列表,应用之后会重新推测您的兴趣喜好。但您可能还是会看到一些相关的广告。"</string>
<string name="topic10001" msgid="1636806320891333775">"艺术与娱乐"</string>
<string name="topic10002" msgid="1226367977754287428">"演出与剧场"</string>
<string name="topic10003" msgid="6949890838957814881">"动漫与日本漫画"</string>
diff --git a/adservices/apk/res/values-zh-rHK/strings.xml b/adservices/apk/res/values-zh-rHK/strings.xml
index c3227fcbc..6983c8c8c 100644
--- a/adservices/apk/res/values-zh-rHK/strings.xml
+++ b/adservices/apk/res/values-zh-rHK/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"參與計劃方式"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"開啟 Beta 版本後,應用程式就可測試這些更能保護私隱的新方式,向您顯示廣告。您可隨時在私隱權設定中停用 Beta 版本。"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"不用了,謝謝"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"是,我要加入"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"開啟"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"更多內容"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"感謝您的參與"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"您已加入 Android 廣告私隱權 Beta 版本計劃。您的裝置已啟用私隱沙箱。\n\n您可隨時在私隱權設定中瞭解詳情或停用 Beta 版本。"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"您已選擇不參與"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"目前沒有可顯示的興趣"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"私隱沙箱"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android 廣告私隱權 Beta 版本提供全新功能,讓應用程式可向您顯示可能感興趣的廣告。這些技術不會使用裝置識別碼。\n\n推測您可能感興趣的廣告類別,並在裝置上暫時儲存興趣。這允許應用程式向您顯示相關的廣告,但不會追蹤您在其他開發人員的網站和應用程式內的活動。"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"您沒有封鎖任何興趣"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"應用程式可推測您的興趣,並暫時儲存在 Android 系統中。然後,另一個應用程式可根據這些興趣向您顯示廣告。\n\n如果您封鎖某個應用程式,該應用程式就不會再推測其他興趣。除非您解除封鎖,否則該應用程式不會再加入此應用程式清單。系統會刪除應用程式之前推測的興趣,但您仍可能看到部分相關的廣告。"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"您已封鎖的應用程式"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"重設應用程式推測的興趣"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"私隱沙箱"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android 廣告私隱權 Beta 版本提供全新功能,讓應用程式可向您顯示可能感興趣的廣告。這些技術不會使用裝置識別碼。\n\n應用程式可推測您可能感興趣的廣告類別,並在裝置上暫時儲存興趣。這允許應用程式向您顯示相關的廣告,但不會追蹤您在其他開發人員的網站和應用程式內的活動。"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"您沒有封鎖任何應用程式"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"您沒有封鎖任何興趣"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"取消"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"要關閉私隱沙箱嗎?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"如果您改變主意或希望瞭解 Android 廣告私隱權 Beta 版本,請前往私隱權設定。"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"關閉"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"要封鎖「<xliff:g id="TOPIC">%1$s</xliff:g>」嗎?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"除非您自行加回,否則系統將封鎖此興趣,且不會再加入清單。您可能仍會看到部分相關的廣告。"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"封鎖"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"已解除封鎖「<xliff:g id="TOPIC">%1$s</xliff:g>」"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android 可能會再將此興趣加入清單,但未必立即顯示"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"確定"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"要重設所有興趣嗎?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"系統將清除您的清單,並會重新估計您的興趣。您可能仍會看到一些與已清除的興趣相關的廣告。"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"重設"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"要封鎖「<xliff:g id="APP">%1$s</xliff:g>」嗎?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"加入私隱沙箱後,此應用程式將不會推測您的興趣。除非您解除封鎖,否則系統不會再將應用程式加入清單。\n\n系統會刪除應用程式之前推測的興趣,但您可能仍會看到部分相關的廣告。"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"已解除封鎖「<xliff:g id="APP">%1$s</xliff:g>」"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"此應用程式可再推測您的興趣,但未必立即在清單上顯示。相關廣告可能需要一段時間後才會顯示。"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"要重設由應用程式產生的興趣嗎?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"系統將從「私隱沙箱」中刪除應用程式估計的興趣清單內容,而應用程式之後會重新估計你的興趣。您可能仍會看到部分相關的廣告。"</string>
<string name="topic10001" msgid="1636806320891333775">"藝術和娛樂"</string>
<string name="topic10002" msgid="1226367977754287428">"表演和戲劇"</string>
<string name="topic10003" msgid="6949890838957814881">"動漫和日本漫畫"</string>
diff --git a/adservices/apk/res/values-zh-rTW/strings.xml b/adservices/apk/res/values-zh-rTW/strings.xml
index 7f397ad05..3c8511093 100644
--- a/adservices/apk/res/values-zh-rTW/strings.xml
+++ b/adservices/apk/res/values-zh-rTW/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"如何加入本計畫"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"如果開啟 Beta 版,應用程式就可以測試這些新技術,以更保護隱私的方式向你顯示廣告。你隨時可以前往隱私權設定關閉 Beta 版。"</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"不用了,謝謝"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"是,我要加入"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"開啟"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"更多內容"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"感謝你的參與。"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"你已加入 Android 廣告隱私權 Beta 版測試計畫。你的裝置已開啟 Privacy Sandbox。\n\n你隨時都可以前往隱私權設定瞭解詳情或關閉 Beta 版。"</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"你已選擇不參與"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"目前沒有可顯示的興趣喜好"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"Android 廣告隱私權 Beta 版提供全新功能,可讓應用程式用來向你顯示可能喜歡的廣告。這些技術並未使用裝置 ID。\n\nAndroid 可以推測你可能感興趣的廣告類別,並在裝置上暫時儲存你的興趣喜好。這樣一來,應用程式不必追蹤你在其他開發商的網站和應用程式內的活動,也能向你顯示貼近需求的廣告。"</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"你沒有封鎖任何興趣喜好"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"應用程式可以推測你的興趣喜好,並暫時儲存在 Android 系統中。之後,其他應用程式可以根據這些興趣喜好向你顯示廣告。\n\n如果你封鎖某個應用程式,該應用程式就不會再推測興趣喜好。除非你解除封鎖,否則該應用程式不會再加入這份應用程式清單。系統將刪除這個應用程式已推測出的興趣喜好,不過你可能還是會看到某些相關的廣告。"</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"已封鎖的應用程式"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"重設應用程式推測的興趣喜好"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"Android 廣告隱私權 Beta 版提供全新功能,可讓應用程式用來向你顯示可能喜歡的廣告。這些技術並未使用裝置 ID。\n\n應用程式可以推測你可能感興趣的廣告類別,並在裝置上暫時儲存你的興趣喜好。這樣一來,應用程式不必追蹤你在其他開發商的網站和應用程式內的活動,也能向你顯示貼近需求的廣告。"</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"你沒有封鎖任何應用程式"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"你沒有封鎖任何興趣喜好"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"取消"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"要關閉 Privacy Sandbox 嗎?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"如果你改變心意,或想進一步瞭解 Android 的廣告隱私權 Beta 版計畫,請前往隱私權設定"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"關閉"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"要封鎖「<xliff:g id="TOPIC">%1$s</xliff:g>」嗎?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"系統將封鎖這個興趣喜好,而且不會再新增到清單 (除非你自行加回)。請注意,你可能還是會看到一些相關的廣告。"</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"封鎖"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"已解除封鎖「<xliff:g id="TOPIC">%1$s</xliff:g>」"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"Android 可能會再次將這項興趣喜好新增到你的清單,但或許不會立即顯示"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"確定"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"要重設所有興趣喜好嗎?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"系統將清除清單內容,之後會重新推測你的興趣喜好。系統清除清單中的興趣喜好後,你可能還是會看到一些相關的廣告。"</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"重設"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"要封鎖「<xliff:g id="APP">%1$s</xliff:g>」嗎?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"加入 Privacy Sandbox 後,這個應用程式將無法推測你的興趣喜好,系統也不會再將應用程式新增到清單 (除非你解除封鎖)。\n\n系統將刪除這個應用程式推測的興趣喜好,但你可能還是會看到一些相關的廣告。"</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"已解除封鎖「<xliff:g id="APP">%1$s</xliff:g>」"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"這個應用程式將可再推測你的興趣喜好,但可能不會立即顯示在你的清單上。相關廣告可能要一段時間後才會顯示。"</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"要重設由應用程式產生的興趣喜好嗎?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"系統將從 Privacy Sandbox 中刪除應用程式推測的興趣喜好清單內容,應用程式之後會重新推測你的興趣喜好。請注意,你可能還是會看到一些相關的廣告。"</string>
<string name="topic10001" msgid="1636806320891333775">"藝術與娛樂"</string>
<string name="topic10002" msgid="1226367977754287428">"表演與戲劇"</string>
<string name="topic10003" msgid="6949890838957814881">"動漫與日本漫畫"</string>
diff --git a/adservices/apk/res/values-zu/strings.xml b/adservices/apk/res/values-zu/strings.xml
index de06397c9..6bac8cabc 100644
--- a/adservices/apk/res/values-zu/strings.xml
+++ b/adservices/apk/res/values-zu/strings.xml
@@ -47,7 +47,8 @@
<string name="notificationUI_container2_title_eu" msgid="7198761373796187596">"Indlela yokuhlanganyela"</string>
<string name="notificationUI_container2_body_text_eu" msgid="5763735880112009239">"Ukuvula i-beta kuvumela ama-app ukuthi ahlole lezi zindlela ezintsha, zokugodliwe zokukubonisa izikhangiso. Ungakwazi ukuvala i-beta noma nini kumasethingi akho obumfihlo."</string>
<string name="notificationUI_left_control_button_text_eu" msgid="3573715612232957187">"Cha ngiyabonga"</string>
- <string name="notificationUI_right_control_button_text_eu" msgid="6323638565116618581">"Yebo, ngizojoyina"</string>
+ <string name="notificationUI_right_control_button_text_eu" msgid="8484787286437052150">"Vula"</string>
+ <string name="notificationUI_more_button_text" msgid="8861584693765844519">"Okwengeziwe"</string>
<string name="notificationUI_confirmation_accept_title" msgid="3178322390976087463">"Siyabonga ngokubamba iqhaza"</string>
<string name="notificationUI_confirmation_accept_subtitle" msgid="6980730715078085252">"Uyingxenye ye-beta yobumfihlo yezikhangiso ze-Android. I-Privacy Sandbox ivuliwe kudivayisi yakho.\n\nUngafunda kabanzi noma uvale i-beta noma nini kumasethingi akho obumfihlo."</string>
<string name="notificationUI_confirmation_decline_title" msgid="6453417169877486440">"Ukhethe ukungabambi iqhaza"</string>
@@ -79,8 +80,7 @@
<string name="settingsUI_topics_view_no_topics_text" msgid="912262290260184604">"Azikho izintshisekelo ongazibonisa njengamanje"</string>
<string name="settingsUI_topics_view_info_text1" msgid="2477020249748216309">"I-Privacy Sandbox"</string>
<string name="settingsUI_topics_view_info_text2" msgid="6223353589295876851">"I-beta yobumfihlo yezikhangiso ze-Android inikeza izakhi ezintsha ezingasetshenziswa ama-app ukuze akubonise izikhangiso ongase uzithande. Lobu buchwepheshe abuzisebenzisi izinkomba zedivayisi.\n\nI-Android ingalinganisela izinhlobo zezikhangiso ongaba nentshisekelo kuzo, futhi ilondoloze lezi zintshisekelo zakho okwesikhashana kudivayisi yakho. Lokhu kuvumela ama-app ukuthi akubonise izikhangiso ezihambisanayo, ngaphandle kokulandelela umsebenzi wakho kuwo wonke amawebhusayithi nama-app avela kwabanye onjiniyela."</string>
- <!-- no translation found for settingsUI_apps_view_title (5751645180393634322) -->
- <skip />
+ <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Awunakho ongaba nentshiseklo kukho okuvinjiwe"</string>
<string name="settingsUI_apps_view_subtitle" msgid="8193343057416677400">"Ama-app angalinganisela ongaba nentshisekelo kukho futhi alondoloze lezi okwesikhashana nge-Android. Kamuva, i-app ehlukile ingakubonisa isikhangiso esisekelwe kulezi zintshisekelo.\n\nUma uvimba i-app, ngeke iphinde isikisele okunye ongaba nentshisekelo kukho. Ngeke iphinde yengezwe kulolu uhlu lwama-app ngaphandle kokuthi uyivulele. Ongaba netshisekelo kukho okusikiselwe kakade i-app kuzosulwa, kodwa usengabona ezinye izikhangiso ezihambisanayo."</string>
<string name="settingsUI_blocked_apps_title" msgid="3902259560117462894">"Ama-app owavimbile"</string>
<string name="settingsUI_reset_apps_title" msgid="6221704974528131179">"Setha kabusha ongaba nentshisekelo kukho okusikiselwe ama-app"</string>
@@ -88,7 +88,25 @@
<string name="settingsUI_apps_view_info_text1" msgid="7835635425477842692">"I-Privacy Sandbox"</string>
<string name="settingsUI_apps_view_info_text2" msgid="308547275474796552">"I-beta yobumfihlo yezikhangiso ze-Android inikeza izakhi ezintsha ezingasetshenziswa ama-app ukuze akubonise izikhangiso ongase uzithande. Lobu buchwepheshe abuzisebenzisi izinkomba zedivayisi.\n\nAma-app angalinganisela izinhlobo zezikhangiso ongaba nentshisekelo kuzo, futhi alondoloze lezi zintshisekelo zakho okwesikhashana kudivayisi yakho. Lokhu kuvumela ama-app ukuthi akubonise izikhangiso ezihambisanayo, ngaphandle kokulandelela umsebenzi wakho kuwo wonke amawebhusayithi nama-app avela kwabanye onjiniyela."</string>
<string name="settingsUI_apps_view_no_blocked_apps_text" msgid="3953560911214151396">"Awunawo ama-app avinjiwe"</string>
- <string name="settingsUI_topics_view_no_blocked_topics_text" msgid="6553014185055718781">"Awunakho ongaba nentshiseklo kukho okuvinjiwe"</string>
+ <string name="settingsUI_dialog_negative_text" msgid="3014088254631375852">"Khansela"</string>
+ <string name="settingsUI_dialog_opt_out_title" msgid="1862069591629418291">"Vala i-Privacy Sandbox?"</string>
+ <string name="settingsUI_dialog_opt_out_message" msgid="5696012172835241848">"Uma ushintsha umqondo wakho noma ufuna ukufunda okwengeziwe mayelana ne-beta yobumfihlo yezikhangiso ze-Android, yiya kumasethingi akho obumfihlo"</string>
+ <string name="settingsUI_dialog_opt_out_positive_text" msgid="5344259263448028614">"Vala"</string>
+ <string name="settingsUI_dialog_block_topic_title" msgid="5744004384480280782">"Vimba i-<xliff:g id="TOPIC">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_topic_message" msgid="2474902516721078270">"Le ntshisekelo izovinjwa futhi ngeke ingezwe ohlwini lwakho futhi, ngaphandle uma uyengeza futhi. Usengabona ezinye zezikhangiso ezihlobene."</string>
+ <string name="settingsUI_dialog_block_topic_positive_text" msgid="2626152417590300510">"Vimba"</string>
+ <string name="settingsUI_dialog_unblock_topic_title" msgid="7361000923920947234">"I-<xliff:g id="TOPIC">%1$s</xliff:g> ivulelwe"</string>
+ <string name="settingsUI_dialog_unblock_topic_message" msgid="4058967523591550460">"I-Android ingase yengeze le ntshisekelo ohlwini lwakho futhi, kodwa ingase ingaveli ngokushesha"</string>
+ <string name="settingsUI_dialog_unblock_topic_positive_text" msgid="2192452325573789737">"KULUNGILE"</string>
+ <string name="settingsUI_dialog_reset_topic_title" msgid="1949146936958849544">"Setha kabusha zonke izintshisekelo zakho?"</string>
+ <string name="settingsUI_dialog_reset_topic_message" msgid="3993838583084657398">"Uhlu lwakho luzosulwa futhi intshisekelo entsha izolinganiswa ukuya phambili. Usangabona eziny izikhangiso ezihlobene nentshisekelo esuliwe."</string>
+ <string name="settingsUI_dialog_reset_topic_positive_text" msgid="8345697359799280761">"Setha kabusha"</string>
+ <string name="settingsUI_dialog_block_app_title" msgid="421160714549264414">"Vimba i-<xliff:g id="APP">%1$s</xliff:g>?"</string>
+ <string name="settingsUI_dialog_block_app_message" msgid="4703493174054204149">"Le-app ngeke ilinganisele ongaba nentshisekelo kukho ku-Privacy Sandbox futhi ngeke lungezwe ohlwini lwakho futhi, ngaphandle kokuthi uluvule.\n\nIzintshisekelo ezivele zilinganiselwe yile-app zizosulwa, kodwa usengabona ezinye izikhangiso ezihlobene."</string>
+ <string name="settingsUI_dialog_unblock_app_title" msgid="4360730780049298286">"I-<xliff:g id="APP">%1$s</xliff:g> ivulelwe"</string>
+ <string name="settingsUI_dialog_unblock_app_message" msgid="7858109719986236041">"Le-app ingalinganisela izinto ongaba nentshisekelo kuzo futhi, kodwa ingase ingaveli ohlwini lwakho ngokushesha. Kungase kuthathe isikhashana ukuze ubone izikhangiso ezihlobene."</string>
+ <string name="settingsUI_dialog_reset_app_title" msgid="7193520707240514769">"Setha kabusha izintshisekelo ezikhiqizwe ama-app?"</string>
+ <string name="settingsUI_dialog_reset_app_message" msgid="1487583055097801881">"Intshisekelo elinganiswa ama-app ohlwini lwakho izosulwa ku-Privacy Sandbox, futhi ama-app azolinganisa intshisekelo entsha ukuya phambili. Usengabona ezinye zezikhangiso ezihlobene."</string>
<string name="topic10001" msgid="1636806320891333775">"Ubuciko Nokokuzijabulisa"</string>
<string name="topic10002" msgid="1226367977754287428">"Ukulingisa Netiyetha"</string>
<string name="topic10003" msgid="6949890838957814881">"I-Anime ne-Manga"</string>
diff --git a/adservices/apk/res/values/colors.xml b/adservices/apk/res/values/colors.xml
index 5b8f0ecaf..d428cdb75 100644
--- a/adservices/apk/res/values/colors.xml
+++ b/adservices/apk/res/values/colors.xml
@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <color name="background_color">#F2F2F2</color>
+ <color name="background_color">@color/settingslib_background_device_default_light</color>
<color name="divider_color">#E3E3E3</color>
<color name="card_background_color">#F9F9F9</color>
- <color name="primary_text_color">#1B1C17</color>
- <color name="secondary_text_color">#474747</color>
-
+ <color name="primary_text_color">@color/settingslib_text_color_primary_device_default</color>
+ <color name="secondary_text_color">@color/settingslib_text_color_secondary_device_default</color>
<!-- Material inverse ripple color, useful for inverted backgrounds. -->
<color name="ripple_material_inverse">@*android:color/ripple_material_dark</color>
</resources>
diff --git a/adservices/apk/res/values/dimens.xml b/adservices/apk/res/values/dimens.xml
new file mode 100644
index 000000000..882854c35
--- /dev/null
+++ b/adservices/apk/res/values/dimens.xml
@@ -0,0 +1,4 @@
+<resources>
+ <dimen name="disabled_button_alpha" format="float">0.3</dimen>
+ <dimen name="enabled_button_alpha" format="float">1.0</dimen>
+</resources> \ No newline at end of file
diff --git a/adservices/apk/res/values/strings.xml b/adservices/apk/res/values/strings.xml
index beb62883e..1bf8f3b15 100644
--- a/adservices/apk/res/values/strings.xml
+++ b/adservices/apk/res/values/strings.xml
@@ -99,7 +99,9 @@
<!-- Text for button to decline joining the Privacy Sandbox. [CHAR LIMIT=NONE] -->
<string name="notificationUI_left_control_button_text_eu">No thanks</string>
<!-- Text for button to accept joining the Privacy Sandbox. [CHAR LIMIT=NONE] -->
- <string name="notificationUI_right_control_button_text_eu">Yes, I’ll join</string>
+ <string name="notificationUI_right_control_button_text_eu">Turn On</string>
+ <!-- Text for button that scrolls the page down to view more text. [CHAR LIMIT=NONE] -->
+ <string name="notificationUI_more_button_text">More</string>
<!-- EU Detailed Notification Activity Confirmation Pages UI strings ************************-->
<!-- Text for the title of the confirmation screen for accepting to be part of the Privacy Sandbox Beta. [CHAR LIMIT=NONE] -->
@@ -126,7 +128,6 @@
<!-- ROW Detailed Notification Activity Landing Page UI strings *****************************-->
<!-- Text for the title of the header. [CHAR LIMIT=NONE] -->
<string name="notificationUI_header_title" translatable="false">@string/notificationUI_header_title_eu</string>
-
<!-- Text for container1 title. [CHAR LIMIT=NONE] -->
<string name="notificationUI_container1_title" translatable="false">@string/notificationUI_container1_title_eu</string>
<!-- Text for container1 body text. [CHAR LIMIT=NONE] -->
@@ -205,12 +206,12 @@
<string name="settingsUI_topics_view_info_text1">Privacy Sandbox</string>
<!-- Info text at the bottom of the topics view. [CHAR LIMIT=NONE] -->
<string name="settingsUI_topics_view_info_text2">Android’s ads privacy beta provides new features that apps can use to show you ads you might like. These technologies don’t use device identifiers.\n\nAndroid can estimate the kinds of ads you might be interested in, and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers.</string>
-
- <!-- TODO(b/242242693): add texts for topics confirmation dialogue.-->
+ <!-- Text to show if there are no topics. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_topics_view_no_blocked_topics_text">You have no blocked interests</string>
<!-- Settings App UI apps view strings ****************************************************-->
<!-- Title text of the apps view. [CHAR LIMIT=NONE] -->
- <string name="settingsUI_apps_view_title">@string/settingsUI_apps_title</string>
+ <string name="settingsUI_apps_view_title" translatable="false">@string/settingsUI_apps_title</string>
<!-- Sub title text of the apps view. [CHAR LIMIT=NONE] -->
<string name="settingsUI_apps_view_subtitle">Apps can estimate your interests and temporarily save these with Android. Later, a different app can show you an ad based on these interests.\n\nIf you block an app, it won’t estimate any more interests. It won’t be added to this list of apps again unless you unblock it. Interests already estimated by the app will be deleted, but you might still see some related ads.</string>
<!-- Text for the button to block an app. [CHAR LIMIT=NONE] -->
@@ -227,14 +228,54 @@
<string name="settingsUI_apps_view_info_text1">Privacy Sandbox</string>
<!-- Info text at the bottom of the apps view. [CHAR LIMIT=NONE] -->
<string name="settingsUI_apps_view_info_text2">Android’s ads privacy beta provides new features that apps can use to show you ads you might like. These technologies don’t use device identifiers.\n\nApps can estimate the kinds of ads you might be interested in, and save these interests temporarily on your device. This allows apps to show you relevant ads, without tracking your activity across websites and apps from other developers.</string>
-
- <!-- TODO(b/242242693): add texts for apps confirmation dialogue.-->
-
- <!-- TODO: TBD -->
<!-- Text to show if there are no apps. [CHAR LIMIT=NONE] -->
<string name="settingsUI_apps_view_no_blocked_apps_text">You have no blocked apps</string>
- <!-- Text to show if there are no topics. [CHAR LIMIT=NONE] -->
- <string name="settingsUI_topics_view_no_blocked_topics_text">You have no blocked interests</string>
+
+ <!-- Settings App UI dialog strings *******************************************************-->
+ <!-- Text for the button to cancel out of a dialog. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_negative_text">Cancel</string>
+ <!-- Title for the dialog to opt out of the Privacy Sandbox. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_opt_out_title">Turn off Privacy Sandbox?</string>
+ <!-- Message for the dialog to opt out of the Privacy Sandbox. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_opt_out_message">If you change your mind or want to learn more about Android’s ads privacy beta, go to your privacy settings</string>
+ <!-- Text for the button in a dialog to opt out of the Privacy Sandbox. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_opt_out_positive_text">Turn off</string>
+ <!-- Title for the dialog to block a topic. This dialog is confirming that the user wants to block the specified topic. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_block_topic_title">Block <xliff:g id="topic" example="Fashion">%1$s</xliff:g>?</string>
+ <!-- Body text for the dialog to block a topic. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_block_topic_message">This interest will be blocked and won’t be added to your list again, unless you add it back. You may still see some related ads.</string>
+ <!-- Text for button in a dialog to block a topic. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_block_topic_positive_text">Block</string>
+ <!-- Title for the dialog to unblock a topic. This dialog is notifying that the user has unblocked the specified topic. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_unblock_topic_title"><xliff:g id="topic" example="Fashion">%1$s</xliff:g> is unblocked</string>
+ <!-- Body text for the dialog to unblock a topic. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_unblock_topic_message">Android may add this interest to your list again, but it might not appear immediately</string>
+ <!-- Text for button in a dialog to unblock a topic. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_unblock_topic_positive_text">OK</string>
+ <!-- Title for the dialog to reset all topics. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_reset_topic_title">Reset all of your interests?</string>
+ <!-- Body text for the dialog to reset all topics. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_reset_topic_message">Your list will be cleared and new interests will be estimated going forward. You may still see some ads related to cleared interests.</string>
+ <!-- Text for the button in a dialog to reset all topics. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_reset_topic_positive_text">Reset</string>
+ <!-- Title for the dialog to block a an app. This dialog is confirming that the user wants to block the specified app from estimating interests. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_block_app_title">Block <xliff:g id="app" example="AirBnB">%1$s</xliff:g>?</string>
+ <!-- Body text for the dialog to block an app. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_block_app_message">This app won’t estimate interests for the Privacy Sandbox and won’t be added to your list again, unless you unblock it.\n\nInterests already estimated by this app will be deleted, but you might still see some related ads.</string>
+ <!-- Text for button in a dialog to block an app. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_block_app_positive_text" translatable="false">@string/settingsUI_dialog_block_topic_positive_text</string>
+ <!-- Title for the dialog to unblock an app. This dialog is notifying that the user has unblocked the specified app. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_unblock_app_title"><xliff:g id="app" example="AirBnB">%1$s</xliff:g> is unblocked</string>
+ <!-- Body text for the dialog to unblock an app. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_unblock_app_message">This app can estimate interests for you again, but it might not appear on your list immediately. It might take a while for you to see related ads.</string>
+ <!-- Text for button in a dialog to unblock an app. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_unblock_app_positive_text" translatable="false">@string/settingsUI_dialog_unblock_topic_positive_text</string>
+ <!-- Title for the dialog to reset all apps. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_reset_app_title">Reset interests generated by apps?</string>
+ <!-- Body text for the dialog to reset all apps. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_reset_app_message">Interests estimated by the apps on your list will be deleted from the Privacy Sandbox, and the apps will estimate new interests going forward. You may still see some related ads.</string>
+ <!-- Text for the button in a dialog to reset all apps. [CHAR LIMIT=NONE] -->
+ <string name="settingsUI_dialog_reset_app_positive_text" translatable="false">@string/settingsUI_dialog_reset_topic_positive_text</string>
<!-- All Topics strings *********************************************************************-->
<!-- Text for the topic name for topic Arts &amp; Entertainment. [CHAR LIMIT=NONE] -->
diff --git a/adservices/apk/res/values/styles.xml b/adservices/apk/res/values/styles.xml
index 9937271a0..f9915b3b6 100644
--- a/adservices/apk/res/values/styles.xml
+++ b/adservices/apk/res/values/styles.xml
@@ -20,8 +20,7 @@
<item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
</style>
-<!-- <style name="MainStyle.NotificationPrimaryButton" parent="android:Widget.Material.Button">-->
- <style name="MainStyle.NotificationPrimaryButton">
+ <style name="MainStyle.PrimaryButton">
<item name="android:theme">@style/RoundedCornerThemeOverlay</item>
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
@@ -30,6 +29,10 @@
<item name="android:layout_marginTop">16dp</item>
<item name="android:backgroundTint">@color/settingslib_state_on_color</item>
<item name="android:colorControlHighlight">@color/ripple_material_inverse</item>
+ <item name="android:textColor">@color/settingslib_primary_dark_device_default_settings</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
</style>
<style name="RoundedCornerThemeOverlay">
diff --git a/adservices/apk/res/values/themes.xml b/adservices/apk/res/values/themes.xml
index 9c537cbe0..554be28b9 100644
--- a/adservices/apk/res/values/themes.xml
+++ b/adservices/apk/res/values/themes.xml
@@ -14,13 +14,6 @@
limitations under the License.
-->
<resources>
- <!-- This theme should contain attributes that should always be set despite OEM overlays. -->
- <style name="Theme.AdServices.Settings" parent="Theme.SubSettingsBase">
- <!-- These two attributes are required when using Toolbar as ActionBar. -->
- <item name="android:windowActionBar">false</item>
- <item name="android:windowNoTitle">true</item>
- </style>
-
<style name="AdServices.MainTheme" parent="android:Theme.DeviceDefault.Settings">
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:navigationBarDividerColor">@null</item>
diff --git a/adservices/apk/tests/Android.bp b/adservices/apk/tests/Android.bp
index 87d856b5d..ea3ca3649 100644
--- a/adservices/apk/tests/Android.bp
+++ b/adservices/apk/tests/Android.bp
@@ -41,6 +41,7 @@ android_test {
"SettingsLibSettingsTheme",
"SettingsLibCollapsingToolbarBaseActivity",
"SettingsLibMainSwitchPreference",
+ "androidx.test.uiautomator_uiautomator",
],
libs: [
"android.test.base",
diff --git a/adservices/apk/tests/AndroidManifest.xml b/adservices/apk/tests/AndroidManifest.xml
index 762070c5f..a4b13001e 100644
--- a/adservices/apk/tests/AndroidManifest.xml
+++ b/adservices/apk/tests/AndroidManifest.xml
@@ -24,14 +24,58 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application android:debuggable="true">
- <!-- Activity for Adservices Settings UI -->
+ <!-- Activity for the main view of Adservices Settings UI-->
<activity
- android:name="com.android.adservices.ui.settings.AdServicesSettingsActivityWrapper"
- android:exported="true"
- android:theme="@style/Theme.AdServices.Settings">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ android:name="com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.test.adservices.ui.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for the topics view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.TopicsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.test.adservices.ui.TOPICS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for the blocked topics view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.BlockedTopicsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.test.adservices.ui.BLOCKED_TOPICS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for the apps view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.AppsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.test.adservices.ui.APPS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity for the blocked apps view of Adservices Settings UI-->
+ <activity
+ android:name="com.android.adservices.ui.settings.activities.BlockedAppsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SubSettingsBase">
+ <intent-filter android:priority="1">
+ <action android:name="android.test.adservices.ui.BLOCKED_APPS" />
+ <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
diff --git a/adservices/apk/tests/src/com/android/adservices/ui/notifications/ConsentNotificationTriggerTest.java b/adservices/apk/tests/src/com/android/adservices/ui/notifications/ConsentNotificationTriggerTest.java
index 31bba31bc..cc9d5ab74 100644
--- a/adservices/apk/tests/src/com/android/adservices/ui/notifications/ConsentNotificationTriggerTest.java
+++ b/adservices/apk/tests/src/com/android/adservices/ui/notifications/ConsentNotificationTriggerTest.java
@@ -27,13 +27,20 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
-import android.content.pm.PackageManager;
import androidx.core.app.NotificationManagerCompat;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
import com.android.adservices.api.R;
+import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.consent.ConsentManager;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -48,6 +55,8 @@ import org.mockito.quality.Strictness;
@RunWith(AndroidJUnit4.class)
public class ConsentNotificationTriggerTest {
private static final String NOTIFICATION_CHANNEL_ID = "PRIVACY_SANDBOX_CHANNEL";
+ private static final int LAUNCH_TIMEOUT = 5000;
+ private static UiDevice sDevice;
@Mock private NotificationManagerCompat mNotificationManagerCompat;
@Mock private ConsentManager mConsentManager;
@@ -59,17 +68,21 @@ public class ConsentNotificationTriggerTest {
@Before
public void setUp() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ // Initialize UiDevice instance
+ sDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}
@Test
- public void testEuNotification() throws InterruptedException {
+ public void testEuNotification() throws InterruptedException, UiObjectNotFoundException {
MockitoAnnotations.initMocks(this);
mStaticMockSession =
ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.WARN)
.initMocks(this)
.startMocking();
try {
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
final String expectedTitle =
mContext.getString(R.string.notificationUI_notification_title_eu);
@@ -87,14 +100,32 @@ public class ConsentNotificationTriggerTest {
.isEqualTo(expectedTitle);
assertThat(notification.extras.getCharSequence(Notification.EXTRA_TEXT))
.isEqualTo(expectedContent);
- Thread.sleep(5000); // wait 5s to make sure that Notification disappears.
+
+ sDevice.openNotification();
+ sDevice.wait(Until.hasObject(By.pkg("com.android.systemui")), LAUNCH_TIMEOUT);
+ UiObject scroller =
+ sDevice.findObject(
+ new UiSelector()
+ .packageName("com.android.systemui")
+ .resourceId(
+ "com.android.systemui:id/notification_stack_scroller"));
+ assertThat(scroller.exists()).isTrue();
+ UiSelector notificationCardSelector =
+ new UiSelector().text(getString(R.string.notificationUI_notification_title_eu));
+ UiObject notificationCard = scroller.getChild(notificationCardSelector);
+ assertThat(notificationCard.exists()).isTrue();
+
+ notificationCard.click();
+ Thread.sleep(LAUNCH_TIMEOUT);
+ UiObject title = getElement(R.string.notificationUI_header_title_eu);
+ assertThat(title.exists()).isTrue();
} finally {
mStaticMockSession.finishMocking();
}
}
@Test
- public void testNonEuNotifications() throws InterruptedException {
+ public void testNonEuNotifications() throws InterruptedException, UiObjectNotFoundException {
MockitoAnnotations.initMocks(this);
mStaticMockSession =
ExtendedMockito.mockitoSession()
@@ -114,7 +145,7 @@ public class ConsentNotificationTriggerTest {
Thread.sleep(1000); // wait 1s to make sure that Notification is displayed.
verify(mConsentManager).enable(any(Context.class));
- verify(mConsentManager).recordNotificationDisplayed(any(PackageManager.class));
+ verify(mConsentManager).recordNotificationDisplayed();
verifyNoMoreInteractions(mConsentManager);
assertThat(mNotificationManager.getActiveNotifications()).hasLength(1);
final Notification notification =
@@ -124,7 +155,27 @@ public class ConsentNotificationTriggerTest {
.isEqualTo(expectedTitle);
assertThat(notification.extras.getCharSequence(Notification.EXTRA_TEXT))
.isEqualTo(expectedContent);
- Thread.sleep(5000); // wait 5s to make sure that Notification disappears.
+
+ sDevice.openNotification();
+ sDevice.wait(Until.hasObject(By.pkg("com.android.systemui")), LAUNCH_TIMEOUT);
+
+ UiObject scroller =
+ sDevice.findObject(
+ new UiSelector()
+ .packageName("com.android.systemui")
+ .resourceId(
+ "com.android.systemui:id/notification_stack_scroller"));
+ assertThat(scroller.exists()).isTrue();
+ UiObject notificationCard =
+ scroller.getChild(
+ new UiSelector()
+ .text(getString(R.string.notificationUI_notification_title)));
+ assertThat(notificationCard.exists()).isTrue();
+
+ notificationCard.click();
+ Thread.sleep(LAUNCH_TIMEOUT);
+ UiObject title = getElement(R.string.notificationUI_header_title);
+ assertThat(title.exists()).isTrue();
} finally {
mStaticMockSession.finishMocking();
}
@@ -148,10 +199,18 @@ public class ConsentNotificationTriggerTest {
ConsentNotificationTrigger.showConsentNotification(mContext, true);
- verify(mConsentManager).recordNotificationDisplayed(any(PackageManager.class));
+ verify(mConsentManager).recordNotificationDisplayed();
verifyNoMoreInteractions(mConsentManager);
} finally {
mStaticMockSession.finishMocking();
}
}
+
+ private String getString(int resourceId) {
+ return ApplicationProvider.getApplicationContext().getResources().getString(resourceId);
+ }
+
+ private UiObject getElement(int resId) {
+ return sDevice.findObject(new UiSelector().text(getString(resId)));
+ }
}
diff --git a/adservices/apk/tests/src/com/android/adservices/ui/notifications/NotificationActivityTest.java b/adservices/apk/tests/src/com/android/adservices/ui/notifications/NotificationActivityTest.java
deleted file mode 100644
index e02dab4b0..000000000
--- a/adservices/apk/tests/src/com/android/adservices/ui/notifications/NotificationActivityTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.ui.notifications;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-
-import static org.mockito.ArgumentMatchers.any;
-
-import android.content.Context;
-import android.content.Intent;
-
-import androidx.test.core.app.ActivityScenario;
-
-import com.android.adservices.api.R;
-import com.android.adservices.service.common.BackgroundJobsManager;
-import com.android.adservices.ui.settings.AdServicesSettingsActivity;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsMainFragment;
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.MockitoAnnotations;
-import org.mockito.quality.Strictness;
-
-/** Tests for {@link ConsentNotificationActivity}. */
-public class NotificationActivityTest {
- private static final String NOTIFICATION_INTENT = "android.test.adservices.ui.NOTIFICATIONS";
- private StaticMockitoSession mStaticMockSession;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mStaticMockSession =
- ExtendedMockito.mockitoSession()
- .spyStatic(BackgroundJobsManager.class)
- .strictness(Strictness.WARN)
- .initMocks(this)
- .startMocking();
- ExtendedMockito.doNothing()
- .when(() -> BackgroundJobsManager.scheduleAllBackgroundJobs(any(Context.class)));
-
- Intent mIntent = new Intent(NOTIFICATION_INTENT);
- mIntent.putExtra("isEUDevice", false);
- ActivityScenario.launch(mIntent);
- }
-
- @After
- public void teardown() {
- if (mStaticMockSession != null) {
- mStaticMockSession.finishMocking();
- }
- }
-
- /**
- * Test if {@link AdServicesSettingsMainFragment} is displayed in {@link
- * AdServicesSettingsActivity}.
- */
- @Test
- public void test_FragmentContainer_isDisplayed() {
- onView(withId(R.id.fragment_container_view)).check(matches(isDisplayed()));
- }
-
- /**
- * Test if {@link ConsentNotificationFragment} is displayed in {@link
- * ConsentNotificationActivity}.
- */
- @Test
- public void test_ConsentNotificationFragment_isDisplayed() {
- checkConsentNotificationFragmentIsDisplayed();
- }
-
- @Test
- public void test_ConsentNotificationConfirmationFragment_isDisplayed() {
- launchEUActivity();
- checkConsentNotificationFragmentIsDisplayed();
-
- onView(withId(R.id.rightControlButton)).perform(click());
-
- onView(withId(R.id.consent_notification_accept_confirmation_view))
- .check(matches(isDisplayed()));
- }
-
- private void checkConsentNotificationFragmentIsDisplayed() {
- onView(withText(R.string.notificationUI_header_title)).check(matches(isDisplayed()));
- }
-
- private void launchEUActivity() {
- Intent mIntent = new Intent(NOTIFICATION_INTENT);
- mIntent.putExtra("isEUDevice", true);
- ActivityScenario.launch(mIntent);
- }
-}
diff --git a/adservices/apk/tests/src/com/android/adservices/ui/notifications/NotificationActivityUiAutomatorTest.java b/adservices/apk/tests/src/com/android/adservices/ui/notifications/NotificationActivityUiAutomatorTest.java
new file mode 100644
index 000000000..cd072cc61
--- /dev/null
+++ b/adservices/apk/tests/src/com/android/adservices/ui/notifications/NotificationActivityUiAutomatorTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.ui.notifications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
+
+import com.android.adservices.api.R;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.PhFlags;
+import com.android.adservices.service.common.BackgroundJobsManager;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationActivityUiAutomatorTest {
+ private static final String NOTIFICATION_TEST_PACKAGE =
+ "android.test.adservices.ui.NOTIFICATIONS";
+ private static final int LAUNCH_TIMEOUT = 5000;
+ private static Context sContext;
+ private static UiDevice sDevice;
+ private static Intent sIntent;
+ private MockitoSession mStaticMockSession;
+ private PhFlags mPhFlags;
+
+ @Before
+ public void setup() throws UiObjectNotFoundException, IOException {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PhFlags.class)
+ .spyStatic(BackgroundJobsManager.class)
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+ ExtendedMockito.doNothing()
+ .when(() -> BackgroundJobsManager.scheduleAllBackgroundJobs(any(Context.class)));
+ mPhFlags = spy(PhFlags.getInstance());
+ doReturn(true).when(mPhFlags).getUIDialogsFeatureEnabled();
+ ExtendedMockito.doReturn(mPhFlags).when(PhFlags::getInstance);
+
+ // Initialize UiDevice instance
+ sDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ // Start from the home screen
+ sDevice.pressHome();
+
+ // Wait for launcher
+ final String launcherPackage = sDevice.getLauncherPackageName();
+ assertThat(launcherPackage).isNotNull();
+ sDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);
+
+ // Create intent
+ sContext = ApplicationProvider.getApplicationContext();
+ sIntent = new Intent(NOTIFICATION_TEST_PACKAGE);
+ sIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ @After
+ public void teardown() {
+ if (mStaticMockSession != null) {
+ mStaticMockSession.finishMocking();
+ }
+ }
+
+ @Test
+ public void moreButtonTest() throws UiObjectNotFoundException, InterruptedException {
+ startActivity(true);
+ UiObject leftControlButton =
+ getElement(R.string.notificationUI_left_control_button_text_eu);
+ UiObject rightControlButton =
+ getElement(R.string.notificationUI_right_control_button_text_eu);
+ UiObject moreButton = getElement(R.string.notificationUI_more_button_text);
+ assertThat(leftControlButton.exists()).isFalse();
+ assertThat(rightControlButton.exists()).isFalse();
+ assertThat(moreButton.exists()).isTrue();
+
+ while (moreButton.exists()) {
+ moreButton.click();
+ Thread.sleep(2000);
+ }
+ assertThat(leftControlButton.exists()).isTrue();
+ assertThat(rightControlButton.exists()).isTrue();
+ assertThat(moreButton.exists()).isFalse();
+ }
+
+ @Test
+ public void acceptedConfirmationScreenTest()
+ throws UiObjectNotFoundException, InterruptedException {
+ startActivity(true);
+ UiObject leftControlButton =
+ getElement(R.string.notificationUI_left_control_button_text_eu);
+ UiObject rightControlButton =
+ getElement(R.string.notificationUI_right_control_button_text_eu);
+ UiObject moreButton = getElement(R.string.notificationUI_more_button_text);
+ assertThat(leftControlButton.exists()).isFalse();
+ assertThat(rightControlButton.exists()).isFalse();
+ assertThat(moreButton.exists()).isTrue();
+
+ while (moreButton.exists()) {
+ moreButton.click();
+ Thread.sleep(2000);
+ }
+ assertThat(leftControlButton.exists()).isTrue();
+ assertThat(rightControlButton.exists()).isTrue();
+ assertThat(moreButton.exists()).isFalse();
+
+ rightControlButton.click();
+ UiObject acceptedTitle = getElement(R.string.notificationUI_confirmation_accept_title);
+ assertThat(acceptedTitle.exists()).isTrue();
+ }
+
+ private void startActivity(boolean isEUActivity) {
+ // Send intent
+ sIntent.putExtra("isEUDevice", isEUActivity);
+ sContext.startActivity(sIntent);
+
+ // Wait for the app to appear
+ sDevice.wait(Until.hasObject(By.pkg(NOTIFICATION_TEST_PACKAGE).depth(0)), LAUNCH_TIMEOUT);
+ }
+
+ private String getString(int resourceId) {
+ return ApplicationProvider.getApplicationContext().getResources().getString(resourceId);
+ }
+
+ private UiObject getElement(int resId) {
+ return sDevice.findObject(new UiSelector().text(getString(resId)));
+ }
+}
diff --git a/adservices/apk/tests/src/com/android/adservices/ui/settings/AdServicesSettingsActivityWrapper.java b/adservices/apk/tests/src/com/android/adservices/ui/settings/AdServicesSettingsActivityWrapper.java
deleted file mode 100644
index c58e51e53..000000000
--- a/adservices/apk/tests/src/com/android/adservices/ui/settings/AdServicesSettingsActivityWrapper.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.ui.settings;
-
-import com.android.adservices.service.PhFlags;
-import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
-
-
-/**
- * Wrapper class for {@link AdServicesSettingsActivity} used for testing purposes only. Instantiates
- * a {@link AdServicesSettingsActivity} with the custom constructor that can be passed a a mocked
- * view model provider. This is needed because the some view models (such as the {@link
- * TopicsViewModel}) will ultimately need to call {@link PhFlags} to read system settings, which
- * requires the READ_DEVICE_CONFIG permission that is only granted to the real PP API process and
- * not the process used for the test application.
- */
-public class AdServicesSettingsActivityWrapper extends AdServicesSettingsActivity {
- public AdServicesSettingsActivityWrapper() {
- super(new SettingsActivityTest().generateMockedViewModelProvider());
- }
-}
diff --git a/adservices/apk/tests/src/com/android/adservices/ui/settings/SettingsActivityTest.java b/adservices/apk/tests/src/com/android/adservices/ui/settings/SettingsActivityTest.java
deleted file mode 100644
index de1309576..000000000
--- a/adservices/apk/tests/src/com/android/adservices/ui/settings/SettingsActivityTest.java
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * 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.ui.settings;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.Espresso.pressBack;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
-import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
-import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.View;
-import android.widget.HorizontalScrollView;
-import android.widget.ListView;
-import android.widget.ScrollView;
-import android.widget.Switch;
-
-import androidx.core.widget.NestedScrollView;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.espresso.PerformException;
-import androidx.test.espresso.UiController;
-import androidx.test.espresso.ViewAction;
-import androidx.test.espresso.action.ViewActions;
-import androidx.test.espresso.matcher.ViewMatchers;
-import androidx.test.espresso.util.HumanReadables;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-
-import com.android.adservices.api.R;
-import com.android.adservices.data.topics.Topic;
-import com.android.adservices.service.common.BackgroundJobsManager;
-import com.android.adservices.service.consent.App;
-import com.android.adservices.service.consent.ConsentManager;
-import com.android.adservices.ui.settings.fragments.AdServicesSettingsMainFragment;
-import com.android.adservices.ui.settings.viewmodels.AppsViewModel;
-import com.android.adservices.ui.settings.viewmodels.MainViewModel;
-import com.android.adservices.ui.settings.viewmodels.TopicsViewModel;
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-
-import com.google.common.collect.ImmutableList;
-
-import junit.framework.AssertionFailedError;
-
-import org.hamcrest.Matcher;
-import org.hamcrest.Matchers;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/** Tests for {@link AdServicesSettingsActivity}. */
-public class SettingsActivityTest {
- static ViewModelProvider sViewModelProvider = Mockito.mock(ViewModelProvider.class);
- static ConsentManager sConsentManager;
- private MockitoSession mStaticMockSession;
-
- private static final class NestedScrollToAction implements ViewAction {
- private static final String TAG =
- androidx.test.espresso.action.ScrollToAction.class.getSimpleName();
-
- @Override
- public Matcher<View> getConstraints() {
- return Matchers.allOf(
- ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
- ViewMatchers.isDescendantOfA(
- Matchers.anyOf(
- ViewMatchers.isAssignableFrom(NestedScrollView.class),
- ViewMatchers.isAssignableFrom(ScrollView.class),
- ViewMatchers.isAssignableFrom(HorizontalScrollView.class),
- ViewMatchers.isAssignableFrom(ListView.class))));
- }
-
- @Override
- public void perform(UiController uiController, View view) {
- if (isDisplayingAtLeast(90).matches(view)) {
- Log.i(TAG, "View is already displayed. Returning.");
- return;
- }
- Rect rect = new Rect();
- view.getDrawingRect(rect);
- if (!view.requestRectangleOnScreen(rect, true /* immediate */)) {
- Log.w(TAG, "Scrolling to view was requested, but none of the parents scrolled.");
- }
- uiController.loopMainThreadUntilIdle();
- if (!isDisplayingAtLeast(90).matches(view)) {
- throw new PerformException.Builder()
- .withActionDescription(this.getDescription())
- .withViewDescription(HumanReadables.describe(view))
- .withCause(
- new RuntimeException(
- "Scrolling to view was attempted, but the view is not "
- + "displayed"))
- .build();
- }
- }
-
- @Override
- public String getDescription() {
- return "scroll to";
- }
- }
-
- private static ViewAction nestedScrollTo() {
- return ViewActions.actionWithAssertions(new NestedScrollToAction());
- }
-
- /**
- * {@link ActivityScenarioRule} is a JUnit {@link Rule @Rule} to launch your activity under
- * test.
- *
- * <p>Rules are interceptors which are executed for each test method and are important building
- * blocks of Junit tests.
- */
- @Rule
- public ActivityScenarioRule mRule =
- new ActivityScenarioRule<>(AdServicesSettingsActivityWrapper.class);
-
- /**
- * This is used by {@link AdServicesSettingsActivityWrapper}. Provides a mocked {@link
- * ViewModelProvider} that serves mocked view models, which use a mocked {@link ConsentManager},
- * which gives mocked data.
- *
- * @return the mocked {@link ViewModelProvider}
- */
- public ViewModelProvider generateMockedViewModelProvider() {
- sConsentManager =
- spy(ConsentManager.getInstance(ApplicationProvider.getApplicationContext()));
- List<Topic> tempList = new ArrayList<>();
- tempList.add(Topic.create(10001, 1, 1));
- tempList.add(Topic.create(10002, 1, 1));
- tempList.add(Topic.create(10003, 1, 1));
- ImmutableList<Topic> topicsList = ImmutableList.copyOf(tempList);
- doReturn(topicsList).when(sConsentManager).getKnownTopicsWithConsent();
-
- tempList = new ArrayList<>();
- tempList.add(Topic.create(10004, 1, 1));
- tempList.add(Topic.create(10005, 1, 1));
- ImmutableList<Topic> blockedTopicsList = ImmutableList.copyOf(tempList);
- doReturn(blockedTopicsList).when(sConsentManager).getTopicsWithRevokedConsent();
-
- List<App> appTempList = new ArrayList<>();
- appTempList.add(App.create("app1"));
- appTempList.add(App.create("app2"));
- ImmutableList<App> appsList = ImmutableList.copyOf(appTempList);
- doReturn(appsList).when(sConsentManager).getKnownAppsWithConsent();
-
- appTempList = new ArrayList<>();
- appTempList.add(App.create("app3"));
- ImmutableList<App> blockedAppsList = ImmutableList.copyOf(appTempList);
- doReturn(blockedAppsList).when(sConsentManager).getAppsWithRevokedConsent();
-
- doNothing().when(sConsentManager).resetTopicsAndBlockedTopics();
- try {
- doNothing().when(sConsentManager).resetAppsAndBlockedApps();
- } catch (IOException e) {
- e.printStackTrace();
- }
- doNothing().when(sConsentManager).resetMeasurement();
-
- TopicsViewModel topicsViewModel =
- new TopicsViewModel(ApplicationProvider.getApplicationContext(), sConsentManager);
- AppsViewModel appsViewModel =
- new AppsViewModel(ApplicationProvider.getApplicationContext(), sConsentManager);
- MainViewModel mainViewModel =
- new MainViewModel(ApplicationProvider.getApplicationContext(), sConsentManager);
- doReturn(topicsViewModel).when(sViewModelProvider).get(TopicsViewModel.class);
- doReturn(mainViewModel).when(sViewModelProvider).get(MainViewModel.class);
- doReturn(appsViewModel).when(sViewModelProvider).get(AppsViewModel.class);
- return sViewModelProvider;
- }
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mStaticMockSession =
- ExtendedMockito.mockitoSession()
- .spyStatic(BackgroundJobsManager.class)
- .strictness(Strictness.WARN)
- .initMocks(this)
- .startMocking();
- ExtendedMockito.doNothing()
- .when(() -> BackgroundJobsManager.scheduleAllBackgroundJobs(any(Context.class)));
- }
-
- @After
- public void teardown() {
- if (mStaticMockSession != null) {
- mStaticMockSession.finishMocking();
- }
- }
-
- /**
- * Test if {@link AdServicesSettingsMainFragment} is displayed in {@link
- * AdServicesSettingsActivity}.
- */
- @Test
- public void test_FragmentContainer_isDisplayed() {
- giveConsentIfNeeded();
- onView(withId(R.id.fragment_container_view)).check(matches(isDisplayed()));
- }
-
- /**
- * Test if the strings (settingsUI_topics_title, settingsUI_apps_title,
- * settingsUI_main_view_title) are displayed in {@link AdServicesSettingsMainFragment}.
- */
- @Test
- public void test_MainFragmentView_isDisplayed() {
- giveConsentIfNeeded();
- onView(withText(R.string.settingsUI_privacy_sandbox_beta_switch_title))
- .perform(nestedScrollTo())
- .check(matches(isDisplayed()));
- onView(withText(R.string.settingsUI_topics_title))
- .perform(nestedScrollTo())
- .check(matches(isDisplayed()));
- onView(withText(R.string.settingsUI_apps_title))
- .perform(nestedScrollTo())
- .check(matches(isDisplayed()));
- }
-
- /** Test if {@link MainViewModel} works if Activity is recreated (simulates rotate phone). */
- @Test
- public void test_MainViewModel_getConsent() {
- giveConsentIfNeeded();
- onView(withId(R.id.main_fragment))
- .check(
- matches(
- hasDescendant(
- Matchers.allOf(
- withClassName(Matchers.is(Switch.class.getName())),
- isChecked()))));
- onView(withText(R.string.settingsUI_privacy_sandbox_beta_switch_title))
- .perform(nestedScrollTo(), click());
-
- mRule.getScenario().recreate();
- onView(withId(R.id.main_fragment))
- .check(
- matches(
- hasDescendant(
- Matchers.allOf(
- withClassName(Matchers.is(Switch.class.getName())),
- Matchers.not(isChecked())))));
-
- // Give consent back
- onView(withText(R.string.settingsUI_privacy_sandbox_beta_switch_title))
- .perform(nestedScrollTo(), click());
- onView(withId(R.id.main_fragment))
- .check(
- matches(
- hasDescendant(
- Matchers.allOf(
- withClassName(Matchers.is(Switch.class.getName())),
- isChecked()))));
- }
-
- /**
- * Test if the Topics button in the main fragment opens the topics fragment, and the back button
- * returns to the main fragment.
- */
- @Test
- public void test_TopicsView() {
- giveConsentIfNeeded();
-
- assertMainFragmentDisplayed();
-
- onView(withText(R.string.settingsUI_topics_title)).perform(nestedScrollTo(), click());
-
- assertTopicsFragmentDisplayed();
-
- pressBack();
-
- assertMainFragmentDisplayed();
- }
-
- /**
- * Test if the Topics button in the main fragment opens the topics fragment, and the back button
- * returns to the main fragment.
- */
- @Test
- public void test_BlockedTopicsView() {
- giveConsentIfNeeded();
-
- assertMainFragmentDisplayed();
-
- onView(withText(R.string.settingsUI_topics_title)).perform(nestedScrollTo(), click());
-
- assertTopicsFragmentDisplayed();
-
- onView(withId(R.id.blocked_topics_button)).perform(nestedScrollTo(), click());
-
- assertBlockedTopicsFragmentDisplayed();
-
- pressBack();
-
- assertTopicsFragmentDisplayed();
-
- pressBack();
-
- assertMainFragmentDisplayed();
- }
-
- /**
- * Test if the Topics button in the main fragment opens the topics fragment, and the back button
- * returns to the main fragment.
- */
- @Test
- public void test_AppsView() {
- giveConsentIfNeeded();
-
- assertMainFragmentDisplayed();
-
- onView(withText(R.string.settingsUI_apps_title)).perform(nestedScrollTo(), click());
-
- assertAppsFragmentDisplayed();
-
- pressBack();
-
- assertMainFragmentDisplayed();
- }
-
- /**
- * Test if the Topics button in the main fragment opens the topics fragment, and the back button
- * returns to the main fragment.
- */
- @Test
- public void test_BlockedAppsView() {
- giveConsentIfNeeded();
-
- assertMainFragmentDisplayed();
-
- onView(withText(R.string.settingsUI_apps_title)).perform(nestedScrollTo(), click());
-
- assertAppsFragmentDisplayed();
-
- onView(withId(R.id.blocked_apps_button)).perform(nestedScrollTo(), click());
-
- assertBlockedAppsFragmentDisplayed();
-
- pressBack();
-
- assertAppsFragmentDisplayed();
-
- pressBack();
-
- assertMainFragmentDisplayed();
- }
-
- private void assertMainFragmentDisplayed() {
- onView(withText(R.string.settingsUI_main_view_subtitle))
- .perform(nestedScrollTo())
- .check(matches(isDisplayed()));
- }
-
- private void assertTopicsFragmentDisplayed() {
- onView(withText(R.string.settingsUI_topics_view_subtitle))
- .perform(nestedScrollTo())
- .check(matches(isDisplayed()));
- }
-
- private void assertAppsFragmentDisplayed() {
- onView(withText(R.string.settingsUI_apps_view_subtitle))
- .perform(nestedScrollTo())
- .check(matches(isDisplayed()));
- }
-
- private void assertBlockedTopicsFragmentDisplayed() {
- onView(withId(R.id.blocked_topics_list)).check(matches(isDisplayed()));
- }
-
- private void assertBlockedAppsFragmentDisplayed() {
- onView(withId(R.id.blocked_apps_list)).check(matches(isDisplayed()));
- }
-
- private void giveConsentIfNeeded() {
- try {
- onView(withId(R.id.main_fragment))
- .check(
- matches(
- hasDescendant(
- Matchers.allOf(
- withClassName(
- Matchers.is(Switch.class.getName())),
- isChecked()))));
- } catch (AssertionFailedError e) {
- // Give consent
- onView(withText(R.string.settingsUI_privacy_sandbox_beta_switch_title))
- .perform(nestedScrollTo(), click());
- }
- }
-}
diff --git a/adservices/apk/tests/src/com/android/adservices/ui/settings/SettingsActivityUiAutomatorTest.java b/adservices/apk/tests/src/com/android/adservices/ui/settings/SettingsActivityUiAutomatorTest.java
new file mode 100644
index 000000000..65184b5eb
--- /dev/null
+++ b/adservices/apk/tests/src/com/android/adservices/ui/settings/SettingsActivityUiAutomatorTest.java
@@ -0,0 +1,471 @@
+/*
+ * 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.ui.settings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.ViewModelProvider;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
+
+import com.android.adservices.api.R;
+import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.PhFlags;
+import com.android.adservices.service.common.BackgroundJobsManager;
+import com.android.adservices.service.consent.App;
+import com.android.adservices.service.consent.ConsentManager;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SettingsActivityUiAutomatorTest {
+ private static final String PRIVACY_SANDBOX_TEST_PACKAGE = "android.test.adservices.ui.MAIN";
+ private static final int LAUNCH_TIMEOUT = 5000;
+ private static UiDevice sDevice;
+ static ViewModelProvider sViewModelProvider = Mockito.mock(ViewModelProvider.class);
+ static ConsentManager sConsentManager;
+ private MockitoSession mStaticMockSession;
+ private PhFlags mPhFlags;
+ private ConsentManager mConsentManager;
+
+ @Before
+ public void setup() throws UiObjectNotFoundException, IOException {
+ // Static mocking
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PhFlags.class)
+ .spyStatic(FlagsFactory.class)
+ .spyStatic(BackgroundJobsManager.class)
+ .spyStatic(ConsentManager.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+
+ // prepare objects used by static mocking
+ mConsentManager =
+ spy(ConsentManager.getInstance(ApplicationProvider.getApplicationContext()));
+ List<Topic> tempList = new ArrayList<>();
+ tempList.add(Topic.create(10001, 1, 1));
+ tempList.add(Topic.create(10002, 1, 1));
+ tempList.add(Topic.create(10003, 1, 1));
+ ImmutableList<Topic> topicsList = ImmutableList.copyOf(tempList);
+ doReturn(topicsList).when(mConsentManager).getKnownTopicsWithConsent();
+
+ tempList = new ArrayList<>();
+ tempList.add(Topic.create(10004, 1, 1));
+ tempList.add(Topic.create(10005, 1, 1));
+ ImmutableList<Topic> blockedTopicsList = ImmutableList.copyOf(tempList);
+ doReturn(blockedTopicsList).when(mConsentManager).getTopicsWithRevokedConsent();
+
+ List<App> appTempList = new ArrayList<>();
+ appTempList.add(App.create("app1"));
+ appTempList.add(App.create("app2"));
+ ImmutableList<App> appsList = ImmutableList.copyOf(appTempList);
+ doReturn(appsList).when(mConsentManager).getKnownAppsWithConsent();
+
+ appTempList = new ArrayList<>();
+ appTempList.add(App.create("app3"));
+ ImmutableList<App> blockedAppsList = ImmutableList.copyOf(appTempList);
+ doReturn(blockedAppsList).when(mConsentManager).getAppsWithRevokedConsent();
+
+ doNothing().when(mConsentManager).resetTopicsAndBlockedTopics();
+ doNothing().when(mConsentManager).resetTopics();
+ doNothing().when(mConsentManager).revokeConsentForTopic(any(Topic.class));
+ doNothing().when(mConsentManager).restoreConsentForTopic(any(Topic.class));
+ try {
+ doNothing().when(mConsentManager).resetAppsAndBlockedApps();
+ doNothing().when(mConsentManager).resetApps();
+ doNothing().when(mConsentManager).revokeConsentForApp(any(App.class));
+ doNothing().when(mConsentManager).restoreConsentForApp(any(App.class));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ doNothing().when(mConsentManager).resetMeasurement();
+
+ ExtendedMockito.doNothing()
+ .when(() -> BackgroundJobsManager.scheduleAllBackgroundJobs(any(Context.class)));
+ mPhFlags = spy(PhFlags.getInstance());
+ doReturn(true).when(mPhFlags).getUIDialogsFeatureEnabled();
+ ExtendedMockito.doReturn(mPhFlags).when(PhFlags::getInstance);
+ ExtendedMockito.doReturn(mConsentManager)
+ .when(() -> ConsentManager.getInstance(any(Context.class)));
+
+ // Initialize UiDevice instance
+ sDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ // Start from the home screen
+ sDevice.pressHome();
+
+ // Wait for launcher
+ final String launcherPackage = sDevice.getLauncherPackageName();
+ assertThat(launcherPackage).isNotNull();
+ sDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);
+
+ // launch app
+ Context context = ApplicationProvider.getApplicationContext();
+ Intent intent = new Intent(PRIVACY_SANDBOX_TEST_PACKAGE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+
+ // Wait for the app to appear
+ sDevice.wait(
+ Until.hasObject(By.pkg(PRIVACY_SANDBOX_TEST_PACKAGE).depth(0)), LAUNCH_TIMEOUT);
+
+ // set consent to true if not
+ UiObject mainSwitch =
+ sDevice.findObject(new UiSelector().className("android.widget.Switch"));
+ assertThat(mainSwitch.exists()).isTrue();
+ if (!mainSwitch.isChecked()) mainSwitch.click();
+ assertThat(mainSwitch.isChecked()).isTrue();
+ }
+
+ @After
+ public void teardown() {
+ if (mStaticMockSession != null) {
+ mStaticMockSession.finishMocking();
+ }
+ }
+
+ private void scrollToAndClick(int resId) throws UiObjectNotFoundException {
+ UiScrollable scrollView =
+ new UiScrollable(
+ new UiSelector().scrollable(true).className("android.widget.ScrollView"));
+ UiObject element =
+ sDevice.findObject(
+ new UiSelector().childSelector(new UiSelector().text(getString(resId))));
+ scrollView.scrollIntoView(element);
+ element.click();
+ }
+
+ private UiObject getElement(int resId) {
+ return sDevice.findObject(new UiSelector().text(getString(resId)));
+ }
+
+ private UiObject getElement(int resId, int index) {
+ return sDevice.findObject(new UiSelector().text(getString(resId)).instance(index));
+ }
+
+ private String getString(int resourceId) {
+ return ApplicationProvider.getApplicationContext().getResources().getString(resourceId);
+ }
+
+ @Test
+ public void optOutDialogTest() throws UiObjectNotFoundException {
+ UiObject mainSwitch =
+ sDevice.findObject(new UiSelector().className("android.widget.Switch"));
+ assertThat(mainSwitch.exists()).isTrue();
+
+ // click switch
+ mainSwitch.click();
+ UiObject dialogTitle = getElement(R.string.settingsUI_dialog_opt_out_title);
+ UiObject positiveText = getElement(R.string.settingsUI_dialog_opt_out_positive_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(positiveText.exists()).isTrue();
+
+ // confirm
+ positiveText.click();
+ assertThat(mainSwitch.isChecked()).isFalse();
+
+ // reset to opted in
+ mainSwitch.click();
+ assertThat(mainSwitch.isChecked()).isTrue();
+
+ // click switch
+ mainSwitch.click();
+ dialogTitle = getElement(R.string.settingsUI_dialog_opt_out_title);
+ UiObject negativeText = getElement(R.string.settingsUI_dialog_negative_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(negativeText.exists()).isTrue();
+
+ // cancel
+ negativeText.click();
+ assertThat(mainSwitch.isChecked()).isTrue();
+ }
+
+ @Test
+ public void blockTopicDialogTest() throws UiObjectNotFoundException {
+ // open topics view
+ scrollToAndClick(R.string.settingsUI_topics_title);
+ UiObject blockTopicText = getElement(R.string.settingsUI_block_topic_title, 0);
+ assertThat(blockTopicText.exists()).isTrue();
+
+ // click block
+ blockTopicText.click();
+ UiObject dialogTitle = getElement(R.string.settingsUI_dialog_block_topic_message);
+ UiObject positiveText = getElement(R.string.settingsUI_dialog_block_topic_positive_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(positiveText.exists()).isTrue();
+
+ // confirm
+ positiveText.click();
+ verify(mConsentManager).revokeConsentForTopic(any(Topic.class));
+ blockTopicText = getElement(R.string.settingsUI_block_topic_title, 0);
+ assertThat(blockTopicText.exists()).isTrue();
+
+ // click block again
+ blockTopicText.click();
+ dialogTitle = getElement(R.string.settingsUI_dialog_block_topic_message);
+ UiObject negativeText = getElement(R.string.settingsUI_dialog_negative_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(negativeText.exists()).isTrue();
+
+ // cancel and verify it has still only been called once
+ negativeText.click();
+ verify(mConsentManager).revokeConsentForTopic(any(Topic.class));
+ }
+
+ @Test
+ public void unblockTopicDialogTest() throws UiObjectNotFoundException {
+ // open topics view
+ scrollToAndClick(R.string.settingsUI_topics_title);
+
+ // open blocked topics view
+ scrollToAndClick(R.string.settingsUI_blocked_topics_title);
+ UiObject unblockTopicText = getElement(R.string.settingsUI_unblock_topic_title, 0);
+ assertThat(unblockTopicText.exists()).isTrue();
+
+ // click unblock
+ unblockTopicText.click();
+ UiObject dialogTitle = getElement(R.string.settingsUI_dialog_unblock_topic_message);
+ UiObject positiveText = getElement(R.string.settingsUI_dialog_unblock_topic_positive_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(positiveText.exists()).isTrue();
+
+ // confirm
+ positiveText.click();
+ verify(mConsentManager).restoreConsentForTopic(any(Topic.class));
+ unblockTopicText = getElement(R.string.settingsUI_unblock_topic_title, 0);
+ assertThat(unblockTopicText.exists()).isTrue();
+ }
+
+ @Test
+ public void resetTopicDialogTest() throws UiObjectNotFoundException {
+ // open topics view
+ scrollToAndClick(R.string.settingsUI_topics_title);
+
+ // click reset
+ scrollToAndClick(R.string.settingsUI_reset_topics_title);
+ UiObject dialogTitle = getElement(R.string.settingsUI_dialog_reset_topic_message);
+ UiObject positiveText = getElement(R.string.settingsUI_dialog_reset_topic_positive_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(positiveText.exists()).isTrue();
+
+ // confirm
+ positiveText.click();
+ verify(mConsentManager).resetTopics();
+
+ // click reset again
+ scrollToAndClick(R.string.settingsUI_reset_topics_title);
+ dialogTitle = getElement(R.string.settingsUI_dialog_reset_topic_message);
+ UiObject negativeText = getElement(R.string.settingsUI_dialog_negative_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(negativeText.exists()).isTrue();
+
+ // cancel and verify it has still only been called once
+ negativeText.click();
+ verify(mConsentManager).resetTopics();
+ }
+
+ @Test
+ public void blockAppDialogTest() throws UiObjectNotFoundException, IOException {
+ // open apps view
+ scrollToAndClick(R.string.settingsUI_apps_title);
+ UiObject blockAppText = getElement(R.string.settingsUI_block_app_title, 0);
+ assertThat(blockAppText.exists()).isTrue();
+
+ // click block
+ blockAppText.click();
+ UiObject dialogTitle = getElement(R.string.settingsUI_dialog_block_app_message);
+ UiObject positiveText = getElement(R.string.settingsUI_dialog_block_app_positive_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(positiveText.exists()).isTrue();
+
+ // confirm
+ positiveText.click();
+ verify(mConsentManager).revokeConsentForApp(any(App.class));
+ blockAppText = getElement(R.string.settingsUI_block_app_title, 0);
+ assertThat(blockAppText.exists()).isTrue();
+
+ // click block again
+ blockAppText.click();
+ dialogTitle = getElement(R.string.settingsUI_dialog_block_app_message);
+ UiObject negativeText = getElement(R.string.settingsUI_dialog_negative_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(negativeText.exists()).isTrue();
+
+ // cancel and verify it has still only been called once
+ negativeText.click();
+ verify(mConsentManager).revokeConsentForApp(any(App.class));
+ }
+
+ @Test
+ public void unblockAppDialogTest() throws UiObjectNotFoundException, IOException {
+ // open apps view
+ scrollToAndClick(R.string.settingsUI_apps_title);
+
+ // open blocked apps view
+ scrollToAndClick(R.string.settingsUI_blocked_apps_title);
+ UiObject unblockAppText = getElement(R.string.settingsUI_unblock_app_title, 0);
+ assertThat(unblockAppText.exists()).isTrue();
+
+ // click unblock
+ unblockAppText.click();
+ UiObject dialogTitle = getElement(R.string.settingsUI_dialog_unblock_app_message);
+ UiObject positiveText = getElement(R.string.settingsUI_dialog_unblock_app_positive_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(positiveText.exists()).isTrue();
+
+ // confirm
+ positiveText.click();
+ verify(mConsentManager).restoreConsentForApp(any(App.class));
+ unblockAppText = getElement(R.string.settingsUI_unblock_app_title, 0);
+ assertThat(unblockAppText.exists()).isTrue();
+ }
+
+ @Test
+ public void resetAppDialogTest() throws UiObjectNotFoundException, IOException {
+ // open apps view
+ scrollToAndClick(R.string.settingsUI_apps_title);
+
+ // click reset
+ scrollToAndClick(R.string.settingsUI_reset_apps_title);
+ UiObject dialogTitle = getElement(R.string.settingsUI_dialog_reset_app_message);
+ UiObject positiveText = getElement(R.string.settingsUI_dialog_reset_app_positive_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(positiveText.exists()).isTrue();
+
+ // confirm
+ positiveText.click();
+ verify(mConsentManager).resetApps();
+
+ // click reset again
+ scrollToAndClick(R.string.settingsUI_reset_apps_title);
+ dialogTitle = getElement(R.string.settingsUI_dialog_reset_app_message);
+ UiObject negativeText = getElement(R.string.settingsUI_dialog_negative_text);
+ assertThat(dialogTitle.exists()).isTrue();
+ assertThat(negativeText.exists()).isTrue();
+
+ // cancel and verify it has still only been called once
+ negativeText.click();
+ verify(mConsentManager).resetApps();
+ }
+
+ @Test
+ public void disableDialogFeatureTest() throws UiObjectNotFoundException {
+ doReturn(false).when(mPhFlags).getUIDialogsFeatureEnabled();
+ UiObject mainSwitch =
+ sDevice.findObject(new UiSelector().className("android.widget.Switch"));
+ assertThat(mainSwitch.exists()).isTrue();
+
+ // click switch
+ mainSwitch.click();
+ UiObject dialogTitle = getElement(R.string.settingsUI_dialog_opt_out_title);
+ assertThat(dialogTitle.exists()).isFalse();
+ assertThat(mainSwitch.isChecked()).isFalse();
+
+ // click switch again
+ mainSwitch.click();
+ assertThat(mainSwitch.isChecked()).isTrue();
+
+ // open topics view
+ scrollToAndClick(R.string.settingsUI_topics_title);
+ UiObject blockTopicText = getElement(R.string.settingsUI_block_topic_title, 0);
+ assertThat(blockTopicText.exists()).isTrue();
+
+ // block topic
+ blockTopicText.click();
+ dialogTitle = getElement(R.string.settingsUI_dialog_block_topic_message);
+ assertThat(dialogTitle.exists()).isFalse();
+ verify(mConsentManager).revokeConsentForTopic(any(Topic.class));
+
+ // reset topic
+ scrollToAndClick(R.string.settingsUI_reset_topics_title);
+ dialogTitle = getElement(R.string.settingsUI_dialog_reset_topic_message);
+ assertThat(dialogTitle.exists()).isFalse();
+ verify(mConsentManager).resetTopics();
+
+ // open unblock topic view
+ scrollToAndClick(R.string.settingsUI_blocked_topics_title);
+ UiObject unblockTopicText = getElement(R.string.settingsUI_unblock_topic_title, 0);
+ assertThat(unblockTopicText.exists()).isTrue();
+
+ // click unblock
+ unblockTopicText.click();
+ dialogTitle = getElement(R.string.settingsUI_dialog_unblock_topic_message);
+ assertThat(dialogTitle.exists()).isFalse();
+ verify(mConsentManager).restoreConsentForTopic(any(Topic.class));
+ }
+
+ /**
+ * Test for the Button to show blocked topics when the list of Topics is Empty The Button should
+ * be disabled if blocked topics is empty
+ *
+ * @throws UiObjectNotFoundException
+ */
+ @Test
+ public void blockedTopicsWhenEmptyStateButtonTest() throws UiObjectNotFoundException {
+ // Return an empty topics list
+ doReturn(ImmutableList.of()).when(mConsentManager).getKnownTopicsWithConsent();
+ // Return a non-empty blocked topics list
+ List<Topic> tempList = new ArrayList<>();
+ tempList.add(Topic.create(10004, 1, 1));
+ tempList.add(Topic.create(10005, 1, 1));
+ ImmutableList<Topic> blockedTopicsList = ImmutableList.copyOf(tempList);
+ doReturn(blockedTopicsList).when(mConsentManager).getTopicsWithRevokedConsent();
+ // navigate to topics page
+ scrollToAndClick(R.string.settingsUI_topics_title);
+ UiObject blockedTopicsWhenEmptyStateButton =
+ sDevice.findObject(
+ new UiSelector()
+ .className("android.widget.Button")
+ .text(getString(R.string.settingsUI_blocked_topics_title)));
+
+ assertThat(blockedTopicsWhenEmptyStateButton.isEnabled()).isTrue();
+ }
+}
diff --git a/adservices/apk/unittest/src/com/android/adservices/adselection/AdSelectionServiceTest.java b/adservices/apk/unittest/src/com/android/adservices/adselection/AdSelectionServiceTest.java
index 53fecba2f..2b9d9fcae 100644
--- a/adservices/apk/unittest/src/com/android/adservices/adselection/AdSelectionServiceTest.java
+++ b/adservices/apk/unittest/src/com/android/adservices/adselection/AdSelectionServiceTest.java
@@ -16,24 +16,36 @@
package com.android.adservices.adselection;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.any;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.IBinder;
import androidx.test.core.app.ApplicationProvider;
+import com.android.adservices.download.MddJobService;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.MaintenanceJobService;
import com.android.adservices.service.adselection.AdSelectionServiceImpl;
+import com.android.adservices.service.common.PackageChangedReceiver;
+import com.android.adservices.service.consent.AdServicesApiConsent;
+import com.android.adservices.service.consent.ConsentManager;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
/** Unit test for {@link AdSelectionService} */
@@ -43,10 +55,27 @@ public class AdSelectionServiceTest {
private final Flags mFlagsWithAdSelectionSwitchOff = new FlagsWithKillSwitchOff();
@Mock private AdSelectionServiceImpl mMockAdSelectionServiceImpl;
+ @Mock private ConsentManager mConsentManagerMock;
+ @Mock private PackageManager mPackageManagerMock;
+
+ private MockitoSession mStaticMockSession;
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .mockStatic(ConsentManager.class)
+ .spyStatic(AdSelectionServiceImpl.class)
+ .spyStatic(PackageChangedReceiver.class)
+ .mockStatic(MddJobService.class)
+ .mockStatic(MaintenanceJobService.class)
+ .initMocks(this)
+ .startMocking();
+ }
+
+ @After
+ public void teardown() {
+ mStaticMockSession.finishMocking();
}
@Test
@@ -56,25 +85,36 @@ public class AdSelectionServiceTest {
adSelectionService.onCreate();
IBinder binder = adSelectionService.onBind(getIntentForAdSelectionService());
assertNull(binder);
+
+ verify(mConsentManagerMock, never()).getConsent();
+ verify(() -> MddJobService.scheduleIfNeeded(any(), anyBoolean()), never());
}
@Test
public void testBindableAdSelectionServiceKillSwitchOff() {
- MockitoSession session =
- ExtendedMockito.mockitoSession()
- .spyStatic(AdSelectionServiceImpl.class)
- .startMocking();
-
- ExtendedMockito.doReturn(mMockAdSelectionServiceImpl)
+ doReturn(mMockAdSelectionServiceImpl)
.when(() -> AdSelectionServiceImpl.create(any(Context.class)));
-
- AdSelectionService adSelectionService =
+ doReturn(mConsentManagerMock).when(() -> ConsentManager.getInstance(any(Context.class)));
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+ ExtendedMockito.doReturn(true)
+ .when(() -> PackageChangedReceiver.enableReceiver(any(Context.class)));
+ doReturn(true).when(() -> MddJobService.scheduleIfNeeded(any(), anyBoolean()));
+ doReturn(true).when(() -> MaintenanceJobService.scheduleIfNeeded(any(), anyBoolean()));
+
+ AdSelectionService adSelectionServiceSpy =
new AdSelectionService(mFlagsWithAdSelectionSwitchOff);
- adSelectionService.onCreate();
- IBinder binder = adSelectionService.onBind(getIntentForAdSelectionService());
+
+ spyOn(adSelectionServiceSpy);
+ doReturn(mPackageManagerMock).when(adSelectionServiceSpy).getPackageManager();
+
+ adSelectionServiceSpy.onCreate();
+ IBinder binder = adSelectionServiceSpy.onBind(getIntentForAdSelectionService());
assertNotNull(binder);
- session.finishMocking();
+ verify(mConsentManagerMock).getConsent();
+ verify(() -> PackageChangedReceiver.enableReceiver(any(Context.class)));
+ verify(() -> MddJobService.scheduleIfNeeded(any(), anyBoolean()));
+ verify(() -> MaintenanceJobService.scheduleIfNeeded(any(), anyBoolean()));
}
private Intent getIntentForAdSelectionService() {
diff --git a/adservices/apk/unittest/src/com/android/adservices/customaudience/CustomAudienceServiceTest.java b/adservices/apk/unittest/src/com/android/adservices/customaudience/CustomAudienceServiceTest.java
index 4f8ced89f..10cc1fa6a 100644
--- a/adservices/apk/unittest/src/com/android/adservices/customaudience/CustomAudienceServiceTest.java
+++ b/adservices/apk/unittest/src/com/android/adservices/customaudience/CustomAudienceServiceTest.java
@@ -16,24 +16,35 @@
package com.android.adservices.customaudience;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.any;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.IBinder;
import androidx.test.core.app.ApplicationProvider;
+import com.android.adservices.download.MddJobService;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.common.PackageChangedReceiver;
+import com.android.adservices.service.consent.AdServicesApiConsent;
+import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.customaudience.CustomAudienceServiceImpl;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
public class CustomAudienceServiceTest {
@@ -42,10 +53,26 @@ public class CustomAudienceServiceTest {
private final Flags mFlagsWithCustomAudienceSwitchOff = new FlagsWithKillSwitchOff();
@Mock private CustomAudienceServiceImpl mMockCustomAudienceServiceImpl;
+ @Mock private ConsentManager mConsentManagerMock;
+ @Mock private PackageManager mPackageManagerMock;
+
+ private MockitoSession mStaticMockSession;
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .mockStatic(ConsentManager.class)
+ .spyStatic(CustomAudienceServiceImpl.class)
+ .spyStatic(PackageChangedReceiver.class)
+ .mockStatic(MddJobService.class)
+ .initMocks(this)
+ .startMocking();
+ }
+
+ @After
+ public void teardown() {
+ mStaticMockSession.finishMocking();
}
@Test
@@ -55,26 +82,34 @@ public class CustomAudienceServiceTest {
customAudienceService.onCreate();
IBinder binder = customAudienceService.onBind(getIntentForCustomAudienceService());
assertNull(binder);
+
+ verify(mConsentManagerMock, never()).getConsent();
+ verify(() -> MddJobService.scheduleIfNeeded(any(), anyBoolean()), never());
}
@Test
public void testBindableCustomAudienceServiceKillSwitchOff() {
-
- MockitoSession session =
- ExtendedMockito.mockitoSession()
- .spyStatic(CustomAudienceServiceImpl.class)
- .startMocking();
-
- ExtendedMockito.doReturn(mMockCustomAudienceServiceImpl)
+ doReturn(mMockCustomAudienceServiceImpl)
.when(() -> CustomAudienceServiceImpl.create(any(Context.class)));
+ doReturn(mConsentManagerMock).when(() -> ConsentManager.getInstance(any(Context.class)));
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+ ExtendedMockito.doReturn(true)
+ .when(() -> PackageChangedReceiver.enableReceiver(any(Context.class)));
+ doReturn(true).when(() -> MddJobService.scheduleIfNeeded(any(), anyBoolean()));
- CustomAudienceService customAudienceService =
+ CustomAudienceService customAudienceServiceSpy =
new CustomAudienceService(mFlagsWithCustomAudienceSwitchOff);
- customAudienceService.onCreate();
- IBinder binder = customAudienceService.onBind(getIntentForCustomAudienceService());
+
+ spyOn(customAudienceServiceSpy);
+ doReturn(mPackageManagerMock).when(customAudienceServiceSpy).getPackageManager();
+
+ customAudienceServiceSpy.onCreate();
+ IBinder binder = customAudienceServiceSpy.onBind(getIntentForCustomAudienceService());
assertNotNull(binder);
- session.finishMocking();
+ verify(mConsentManagerMock).getConsent();
+ verify(() -> PackageChangedReceiver.enableReceiver(any(Context.class)));
+ verify(() -> MddJobService.scheduleIfNeeded(any(), anyBoolean()));
}
private Intent getIntentForCustomAudienceService() {
diff --git a/adservices/apk/unittest/src/com/android/adservices/measurement/MeasurementServiceTest.java b/adservices/apk/unittest/src/com/android/adservices/measurement/MeasurementServiceTest.java
index 865574e6a..67a6e4e05 100644
--- a/adservices/apk/unittest/src/com/android/adservices/measurement/MeasurementServiceTest.java
+++ b/adservices/apk/unittest/src/com/android/adservices/measurement/MeasurementServiceTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.IBinder;
@@ -34,13 +35,17 @@ import android.os.IBinder;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.download.MddJobService;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.AppImportanceFilter;
+import com.android.adservices.service.common.PackageChangedReceiver;
import com.android.adservices.service.consent.AdServicesApiConsent;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.AsyncRegistrationQueueJobService;
import com.android.adservices.service.measurement.DeleteExpiredJobService;
+import com.android.adservices.service.measurement.DeleteUninstalledJobService;
import com.android.adservices.service.measurement.MeasurementImpl;
import com.android.adservices.service.measurement.attribution.AttributionJobService;
import com.android.adservices.service.measurement.reporting.AggregateFallbackReportingJobService;
@@ -97,7 +102,9 @@ public class MeasurementServiceTest {
// Verification
assertNotNull(binder);
- verify(mMockConsentManager, times(1)).getConsent(any());
+ verify(mMockConsentManager, times(1)).getConsent();
+ ExtendedMockito.verify(
+ () -> PackageChangedReceiver.enableReceiver(any(Context.class)));
assertJobScheduled(/* timesCalled */ 1);
});
}
@@ -114,7 +121,7 @@ public class MeasurementServiceTest {
// Verification
assertNotNull(binder);
- verify(mMockConsentManager, times(1)).getConsent(any());
+ verify(mMockConsentManager, times(1)).getConsent();
assertJobScheduled(/* timesCalled */ 0);
});
}
@@ -131,7 +138,7 @@ public class MeasurementServiceTest {
// Verification
assertNull(binder);
- verify(mMockConsentManager, never()).getConsent(any());
+ verify(mMockConsentManager, never()).getConsent();
assertJobScheduled(/* timesCalled */ 0);
});
}
@@ -160,10 +167,14 @@ public class MeasurementServiceTest {
.spyStatic(ConsentManager.class)
.spyStatic(EnrollmentDao.class)
.spyStatic(EventReportingJobService.class)
+ .spyStatic(PackageChangedReceiver.class)
.spyStatic(EventFallbackReportingJobService.class)
.spyStatic(DeleteExpiredJobService.class)
+ .spyStatic(DeleteUninstalledJobService.class)
+ .spyStatic(MddJobService.class)
.spyStatic(FlagsFactory.class)
.spyStatic(MeasurementImpl.class)
+ .spyStatic(AsyncRegistrationQueueJobService.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -176,7 +187,7 @@ public class MeasurementServiceTest {
final AdServicesApiConsent mockConsent = mock(AdServicesApiConsent.class);
doReturn(consentStatus).when(mockConsent).isGiven();
- doReturn(mockConsent).when(mMockConsentManager).getConsent(any());
+ doReturn(mockConsent).when(mMockConsentManager).getConsent();
ExtendedMockito.doReturn(mMockEnrollmentDao)
.when(() -> EnrollmentDao.getInstance(any()));
@@ -190,6 +201,8 @@ public class MeasurementServiceTest {
ExtendedMockito.doReturn(mMockAppImportanceFilter)
.when(() -> AppImportanceFilter.create(any(), anyInt(), any()));
+ ExtendedMockito.doReturn(true)
+ .when(() -> PackageChangedReceiver.enableReceiver(any(Context.class)));
ExtendedMockito.doNothing()
.when(() -> AggregateReportingJobService.scheduleIfNeeded(any(), anyBoolean()));
ExtendedMockito.doNothing()
@@ -209,6 +222,15 @@ public class MeasurementServiceTest {
any(), anyBoolean()));
ExtendedMockito.doNothing()
.when(() -> DeleteExpiredJobService.scheduleIfNeeded(any(), anyBoolean()));
+ ExtendedMockito.doNothing()
+ .when(() -> DeleteUninstalledJobService.scheduleIfNeeded(any(), anyBoolean()));
+ ExtendedMockito.doReturn(true)
+ .when(() -> MddJobService.scheduleIfNeeded(any(), anyBoolean()));
+ ExtendedMockito.doNothing()
+ .when(
+ () ->
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ any(), anyBoolean()));
// Execute
execute.run();
@@ -236,5 +258,13 @@ public class MeasurementServiceTest {
ExtendedMockito.verify(
() -> DeleteExpiredJobService.scheduleIfNeeded(any(), anyBoolean()),
times(timesCalled));
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.scheduleIfNeeded(any(), anyBoolean()),
+ times(timesCalled));
+ ExtendedMockito.verify(
+ () -> MddJobService.scheduleIfNeeded(any(), anyBoolean()), times(timesCalled));
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.scheduleIfNeeded(any(), anyBoolean()),
+ times(timesCalled));
}
}
diff --git a/adservices/apk/unittest/src/com/android/adservices/topics/TopicsServiceTest.java b/adservices/apk/unittest/src/com/android/adservices/topics/TopicsServiceTest.java
index 20574aac4..b43d1c98d 100644
--- a/adservices/apk/unittest/src/com/android/adservices/topics/TopicsServiceTest.java
+++ b/adservices/apk/unittest/src/com/android/adservices/topics/TopicsServiceTest.java
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.spy;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.IBinder;
import androidx.test.core.app.ApplicationProvider;
@@ -37,6 +36,7 @@ import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.MaintenanceJobService;
import com.android.adservices.service.common.AppImportanceFilter;
+import com.android.adservices.service.common.PackageChangedReceiver;
import com.android.adservices.service.consent.AdServicesApiConsent;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
@@ -63,7 +63,6 @@ public class TopicsServiceTest {
@Mock AppImportanceFilter mMockAppImportanceFilter;
@Mock Flags mMockFlags;
@Mock AdServicesApiConsent mMockAdServicesApiConsent;
- @Mock PackageManager mMockPackageManager;
@Before
public void setup() {
@@ -84,6 +83,7 @@ public class TopicsServiceTest {
.spyStatic(MddJobService.class)
.spyStatic(EnrollmentDao.class)
.spyStatic(AppImportanceFilter.class)
+ .spyStatic(PackageChangedReceiver.class)
.startMocking();
try {
@@ -97,21 +97,13 @@ public class TopicsServiceTest {
.when(() -> TopicsWorker.getInstance(any(Context.class)));
TopicsService spyTopicsService = spy(new TopicsService());
- doReturn(mMockPackageManager).when(spyTopicsService).getPackageManager();
ExtendedMockito.doReturn(mMockConsentManager)
.when(() -> ConsentManager.getInstance(any(Context.class)));
doReturn(true).when(mMockAdServicesApiConsent).isGiven();
- doReturn(mMockAdServicesApiConsent)
- .when(mMockConsentManager)
- .getConsent(mMockPackageManager);
+ doReturn(mMockAdServicesApiConsent).when(mMockConsentManager).getConsent();
- ExtendedMockito.doReturn(mMockEnrollmentDao)
- .when(() -> EnrollmentDao.getInstance(any(Context.class)));
- ExtendedMockito.doReturn(mMockAppImportanceFilter)
- .when(
- () ->
- AppImportanceFilter.create(
- any(Context.class), anyInt(), any(Supplier.class)));
+ ExtendedMockito.doReturn(true)
+ .when(() -> PackageChangedReceiver.enableReceiver(any(Context.class)));
ExtendedMockito.doReturn(true)
.when(
() ->
@@ -122,6 +114,14 @@ public class TopicsServiceTest {
ExtendedMockito.doReturn(true)
.when(() -> MddJobService.scheduleIfNeeded(any(Context.class), eq(false)));
+ ExtendedMockito.doReturn(mMockEnrollmentDao)
+ .when(() -> EnrollmentDao.getInstance(any(Context.class)));
+ ExtendedMockito.doReturn(mMockAppImportanceFilter)
+ .when(
+ () ->
+ AppImportanceFilter.create(
+ any(Context.class), anyInt(), any(Supplier.class)));
+
spyTopicsService.onCreate();
IBinder binder = spyTopicsService.onBind(getIntentForTopicsService());
assertNotNull(binder);
diff --git a/adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/MainViewModelTest.java b/adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/MainViewModelTest.java
index 3bf33879e..dd65208ca 100644
--- a/adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/MainViewModelTest.java
+++ b/adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/MainViewModelTest.java
@@ -25,7 +25,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
-import android.content.pm.PackageManager;
import androidx.test.core.app.ApplicationProvider;
@@ -48,9 +47,7 @@ public class MainViewModelTest {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any(PackageManager.class));
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
mMainViewModel =
new MainViewModel(ApplicationProvider.getApplicationContext(), mConsentManager);
}
@@ -64,9 +61,7 @@ public class MainViewModelTest {
/** Test if getConsent returns false if the {@link ConsentManager} always returns false. */
@Test
public void testGetConsentReturnsFalse() {
- doReturn(AdServicesApiConsent.REVOKED)
- .when(mConsentManager)
- .getConsent(any(PackageManager.class));
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent();
mMainViewModel =
new MainViewModel(ApplicationProvider.getApplicationContext(), mConsentManager);
diff --git a/adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/TopicsViewModelTest.java b/adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/TopicsViewModelTest.java
index 96a54e0dc..9a4c4c535 100644
--- a/adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/TopicsViewModelTest.java
+++ b/adservices/apk/unittest/src/com/android/adservices/ui/settings/viewmodels/TopicsViewModelTest.java
@@ -40,6 +40,7 @@ import java.io.IOException;
public class TopicsViewModelTest {
private TopicsViewModel mTopicsViewModel;
+ private BlockedTopicsViewModel mBlockedTopicsViewModel;
@Mock private ConsentManager mConsentManager;
/** Setup needed before every test in this class. */
@@ -48,11 +49,14 @@ public class TopicsViewModelTest {
MockitoAnnotations.initMocks(this);
mTopicsViewModel =
new TopicsViewModel(ApplicationProvider.getApplicationContext(), mConsentManager);
+ mBlockedTopicsViewModel =
+ new BlockedTopicsViewModel(
+ ApplicationProvider.getApplicationContext(), mConsentManager);
}
/** Test if getTopics returns no topics if {@link ConsentManager} returns no topics. */
@Test
- public void testGetTopicsReturnsNoTopics() throws IOException {
+ public void testGetTopicsReturnsNoTopics() {
ImmutableList<Topic> topicsList = ImmutableList.of();
doReturn(topicsList).when(mConsentManager).getKnownTopicsWithConsent();
@@ -91,7 +95,7 @@ public class TopicsViewModelTest {
@Test
public void testRestoreTopic() {
Topic topic1 = Topic.create(1, 1, 1);
- mTopicsViewModel.restoreTopicConsent(topic1);
+ mBlockedTopicsViewModel.restoreTopicConsent(topic1);
verify(mConsentManager, times(1)).restoreConsentForTopic(topic1);
}
diff --git a/adservices/clients/java/android/adservices/clients/adselection/AdSelectionClient.java b/adservices/clients/java/android/adservices/clients/adselection/AdSelectionClient.java
index 08227d91c..706dfa519 100644
--- a/adservices/clients/java/android/adservices/clients/adselection/AdSelectionClient.java
+++ b/adservices/clients/java/android/adservices/clients/adselection/AdSelectionClient.java
@@ -60,14 +60,9 @@ public class AdSelectionClient {
adSelectionConfig,
mExecutor,
new OutcomeReceiver<AdSelectionOutcome, Exception>() {
-
@Override
public void onResult(@NonNull AdSelectionOutcome result) {
- completer.set(
- new AdSelectionOutcome.Builder()
- .setAdSelectionId(result.getAdSelectionId())
- .setRenderUri(result.getRenderUri())
- .build());
+ completer.set(result);
}
@Override
diff --git a/adservices/clients/java/android/adservices/clients/topics/AdvertisingTopicsClient.java b/adservices/clients/java/android/adservices/clients/topics/AdvertisingTopicsClient.java
index 9d1906ec5..c8b6dbdc6 100644
--- a/adservices/clients/java/android/adservices/clients/topics/AdvertisingTopicsClient.java
+++ b/adservices/clients/java/android/adservices/clients/topics/AdvertisingTopicsClient.java
@@ -34,14 +34,19 @@ import java.util.concurrent.Executor;
public class AdvertisingTopicsClient {
private String mSdkName;
+ private boolean mRecordObservation;
private TopicsManager mTopicsManager;
private Context mContext;
private Executor mExecutor;
private AdvertisingTopicsClient(
- @NonNull Context context, @NonNull Executor executor, @NonNull String sdkName) {
+ @NonNull Context context,
+ @NonNull Executor executor,
+ @NonNull String sdkName,
+ boolean recordObservation) {
mContext = context;
mSdkName = sdkName;
+ mRecordObservation = recordObservation;
mExecutor = executor;
mTopicsManager = mContext.getSystemService(TopicsManager.class);
}
@@ -64,14 +69,24 @@ public class AdvertisingTopicsClient {
return mExecutor;
}
+ /** Get Record Observation. */
+ public boolean shouldRecordObservation() {
+ return mRecordObservation;
+ }
+
/** Gets the topics. */
public @NonNull ListenableFuture<GetTopicsResponse> getTopics() {
return CallbackToFutureAdapter.getFuture(
completer -> {
- GetTopicsRequest request =
- mSdkName == null
- ? GetTopicsRequest.create()
- : GetTopicsRequest.createWithAdsSdkName(mSdkName);
+ GetTopicsRequest.Builder builder = new GetTopicsRequest.Builder();
+ if (mSdkName != null) {
+ builder = builder.setAdsSdkName(mSdkName);
+ }
+ if (!mRecordObservation) {
+ builder.setShouldRecordObservation(false);
+ }
+ GetTopicsRequest request = builder.build();
+
mTopicsManager.getTopics(
request,
mExecutor,
@@ -95,6 +110,7 @@ public class AdvertisingTopicsClient {
/** Builder class. */
public static final class Builder {
private String mSdkName;
+ private boolean mRecordObservation = true;
private Context mContext;
private Executor mExecutor;
@@ -114,6 +130,18 @@ public class AdvertisingTopicsClient {
}
/**
+ * Set the Record Observation.
+ *
+ * @param recordObservation whether to record that the caller has observed the topics of the
+ * host app or not. This will be used to determine if the caller can receive the topic
+ * in the next epoch.
+ */
+ public @NonNull Builder setShouldRecordObservation(boolean recordObservation) {
+ mRecordObservation = recordObservation;
+ return this;
+ }
+
+ /**
* Sets the worker executor.
*
* <p>If an executor is not provided, the AdvertisingTopicsClient default executor will be
@@ -133,7 +161,7 @@ public class AdvertisingTopicsClient {
if (mExecutor == null) {
throw new NullPointerException("Executor is not set");
}
- return new AdvertisingTopicsClient(mContext, mExecutor, mSdkName);
+ return new AdvertisingTopicsClient(mContext, mExecutor, mSdkName, mRecordObservation);
}
}
}
diff --git a/adservices/framework/api/current.txt b/adservices/framework/api/current.txt
index 1b0d0c0f7..ff9e4ecf9 100644
--- a/adservices/framework/api/current.txt
+++ b/adservices/framework/api/current.txt
@@ -379,9 +379,15 @@ package android.adservices.measurement {
package android.adservices.topics {
public final class GetTopicsRequest {
- method @NonNull public static android.adservices.topics.GetTopicsRequest create();
- method @NonNull public static android.adservices.topics.GetTopicsRequest createWithAdsSdkName(@NonNull String);
method @NonNull public String getAdsSdkName();
+ method public boolean shouldRecordObservation();
+ }
+
+ public static final class GetTopicsRequest.Builder {
+ ctor public GetTopicsRequest.Builder();
+ method @NonNull public android.adservices.topics.GetTopicsRequest build();
+ method @NonNull public android.adservices.topics.GetTopicsRequest.Builder setAdsSdkName(@NonNull String);
+ method @NonNull public android.adservices.topics.GetTopicsRequest.Builder setShouldRecordObservation(boolean);
}
public final class GetTopicsResponse {
diff --git a/adservices/framework/java/android/adservices/AdServicesVersion.java b/adservices/framework/java/android/adservices/AdServicesVersion.java
index 51edf2f0d..947ca0b6a 100644
--- a/adservices/framework/java/android/adservices/AdServicesVersion.java
+++ b/adservices/framework/java/android/adservices/AdServicesVersion.java
@@ -19,7 +19,7 @@ package android.adservices;
import android.annotation.SuppressLint;
/**
- * Information about the current AdServices API version.
+ * This class specifies the current version of the AdServices API.
*/
public class AdServicesVersion {
diff --git a/adservices/framework/java/android/adservices/adselection/AdSelectionManager.java b/adservices/framework/java/android/adservices/adselection/AdSelectionManager.java
index d24f8ef9e..6fd5b2b73 100644
--- a/adservices/framework/java/android/adservices/adselection/AdSelectionManager.java
+++ b/adservices/framework/java/android/adservices/adselection/AdSelectionManager.java
@@ -19,6 +19,7 @@ package android.adservices.adselection;
import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE;
import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.CallerMetadata;
import android.adservices.common.FledgeErrorResponse;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -28,6 +29,7 @@ import android.content.Context;
import android.os.LimitExceededException;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.TransactionTooLargeException;
import com.android.adservices.AdServicesCommon;
@@ -140,6 +142,9 @@ public class AdSelectionManager {
.setAdSelectionConfig(adSelectionConfig)
.setCallerPackageName(getCallerPackageName())
.build(),
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
+ .build(),
new AdSelectionCallback.Stub() {
@Override
public void onSuccess(AdSelectionResponse resultParcel) {
diff --git a/adservices/framework/java/android/adservices/adselection/AdSelectionService.aidl b/adservices/framework/java/android/adservices/adselection/AdSelectionService.aidl
index 5d1387d36..a63401071 100644
--- a/adservices/framework/java/android/adservices/adselection/AdSelectionService.aidl
+++ b/adservices/framework/java/android/adservices/adselection/AdSelectionService.aidl
@@ -18,11 +18,12 @@ package android.adservices.adselection;
import android.adservices.adselection.AdSelectionCallback;
import android.adservices.adselection.AdSelectionConfig;
-import android.adservices.common.AdSelectionSignals;
import android.adservices.adselection.ReportImpressionInput;
import android.adservices.adselection.AdSelectionInput;
import android.adservices.adselection.ReportImpressionCallback;
import android.adservices.adselection.AdSelectionOverrideCallback;
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.CallerMetadata;
/**
* This is the Ad Selection Service, which defines the interface used for the Ad selection workflow
@@ -65,7 +66,7 @@ interface AdSelectionService {
*
* {@hide}
*/
- void runAdSelection(in AdSelectionInput request, in AdSelectionCallback callback);
+ void runAdSelection(in AdSelectionInput request, in CallerMetadata callerMetadata, in AdSelectionCallback callback);
/**
* Notifies PPAPI that there is a new impression to report for the
diff --git a/adservices/framework/java/android/adservices/common/AdServicesStatusUtils.java b/adservices/framework/java/android/adservices/common/AdServicesStatusUtils.java
index fb53de22c..97e32d151 100644
--- a/adservices/framework/java/android/adservices/common/AdServicesStatusUtils.java
+++ b/adservices/framework/java/android/adservices/common/AdServicesStatusUtils.java
@@ -115,6 +115,13 @@ public class AdServicesStatusUtils {
* <p>This error may be considered similar to {@link java.util.concurrent.TimeoutException}
*/
public static final int STATUS_TIMEOUT = 13;
+ /**
+ * The device is not running a version of WebView that supports JSSandbox, required for FLEDGE
+ * Ad Selection.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}.
+ */
+ public static final int STATUS_JS_SANDBOX_UNAVAILABLE = 14;
/** The error message to be returned along with {@link IllegalStateException}. */
public static final String ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE = "Service is not available.";
@@ -164,6 +171,7 @@ public class AdServicesStatusUtils {
return new IOException();
case STATUS_KILLSWITCH_ENABLED: // Intentional fallthrough
case STATUS_USER_CONSENT_REVOKED: // Intentional fallthrough
+ case STATUS_JS_SANDBOX_UNAVAILABLE:
return new IllegalStateException(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE);
case STATUS_PERMISSION_NOT_REQUESTED:
return new SecurityException(
@@ -212,7 +220,8 @@ public class AdServicesStatusUtils {
STATUS_CALLER_NOT_ALLOWED,
STATUS_BACKGROUND_CALLER,
STATUS_UNAUTHORIZED,
- STATUS_TIMEOUT
+ STATUS_TIMEOUT,
+ STATUS_JS_SANDBOX_UNAVAILABLE
})
@Retention(RetentionPolicy.SOURCE)
public @interface StatusCode {}
diff --git a/adservices/framework/java/android/adservices/measurement/RegistrationRequest.java b/adservices/framework/java/android/adservices/measurement/RegistrationRequest.java
index 8f71375ce..303be5957 100644
--- a/adservices/framework/java/android/adservices/measurement/RegistrationRequest.java
+++ b/adservices/framework/java/android/adservices/measurement/RegistrationRequest.java
@@ -51,7 +51,6 @@ public final class RegistrationRequest implements Parcelable {
private final @RegistrationType int mRegistrationType;
private final Uri mRegistrationUri;
- private final Uri mTopOriginUri;
private final InputEvent mInputEvent;
private final String mPackageName;
private final long mRequestTime;
@@ -60,7 +59,6 @@ public final class RegistrationRequest implements Parcelable {
private RegistrationRequest(@NonNull Builder builder) {
mRegistrationType = builder.mRegistrationType;
mRegistrationUri = builder.mRegistrationUri;
- mTopOriginUri = builder.mTopOriginUri;
mInputEvent = builder.mInputEvent;
mPackageName = builder.mPackageName;
mRequestTime = builder.mRequestTime;
@@ -73,7 +71,6 @@ public final class RegistrationRequest implements Parcelable {
private RegistrationRequest(Parcel in) {
mRegistrationType = in.readInt();
mRegistrationUri = Uri.CREATOR.createFromParcel(in);
- mTopOriginUri = Uri.CREATOR.createFromParcel(in);
mPackageName = in.readString();
boolean hasInputEvent = in.readBoolean();
if (hasInputEvent) {
@@ -113,7 +110,6 @@ public final class RegistrationRequest implements Parcelable {
Objects.requireNonNull(out);
out.writeInt(mRegistrationType);
mRegistrationUri.writeToParcel(out, flags);
- mTopOriginUri.writeToParcel(out, flags);
out.writeString(mPackageName);
if (mInputEvent != null) {
out.writeBoolean(true);
@@ -133,13 +129,6 @@ public final class RegistrationRequest implements Parcelable {
}
/**
- * Top level origin of the App / Publisher.
- */
- public @NonNull Uri getTopOriginUri() {
- return mTopOriginUri;
- }
-
- /**
* Source URI of the App / Publisher.
*/
public @NonNull Uri getRegistrationUri() {
@@ -173,7 +162,6 @@ public final class RegistrationRequest implements Parcelable {
public static final class Builder {
private @RegistrationType int mRegistrationType;
private Uri mRegistrationUri;
- private Uri mTopOriginUri;
private InputEvent mInputEvent;
private String mPackageName;
private long mRequestTime;
@@ -198,15 +186,6 @@ public final class RegistrationRequest implements Parcelable {
}
/**
- * See {@link RegistrationRequest#getTopOriginUri}.
- */
- public @NonNull Builder setTopOriginUri(@NonNull Uri origin) {
- Objects.requireNonNull(origin);
- mTopOriginUri = origin;
- return this;
- }
-
- /**
* See {@link RegistrationRequest#getRegistrationUri}.
*/
public @NonNull Builder setRegistrationUri(@NonNull Uri uri) {
@@ -265,12 +244,6 @@ public final class RegistrationRequest implements Parcelable {
throw new IllegalArgumentException("packageName unset");
}
- // Check if topOrigin has been set.
- // However, if it's not set, caller package is defaulted
- if (mTopOriginUri == null) {
- mTopOriginUri = Uri.parse("android-app://" + mPackageName);
- }
-
return new RegistrationRequest(this);
}
}
diff --git a/adservices/framework/java/android/adservices/topics/GetTopicsParam.java b/adservices/framework/java/android/adservices/topics/GetTopicsParam.java
index 201ee133d..ce80436ae 100644
--- a/adservices/framework/java/android/adservices/topics/GetTopicsParam.java
+++ b/adservices/framework/java/android/adservices/topics/GetTopicsParam.java
@@ -17,6 +17,7 @@
package android.adservices.topics;
import static android.adservices.topics.TopicsManager.EMPTY_SDK;
+import static android.adservices.topics.TopicsManager.RECORD_OBSERVATION_DEFAULT;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,20 +33,24 @@ public final class GetTopicsParam implements Parcelable {
private final String mSdkName;
private final String mSdkPackageName;
private final String mAppPackageName;
+ private final boolean mRecordObservation;
private GetTopicsParam(
@NonNull String sdkName,
@Nullable String sdkPackageName,
- @NonNull String appPackageName) {
+ @NonNull String appPackageName,
+ boolean recordObservation) {
mSdkName = sdkName;
mSdkPackageName = sdkPackageName;
mAppPackageName = appPackageName;
+ mRecordObservation = recordObservation;
}
private GetTopicsParam(@NonNull Parcel in) {
mSdkName = in.readString();
mSdkPackageName = in.readString();
mAppPackageName = in.readString();
+ mRecordObservation = in.readBoolean();
}
public static final @NonNull Creator<GetTopicsParam> CREATOR =
@@ -71,6 +76,7 @@ public final class GetTopicsParam implements Parcelable {
out.writeString(mSdkName);
out.writeString(mSdkPackageName);
out.writeString(mAppPackageName);
+ out.writeBoolean(mRecordObservation);
}
/** Get the Sdk Name. This is the name in the <sdk-library> tag of the Manifest. */
@@ -91,11 +97,17 @@ public final class GetTopicsParam implements Parcelable {
return mAppPackageName;
}
+ /** Get the Record Observation. */
+ public boolean shouldRecordObservation() {
+ return mRecordObservation;
+ }
+
/** Builder for {@link GetTopicsParam} objects. */
public static final class Builder {
private String mSdkName;
private String mSdkPackageName;
private String mAppPackageName;
+ private boolean mRecordObservation = RECORD_OBSERVATION_DEFAULT;
public Builder() {}
@@ -123,6 +135,16 @@ public final class GetTopicsParam implements Parcelable {
return this;
}
+ /**
+ * Set the Record Observation. Whether to record that the caller has observed the topics of
+ * the host app or not. This will be used to determine if the caller can receive the topic
+ * in the next epoch.
+ */
+ public @NonNull Builder setShouldRecordObservation(boolean recordObservation) {
+ mRecordObservation = recordObservation;
+ return this;
+ }
+
/** Builds a {@link GetTopicsParam} instance. */
public @NonNull GetTopicsParam build() {
if (mSdkName == null) {
@@ -142,7 +164,8 @@ public final class GetTopicsParam implements Parcelable {
throw new IllegalArgumentException("App PackageName must not be empty or null");
}
- return new GetTopicsParam(mSdkName, mSdkPackageName, mAppPackageName);
+ return new GetTopicsParam(
+ mSdkName, mSdkPackageName, mAppPackageName, mRecordObservation);
}
}
}
diff --git a/adservices/framework/java/android/adservices/topics/GetTopicsRequest.java b/adservices/framework/java/android/adservices/topics/GetTopicsRequest.java
index 5a396e3de..cc8e51bdf 100644
--- a/adservices/framework/java/android/adservices/topics/GetTopicsRequest.java
+++ b/adservices/framework/java/android/adservices/topics/GetTopicsRequest.java
@@ -15,9 +15,10 @@
*/
package android.adservices.topics;
+import static android.adservices.topics.TopicsManager.EMPTY_SDK;
+import static android.adservices.topics.TopicsManager.RECORD_OBSERVATION_DEFAULT;
import android.annotation.NonNull;
-import android.annotation.Nullable;
/** Get Topics Request. */
public final class GetTopicsRequest {
@@ -25,8 +26,12 @@ public final class GetTopicsRequest {
/** Name of Ads SDK that is involved in this request. */
private final String mAdsSdkName;
- private GetTopicsRequest(@Nullable String adsSdkName) {
- mAdsSdkName = adsSdkName;
+ /** Whether to record that the caller has observed the topics of the host app or not. */
+ private final boolean mRecordObservation;
+
+ private GetTopicsRequest(@NonNull Builder builder) {
+ mAdsSdkName = builder.mAdsSdkName;
+ mRecordObservation = builder.mRecordObservation;
}
/** Get the Sdk Name. */
@@ -35,33 +40,58 @@ public final class GetTopicsRequest {
return mAdsSdkName;
}
- /**
- * Builds a {@link GetTopicsRequest} instance.
- *
- * <p>This should be called by either the app itself or by SDK running inside the Sandbox.
- */
- @NonNull
- public static GetTopicsRequest create() {
- return new GetTopicsRequest(/* adsSdkName */ null);
+ /** Get Record Observation. */
+ public boolean shouldRecordObservation() {
+ return mRecordObservation;
}
- /**
- * Create a {@link GetTopicsRequest} instance with the provided Ads Sdk Name.
- *
- * <p>This should be called by SDKs running outside of the Sandbox.
- *
- * @param adsSdkName the Ads Sdk Name.
- */
- @NonNull
- public static GetTopicsRequest createWithAdsSdkName(@NonNull String adsSdkName) {
- // This is the case the SDK calling without the Sandbox.
- // Check if the caller set the adsSdkName
- if (adsSdkName == null) {
- throw new IllegalArgumentException(
- "When calling Topics API outside of the Sandbox, caller should set Ads Sdk"
- + " Name");
+ /** Builder for {@link GetTopicsRequest} objects. */
+ public static final class Builder {
+ private String mAdsSdkName = EMPTY_SDK;
+ private boolean mRecordObservation = RECORD_OBSERVATION_DEFAULT;
+
+ /** Creates a {@link Builder} for {@link GetTopicsRequest} objects. */
+ public Builder() {}
+
+ /**
+ * Set Ads Sdk Name.
+ *
+ * <p>This must be called by SDKs running outside of the Sandbox. Other clients must not
+ * call it.
+ *
+ * @param adsSdkName the Ads Sdk Name.
+ */
+ @NonNull
+ public Builder setAdsSdkName(@NonNull String adsSdkName) {
+ // This is the case the SDK calling from outside of the Sandbox.
+ // Check if the caller set the adsSdkName
+ if (adsSdkName == null) {
+ throw new IllegalArgumentException(
+ "When calling Topics API outside of the Sandbox, caller should set Ads Sdk"
+ + " Name");
+ }
+
+ mAdsSdkName = adsSdkName;
+ return this;
+ }
+
+ /**
+ * Set the Record Observation.
+ *
+ * @param recordObservation whether to record that the caller has observed the topics of the
+ * host app or not. This will be used to determine if the caller can receive the topic
+ * in the next epoch.
+ */
+ @NonNull
+ public Builder setShouldRecordObservation(boolean recordObservation) {
+ mRecordObservation = recordObservation;
+ return this;
}
- return new GetTopicsRequest(adsSdkName);
+ /** Builds a {@link GetTopicsRequest} instance. */
+ @NonNull
+ public GetTopicsRequest build() {
+ return new GetTopicsRequest(this);
+ }
}
-} \ No newline at end of file
+}
diff --git a/adservices/framework/java/android/adservices/topics/TopicsManager.java b/adservices/framework/java/android/adservices/topics/TopicsManager.java
index 0a5c3964a..6a583cb95 100644
--- a/adservices/framework/java/android/adservices/topics/TopicsManager.java
+++ b/adservices/framework/java/android/adservices/topics/TopicsManager.java
@@ -30,6 +30,7 @@ import android.os.LimitExceededException;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.text.TextUtils;
import com.android.adservices.AdServicesCommon;
import com.android.adservices.LogUtil;
@@ -59,6 +60,9 @@ public final class TopicsManager {
// When an app calls the Topics API directly, it sets the SDK name to empty string.
static final String EMPTY_SDK = "";
+ // Default value is true to record SDK's Observation when it calls Topics API.
+ static final boolean RECORD_OBSERVATION_DEFAULT = true;
+
private Context mContext;
private ServiceBinder<ITopicsService> mServiceBinder;
@@ -136,7 +140,7 @@ public final class TopicsManager {
sdkPackageName = sandboxedSdkContext.getSdkPackageName();
appPackageName = sandboxedSdkContext.getClientPackageName();
- if (sdkName != null) {
+ if (!TextUtils.isEmpty(sdkName)) {
throw new IllegalArgumentException(
"When calling Topics API from Sandbox, caller should not set Ads Sdk Name");
}
@@ -163,6 +167,7 @@ public final class TopicsManager {
.setAppPackageName(appPackageName)
.setSdkName(sdkName)
.setSdkPackageName(sdkPackageName)
+ .setShouldRecordObservation(getTopicsRequest.shouldRecordObservation())
.build(),
callerMetadata,
new IGetTopicsCallback.Stub() {
diff --git a/adservices/framework/java/com/android/adservices/AndroidServiceBinder.java b/adservices/framework/java/com/android/adservices/AndroidServiceBinder.java
index f8e8bf35a..47e830c25 100644
--- a/adservices/framework/java/com/android/adservices/AndroidServiceBinder.java
+++ b/adservices/framework/java/com/android/adservices/AndroidServiceBinder.java
@@ -108,17 +108,26 @@ class AndroidServiceBinder<T> extends ServiceBinder<T> {
// We use Runnable::run so that the callback is called on a binder thread.
// Otherwise we'd use the main thread, which could cause a deadlock.
- final boolean success =
- mContext.bindService(intent, BIND_FLAGS, Runnable::run, mServiceConnection);
- if (!success) {
- LogUtil.e("Failed to bindService: " + intent);
+ try {
+ final boolean success =
+ mContext.bindService(
+ intent, BIND_FLAGS, Runnable::run, mServiceConnection);
+ if (!success) {
+ LogUtil.e("Failed to bindService: " + intent);
+ mServiceConnection = null;
+ return null;
+ } else {
+ LogUtil.d("bindService() started...");
+ }
+ } catch (Exception e) {
+ LogUtil.e(
+ "Caught unexpected exception during service binding: "
+ + e.getMessage());
mServiceConnection = null;
return null;
- } else {
- LogUtil.d("bindService() succeeded...");
}
} else {
- LogUtil.d("bindService() already pending...");
+ LogUtil.d("There is already a pending connection!");
}
}
@@ -155,27 +164,21 @@ class AndroidServiceBinder<T> extends ServiceBinder<T> {
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtil.d("onServiceDisconnected " + mServiceIntentAction);
- synchronized (mLock) {
- mService = null;
- }
+ unbindFromService();
mConnectionCountDownLatch.countDown();
}
@Override
public void onBindingDied(ComponentName name) {
LogUtil.d("onBindingDied " + mServiceIntentAction);
- synchronized (mLock) {
- mService = null;
- }
+ unbindFromService();
mConnectionCountDownLatch.countDown();
}
@Override
public void onNullBinding(ComponentName name) {
- LogUtil.e("onNullBinding shouldn't happen: " + mServiceIntentAction);
- synchronized (mLock) {
- mService = null;
- }
+ LogUtil.e("onNullBinding " + mServiceIntentAction);
+ unbindFromService();
mConnectionCountDownLatch.countDown();
}
}
@@ -227,12 +230,10 @@ class AndroidServiceBinder<T> extends ServiceBinder<T> {
@Override
public void unbindFromService() {
synchronized (mLock) {
- if (mService == null || mServiceConnection == null) {
- return; // Nothing to release.
+ if (mServiceConnection != null) {
+ LogUtil.d("unbinding...");
+ mContext.unbindService(mServiceConnection);
}
-
- LogUtil.d("unbinding...");
- mContext.unbindService(mServiceConnection);
mServiceConnection = null;
mService = null;
}
diff --git a/adservices/samples/topics/sampleapp1/java/com/example/adservices/samples/topics/sampleapp1/MainActivity.java b/adservices/samples/topics/sampleapp1/java/com/example/adservices/samples/topics/sampleapp1/MainActivity.java
index 93ed3f3ef..b7e9a7294 100644
--- a/adservices/samples/topics/sampleapp1/java/com/example/adservices/samples/topics/sampleapp1/MainActivity.java
+++ b/adservices/samples/topics/sampleapp1/java/com/example/adservices/samples/topics/sampleapp1/MainActivity.java
@@ -57,6 +57,7 @@ public class MainActivity extends AppCompatActivity {
private static final String RB_SETTING_APP_INTENT = "android.adservices.ui.SETTINGS";
private static final List<String> SDK_NAMES = new ArrayList<>(Arrays.asList("SdkName1"));
private Button mTopicsClientButton;
+ private Button mTopicsPreviewButton;
private TextView mResultTextView;
private Button mSettingsAppButton;
private AdvertisingTopicsClient mAdvertisingTopicsClient;
@@ -68,9 +69,11 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
mTopicsClientButton = findViewById(R.id.topics_client_button);
mSettingsAppButton = findViewById(R.id.settings_app_launch_button);
+ mTopicsPreviewButton = findViewById(R.id.topics_preview_button);
mResultTextView = findViewById(R.id.textView);
registerGetTopicsButton();
registerLauchSettingsAppButton();
+ registerTopicsPreviewButton();
mHandler = new Handler();
}
@@ -78,68 +81,20 @@ public class MainActivity extends AppCompatActivity {
mTopicsClientButton.setOnClickListener(
v -> {
mResultTextView.setText("");
+ mResultTextView.append("Topics -> ");
for (String sdkName : SDK_NAMES) {
- mAdvertisingTopicsClient =
- new AdvertisingTopicsClient.Builder()
- .setContext(this)
- .setSdkName(sdkName)
- .setExecutor(CALLBACK_EXECUTOR)
- .build();
- ListenableFuture<GetTopicsResponse> getTopicsResponseFuture =
- mAdvertisingTopicsClient.getTopics();
-
- Futures.addCallback(
- getTopicsResponseFuture,
- new FutureCallback<GetTopicsResponse>() {
- @Override
- public void onSuccess(GetTopicsResponse result) {
- Log.d(TAG, "GetTopics for sdk " + sdkName + " succeeded!");
- String topics = getTopics(result.getTopics());
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mResultTextView.append(
- sdkName
- + "'s topics: "
- + NEWLINE
- + topics
- + NEWLINE);
- }
- });
- Log.d(
- TAG,
- sdkName
- + "'s topics: "
- + NEWLINE
- + topics
- + NEWLINE);
- }
-
- @Override
- public void onFailure(Throwable t) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- t.printStackTrace(pw);
- Log.e(
- TAG,
- "Failed to getTopics for sdk "
- + sdkName
- + ": "
- + t.getMessage());
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mResultTextView.append(
- "Failed to getTopics for sdk "
- + sdkName
- + ": "
- + t.toString()
- + NEWLINE);
- }
- });
- }
- },
- directExecutor());
+ getTopics(sdkName, true);
+ }
+ });
+ }
+
+ private void registerTopicsPreviewButton() {
+ mTopicsPreviewButton.setOnClickListener(
+ v -> {
+ mResultTextView.setText("");
+ mResultTextView.append("Preview Topics -> ");
+ for (String sdkName : SDK_NAMES) {
+ getTopics(sdkName, false);
}
});
}
@@ -153,6 +108,73 @@ public class MainActivity extends AppCompatActivity {
return sb.toString();
}
+ private void getTopics(String sdkName, boolean shouldRecordObservation){
+ mAdvertisingTopicsClient =
+ new AdvertisingTopicsClient.Builder()
+ .setContext(this)
+ .setSdkName(sdkName)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .setShouldRecordObservation(shouldRecordObservation)
+ .build();
+ ListenableFuture<GetTopicsResponse> getTopicsResponseFuture =
+ mAdvertisingTopicsClient.getTopics();
+
+
+ Futures.addCallback(
+ getTopicsResponseFuture,
+ new FutureCallback<GetTopicsResponse>() {
+ @Override
+ public void onSuccess(GetTopicsResponse result) {
+ Log.d(TAG, "GetTopics for sdk " + sdkName + " succeeded!");
+ String topics = getTopics(result.getTopics());
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mResultTextView.append(
+ sdkName
+ + "'s topics: "
+ + NEWLINE
+ + topics
+ + NEWLINE);
+ }
+ });
+ Log.d(
+ TAG,
+ sdkName
+ + "'s topics: "
+ + NEWLINE
+ + topics
+ + NEWLINE);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ Log.e(
+ TAG,
+ "Failed to getTopics for sdk "
+ + sdkName
+ + ": "
+ + t.getMessage());
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mResultTextView.append(
+ "Failed to getTopics for sdk "
+ + sdkName
+ + ": "
+ + t.toString()
+ + NEWLINE);
+ }
+ });
+ }
+ },
+ directExecutor()
+ );
+ }
+
private void registerLauchSettingsAppButton() {
mSettingsAppButton.setOnClickListener(
new View.OnClickListener() {
diff --git a/adservices/samples/topics/sampleapp1/res/layout/activity_main.xml b/adservices/samples/topics/sampleapp1/res/layout/activity_main.xml
index 20da08894..793c50de2 100644
--- a/adservices/samples/topics/sampleapp1/res/layout/activity_main.xml
+++ b/adservices/samples/topics/sampleapp1/res/layout/activity_main.xml
@@ -22,7 +22,7 @@
android:id="@+id/topics_client_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/button_name"
+ android:text="@string/topics_button_name"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
@@ -31,6 +31,20 @@
app:layout_constraintVertical_bias="0.130"
android:layout_below="@+id/settings_app_launch_button" />
+ <Button
+ android:id="@+id/topics_preview_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/topics_preview_button_name"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.497"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.200"
+ android:layout_below="@+id/topics_client_button" />
+
+
<TextView
android:id="@+id/textView"
android:layout_width="377dp"
@@ -42,6 +56,6 @@
app:layout_constraintHorizontal_bias="0.301"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.400"
- android:layout_below="@+id/topics_client_button"/>
+ app:layout_constraintVertical_bias="0.500"
+ android:layout_below="@+id/topics_preview_button"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/adservices/samples/topics/sampleapp1/res/values/strings.xml b/adservices/samples/topics/sampleapp1/res/values/strings.xml
index 10482cb31..a181594fd 100644
--- a/adservices/samples/topics/sampleapp1/res/values/strings.xml
+++ b/adservices/samples/topics/sampleapp1/res/values/strings.xml
@@ -1,6 +1,7 @@
<resources>
<string name="app_name">SampleTopicsApp1</string>
<string name="app_description">Sample app for Topics API</string>
- <string name="button_name">Topics Client Button</string>
+ <string name="topics_button_name">Topics Client Button</string>
+ <string name="topics_preview_button_name">Preview Topics Button</string>
<string name="settings_app_launchbutton_name">Settings App</string>
</resources>
diff --git a/adservices/service-core/Android.bp b/adservices/service-core/Android.bp
index 3904817cf..13786cca1 100644
--- a/adservices/service-core/Android.bp
+++ b/adservices/service-core/Android.bp
@@ -84,6 +84,8 @@ android_library {
"mobile_data_downloader_lib",
"androidx.webkit_webkit",
"androidx.javascriptengine_javascriptengine",
+ "adservices-proto-lite",
+ "adservices-grpclib-lite",
],
apex_available: ["com.android.adservices"],
}
@@ -105,3 +107,18 @@ cc_library_shared {
apex_available: ["com.android.adservices"],
visibility: ["//packages/modules/AdServices:__subpackages__"],
}
+
+// Schemas needs to be bundled via this android_library since service-core unit tests for schema
+// migration will need this.
+android_library {
+ name: "adservices-service-core-schema",
+ sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
+ asset_dirs: [
+ "schemas",
+ ],
+ // Must use EmptyManifest.xml since this will be used by the service-core tests. If we use the
+ // normal "AndroidManifest.xml", we may introduce potential error like redeclare permissions.
+ // See b/228270294 for the error about duplicated permissions.
+ manifest: "EmptyManifest.xml",
+}
diff --git a/adservices/service-core/EmptyManifest.xml b/adservices/service-core/EmptyManifest.xml
new file mode 100644
index 000000000..92398522f
--- /dev/null
+++ b/adservices/service-core/EmptyManifest.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.adservices.api">
+</manifest>
diff --git a/adservices/service-core/java/com/android/adservices/concurrency/AdServicesExecutors.java b/adservices/service-core/java/com/android/adservices/concurrency/AdServicesExecutors.java
index 379d181ed..0795441e6 100644
--- a/adservices/service-core/java/com/android/adservices/concurrency/AdServicesExecutors.java
+++ b/adservices/service-core/java/com/android/adservices/concurrency/AdServicesExecutors.java
@@ -17,14 +17,18 @@
package com.android.adservices.concurrency;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
/**
* ALl executors of the PP API module.
@@ -34,14 +38,35 @@ import java.util.concurrent.TimeUnit;
// TODO(b/224987182): set appropriate parameters (priority, size, etc..) for the shared thread pools
// after doing detailed analysis. Ideally the parameters should be backed by PH flags.
public final class AdServicesExecutors {
- // We set the minimal number of threads for background executor to 4 and lightweight executor to
- // 2 since Runtime.getRuntime().availableProcessors() may return 1 or 2 for low-end devices.
- // This may cause deadlock for starvation in those low-end devices.
+ // We set the minimal number of threads for background executor to 4 and lightweight & scheduled
+ // executors to 2 since Runtime.getRuntime().availableProcessors() may return 1 or 2 for
+ // low-end devices. This may cause deadlock for starvation in those low-end devices.
private static final int MIN_BACKGROUND_EXECUTOR_THREADS = 4;
private static final int MIN_LIGHTWEIGHT_EXECUTOR_THREADS = 2;
+ private static final int MAX_SCHEDULED_EXECUTOR_THREADS = 4;
+
+ private static final String LIGHTWEIGHT_NAME = "lightweight";
+ private static final String BACKGROUND_NAME = "background";
+ private static final String SCHEDULED_NAME = "scheduled";
+ private static final String BLOCKING_NAME = "blocking";
+
+ private static ThreadFactory getFactory(final String threadPrefix) {
+ return new ThreadFactory() {
+ private final AtomicLong mThreadCount = new AtomicLong(0L);
+
+ @SuppressLint("DefaultLocale")
+ @Override
+ public Thread newThread(Runnable runnable) {
+ Thread thread = Executors.defaultThreadFactory().newThread(runnable);
+ thread.setName(
+ String.format("%s-%d", threadPrefix, mThreadCount.incrementAndGet()));
+ return thread;
+ }
+ };
+ }
private static final ListeningExecutorService sLightWeightExecutor =
- // Always use at least two threads, so that clients can't depend on light weight
+ // Always use at least two threads, so that clients can't depend on light-weight
// executor tasks executing sequentially
MoreExecutors.listeningDecorator(
new ThreadPoolExecutor(
@@ -54,7 +79,8 @@ public final class AdServicesExecutors {
Runtime.getRuntime().availableProcessors() - 2),
/* keepAliveTime = */ 60L,
TimeUnit.SECONDS,
- new LinkedBlockingQueue<>()));
+ new LinkedBlockingQueue<>(),
+ getFactory(LIGHTWEIGHT_NAME)));
/**
* Functions that don't do direct I/O and that are fast (under ten milliseconds or thereabouts)
@@ -78,7 +104,8 @@ public final class AdServicesExecutors {
Runtime.getRuntime().availableProcessors()),
/* keepAliveTime = */ 60L,
TimeUnit.SECONDS,
- new LinkedBlockingQueue<>()));
+ new LinkedBlockingQueue<>(),
+ getFactory(BACKGROUND_NAME)));
/**
* Functions that directly execute disk I/O, or that are CPU bound and long-running (over ten
@@ -93,8 +120,29 @@ public final class AdServicesExecutors {
return sBackgroundExecutor;
}
+ private static final ScheduledThreadPoolExecutor sScheduler =
+ new ScheduledThreadPoolExecutor(
+ /* corePoolSize = */ Math.min(
+ MAX_SCHEDULED_EXECUTOR_THREADS,
+ Runtime.getRuntime().availableProcessors()),
+ getFactory(SCHEDULED_NAME));
+
+ /**
+ * Functions that require to be run with a delay, or have timed executions should run on this
+ * Executor
+ *
+ * <p>Example includes having timeouts on Futures
+ *
+ * @return
+ */
+ @NonNull
+ public static ScheduledThreadPoolExecutor getScheduler() {
+ return sScheduler;
+ }
+
private static final ListeningExecutorService sBlockingExecutor =
- MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+ MoreExecutors.listeningDecorator(
+ Executors.newCachedThreadPool(getFactory(BLOCKING_NAME)));
/**
* Functions that directly execute network I/O, or that block their thread awaiting the progress
diff --git a/adservices/service-core/java/com/android/adservices/data/DbHelper.java b/adservices/service-core/java/com/android/adservices/data/DbHelper.java
index 85f29184f..8925df896 100644
--- a/adservices/service-core/java/com/android/adservices/data/DbHelper.java
+++ b/adservices/service-core/java/com/android/adservices/data/DbHelper.java
@@ -28,7 +28,11 @@ import com.android.adservices.data.enrollment.EnrollmentTables;
import com.android.adservices.data.measurement.MeasurementTables;
import com.android.adservices.data.measurement.migration.IMeasurementDbMigrator;
import com.android.adservices.data.measurement.migration.MeasurementDbMigratorV2;
+import com.android.adservices.data.measurement.migration.MeasurementDbMigratorV3;
import com.android.adservices.data.topics.TopicsTables;
+import com.android.adservices.data.topics.migration.ITopicsDbMigrator;
+import com.android.adservices.data.topics.migration.TopicDbMigratorV3;
+import com.android.adservices.service.FlagsFactory;
import com.android.internal.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -41,12 +45,16 @@ import java.util.List;
* get the same reference.
*/
public class DbHelper extends SQLiteOpenHelper {
+ // Version 3: Add TopicContributors Table for Topics API, guarded by feature flag.
+ public static final int DATABASE_VERSION_V3 = 3;
- static final int LATEST_DATABASE_VERSION = 2;
+ static final int CURRENT_DATABASE_VERSION = 2;
private static final String DATABASE_NAME = "adservices.db";
private static DbHelper sSingleton = null;
private final File mDbFile;
+ // The version when the database is actually created
+ private final int mDbVersion;
/**
* It's only public to unit test.
@@ -59,6 +67,7 @@ public class DbHelper extends SQLiteOpenHelper {
public DbHelper(@NonNull Context context, @NonNull String dbName, int dbVersion) {
super(context, dbName, null, dbVersion);
mDbFile = context.getDatabasePath(dbName);
+ this.mDbVersion = dbVersion;
}
/** Returns an instance of the DbHelper given a context. */
@@ -66,7 +75,7 @@ public class DbHelper extends SQLiteOpenHelper {
public static DbHelper getInstance(@NonNull Context ctx) {
synchronized (DbHelper.class) {
if (sSingleton == null) {
- sSingleton = new DbHelper(ctx, DATABASE_NAME, LATEST_DATABASE_VERSION);
+ sSingleton = new DbHelper(ctx, DATABASE_NAME, getDatabaseVersionToCreate());
}
return sSingleton;
}
@@ -74,7 +83,7 @@ public class DbHelper extends SQLiteOpenHelper {
@Override
public void onCreate(@NonNull SQLiteDatabase db) {
- LogUtil.d("DbHelper.onCreate.");
+ LogUtil.d("DbHelper.onCreate with version %d. Name: %s", mDbVersion, mDbFile.getName());
for (String sql : TopicsTables.CREATE_STATEMENTS) {
db.execSQL(sql);
}
@@ -89,6 +98,12 @@ public class DbHelper extends SQLiteOpenHelper {
}
}
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+ db.execSQL("PRAGMA foreign_keys=ON");
+ }
+
/**
* Wraps getReadableDatabase to catch SQLiteException and log error.
*/
@@ -102,9 +117,7 @@ public class DbHelper extends SQLiteOpenHelper {
}
}
- /**
- * Wraps getWritableDatabase to catch SQLiteException and log error.
- */
+ /** Wraps getWritableDatabase to catch SQLiteException and log error. */
@Nullable
public SQLiteDatabase safeGetWritableDatabase() {
try {
@@ -115,18 +128,54 @@ public class DbHelper extends SQLiteOpenHelper {
}
}
+ // TODO(b/255964885): Consolidate DB Migrator Class across Rubidium
@Override
public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
- LogUtil.d("DbHelper.onUpgrade.");
+ LogUtil.d(
+ "DbHelper.onUpgrade. Attempting to upgrade version from %d to %d.",
+ oldVersion, newVersion);
getOrderedDbMigrators()
.forEach(dbMigrator -> dbMigrator.performMigration(db, oldVersion, newVersion));
+ try {
+ topicsGetOrderedDbMigrators()
+ .forEach(dbMigrator -> dbMigrator.performMigration(db, oldVersion, newVersion));
+ } catch (IllegalArgumentException e) {
+ LogUtil.e(
+ "Topics DB Upgrade is not performed! oldVersion: %d, newVersion: %d.",
+ oldVersion, newVersion);
+ }
}
public long getDbFileSize() {
return mDbFile != null && mDbFile.exists() ? mDbFile.length() : -1;
}
- private static List<IMeasurementDbMigrator> getOrderedDbMigrators() {
- return ImmutableList.of(new MeasurementDbMigratorV2());
+ /**
+ * Check whether TopContributors Table is supported in current database. TopContributors is
+ * introduced in Version 3.
+ */
+ public boolean supportsTopicContributorsTable() {
+ return mDbVersion >= DATABASE_VERSION_V3;
+ }
+
+ /** Get Migrators in order for Measurement. */
+ @VisibleForTesting
+ public List<IMeasurementDbMigrator> getOrderedDbMigrators() {
+ return ImmutableList.of(new MeasurementDbMigratorV2(), new MeasurementDbMigratorV3());
+ }
+
+ /** Get Migrators in order for Topics. */
+ @VisibleForTesting
+ public List<ITopicsDbMigrator> topicsGetOrderedDbMigrators() {
+ return ImmutableList.of(new TopicDbMigratorV3());
+ }
+
+ // Get the database version to create. It may be different as LATEST_DATABASE_VERSION, depending
+ // on Flags status.
+ @VisibleForTesting
+ static int getDatabaseVersionToCreate() {
+ return FlagsFactory.getFlags().getEnableDatabaseSchemaVersion3()
+ ? DATABASE_VERSION_V3
+ : CURRENT_DATABASE_VERSION;
}
}
diff --git a/adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionDatabase.java b/adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionDatabase.java
index 1a387387f..493888bc9 100644
--- a/adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionDatabase.java
+++ b/adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionDatabase.java
@@ -31,13 +31,13 @@ import java.util.Objects;
/** Room based database for Ad Selection. */
@Database(
- exportSchema = false,
entities = {DBAdSelection.class, DBBuyerDecisionLogic.class, DBAdSelectionOverride.class},
- version = 1)
+ version = AdSelectionDatabase.DATABASE_VERSION)
@TypeConverters({FledgeRoomConverters.class})
public abstract class AdSelectionDatabase extends RoomDatabase {
private static final Object SINGLETON_LOCK = new Object();
+ public static final int DATABASE_VERSION = 1;
// TODO(b/230653780): Should we separate the DB.
public static final String DATABASE_NAME = "adselection.db";
diff --git a/adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionEntryDao.java b/adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionEntryDao.java
index 0bdb56093..03aa50f5e 100644
--- a/adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionEntryDao.java
+++ b/adservices/service-core/java/com/android/adservices/data/adselection/AdSelectionEntryDao.java
@@ -16,6 +16,8 @@
package com.android.adservices.data.adselection;
+import android.net.Uri;
+
import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Insert;
@@ -72,6 +74,18 @@ public interface AdSelectionEntryDao {
boolean doesAdSelectionIdExist(long adSelectionId);
/**
+ * Checks if there is a row in the buyer decision logic data with the unique key
+ * bidding_logic_uri
+ *
+ * @param biddingLogicUri which is the key to query the corresponding buyer decision logic data.
+ * @return true if row exists, false otherwise
+ */
+ @Query(
+ "SELECT EXISTS(SELECT 1 FROM buyer_decision_logic WHERE bidding_logic_uri ="
+ + " :biddingLogicUri LIMIT 1)")
+ boolean doesBuyerDecisionLogicExist(Uri biddingLogicUri);
+
+ /**
* Checks if there is a row in the ad selection override data with the unique key
* ad_selection_config_id
*
diff --git a/adservices/service-core/java/com/android/adservices/data/consent/AppConsentDao.java b/adservices/service-core/java/com/android/adservices/data/consent/AppConsentDao.java
index c2eb8d6e3..f5ddbf071 100644
--- a/adservices/service-core/java/com/android/adservices/data/consent/AppConsentDao.java
+++ b/adservices/service-core/java/com/android/adservices/data/consent/AppConsentDao.java
@@ -31,6 +31,7 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Data access object for the App Consent datastore serving the Privacy Sandbox Consent Manager and
@@ -110,8 +111,17 @@ public class AppConsentDao {
initializeDatastoreIfNeeded();
Set<String> apps = new HashSet<>();
Set<String> datastoreKeys = mDatastore.keySetFalse();
+ Set<String> installedPackages =
+ mPackageManager
+ .getInstalledApplications(PackageManager.ApplicationInfoFlags.of(0))
+ .stream()
+ .map(applicationInfo -> applicationInfo.packageName)
+ .collect(Collectors.toSet());
for (String key : datastoreKeys) {
- apps.add(datastoreKeyToPackageName(key));
+ String packageName = datastoreKeyToPackageName(key);
+ if (installedPackages.contains(packageName)) {
+ apps.add(packageName);
+ }
}
return apps;
@@ -125,8 +135,17 @@ public class AppConsentDao {
initializeDatastoreIfNeeded();
Set<String> apps = new HashSet<>();
Set<String> datastoreKeys = mDatastore.keySetTrue();
+ Set<String> installedPackages =
+ mPackageManager
+ .getInstalledApplications(PackageManager.ApplicationInfoFlags.of(0))
+ .stream()
+ .map(applicationInfo -> applicationInfo.packageName)
+ .collect(Collectors.toSet());
for (String key : datastoreKeys) {
- apps.add(datastoreKeyToPackageName(key));
+ String packageName = datastoreKeyToPackageName(key);
+ if (installedPackages.contains(packageName)) {
+ apps.add(packageName);
+ }
}
return apps;
diff --git a/adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceDao.java b/adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceDao.java
index 76386da5f..5b9d1f825 100644
--- a/adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceDao.java
+++ b/adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceDao.java
@@ -17,6 +17,7 @@
package com.android.adservices.data.customaudience;
import android.adservices.common.AdTechIdentifier;
+import android.content.pm.PackageManager;
import android.net.Uri;
import androidx.annotation.NonNull;
@@ -27,12 +28,18 @@ import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.common.AllowLists;
import com.android.adservices.service.customaudience.CustomAudienceUpdatableData;
import com.android.internal.annotations.VisibleForTesting;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* DAO abstract class used to access Custom Audience persistent storage.
@@ -172,8 +179,14 @@ public abstract class CustomAudienceDao {
long customAudienceCount = getCustomAudienceCount();
long customAudienceCountPerOwner = getCustomAudienceCountForOwner(owner);
long ownerCount = getCustomAudienceOwnerCount();
- return new CustomAudienceStats(
- owner, customAudienceCount, customAudienceCountPerOwner, ownerCount);
+
+ // TODO(b/255780705): Add buyer and per-buyer stats
+ return CustomAudienceStats.builder()
+ .setOwner(owner)
+ .setTotalCustomAudienceCount(customAudienceCount)
+ .setPerOwnerCustomAudienceCount(customAudienceCountPerOwner)
+ .setTotalOwnerCount(ownerCount)
+ .build();
}
/**
@@ -317,6 +330,149 @@ public abstract class CustomAudienceDao {
return deleteAllExpiredCustomAudiences(expiryTime);
}
+ /** Returns the set of all unique owner apps in the custom audience table. */
+ @Query("SELECT DISTINCT owner FROM custom_audience")
+ public abstract List<String> getAllCustomAudienceOwners();
+
+ /**
+ * Deletes all custom audiences belonging to any app in the given set of {@code ownersToRemove}.
+ *
+ * <p>This method is not intended to be called on its own. Please use {@link
+ * #deleteAllDisallowedOwnerCustomAudienceData(PackageManager, Flags)} instead.
+ *
+ * @return the number of deleted custom audiences
+ */
+ @Query("DELETE FROM custom_audience WHERE owner IN (:ownersToRemove)")
+ protected abstract int deleteCustomAudiencesByOwner(@NonNull List<String> ownersToRemove);
+
+ /**
+ * Deletes all custom audience background fetch data belonging to any app in the given set of
+ * {@code ownersToRemove}.
+ *
+ * <p>This method is not intended to be called on its own. Please use {@link
+ * #deleteAllDisallowedOwnerCustomAudienceData(PackageManager, Flags)} instead.
+ */
+ @Query("DELETE FROM custom_audience_background_fetch_data WHERE owner IN (:ownersToRemove)")
+ protected abstract void deleteCustomAudienceBackgroundFetchDataByOwner(
+ @NonNull List<String> ownersToRemove);
+
+ /**
+ * Deletes all custom audience data belonging to disallowed owner apps in a single transaction,
+ * where the custom audiences' owner apps cannot be found in the installed list or where the
+ * owner apps are not found in the app allowlist.
+ *
+ * @return a {@link CustomAudienceStats} object containing only the number of deleted custom
+ * audiences and the number of disallowed owner apps found
+ */
+ @Transaction
+ @NonNull
+ public CustomAudienceStats deleteAllDisallowedOwnerCustomAudienceData(
+ @NonNull PackageManager packageManager, @NonNull Flags flags) {
+ Objects.requireNonNull(packageManager);
+ Objects.requireNonNull(flags);
+ List<String> ownersToRemove = getAllCustomAudienceOwners();
+
+ if (!ownersToRemove.isEmpty()) {
+ Set<String> allowedPackages =
+ packageManager
+ .getInstalledApplications(PackageManager.ApplicationInfoFlags.of(0))
+ .stream()
+ .map(applicationInfo -> applicationInfo.packageName)
+ .collect(Collectors.toSet());
+
+ String appAllowList = flags.getPpapiAppAllowList();
+ if (!AllowLists.doesAllowListAllowAll(appAllowList)) {
+ allowedPackages.retainAll(AllowLists.splitAllowList(appAllowList));
+ }
+
+ // Packages must be both installed and allowlisted, or else they should be removed
+ ownersToRemove.removeAll(allowedPackages);
+ }
+
+ long numDisallowedOwnersFound = ownersToRemove.size();
+ long numRemovedCustomAudiences = 0;
+ if (!ownersToRemove.isEmpty()) {
+ deleteCustomAudienceBackgroundFetchDataByOwner(ownersToRemove);
+ numRemovedCustomAudiences = deleteCustomAudiencesByOwner(ownersToRemove);
+ }
+
+ return CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(numRemovedCustomAudiences)
+ .setTotalOwnerCount(numDisallowedOwnersFound)
+ .build();
+ }
+
+ /** Returns the set of all unique buyer ad techs in the custom audience table. */
+ @Query("SELECT DISTINCT buyer FROM custom_audience")
+ public abstract List<AdTechIdentifier> getAllCustomAudienceBuyers();
+
+ /**
+ * Deletes all custom audiences belonging to any ad tech in the given set of {@code
+ * buyersToRemove}.
+ *
+ * <p>This method is not intended to be called on its own. Please use {@link
+ * #deleteAllDisallowedBuyerCustomAudienceData(EnrollmentDao, Flags)} instead.
+ *
+ * @return the number of deleted custom audiences
+ */
+ @Query("DELETE FROM custom_audience WHERE buyer IN (:buyersToRemove)")
+ protected abstract int deleteCustomAudiencesByBuyer(
+ @NonNull List<AdTechIdentifier> buyersToRemove);
+
+ /**
+ * Deletes all custom audience background fetch data belonging to any ad tech in the given set
+ * of {@code buyersToRemove}.
+ *
+ * <p>This method is not intended to be called on its own. Please use {@link
+ * #deleteAllDisallowedBuyerCustomAudienceData(EnrollmentDao, Flags)} instead.
+ */
+ @Query("DELETE FROM custom_audience_background_fetch_data WHERE buyer IN (:buyersToRemove)")
+ protected abstract void deleteCustomAudienceBackgroundFetchDataByBuyer(
+ @NonNull List<AdTechIdentifier> buyersToRemove);
+
+ /**
+ * Deletes all custom audience data belonging to disallowed buyer ad techs in a single
+ * transaction, where the custom audiences' buyer ad techs cannot be found in the enrollment
+ * database.
+ *
+ * @return a {@link CustomAudienceStats} object containing only the number of deleted custom
+ * audiences and the number of disallowed owner apps found
+ */
+ @Transaction
+ @NonNull
+ public CustomAudienceStats deleteAllDisallowedBuyerCustomAudienceData(
+ @NonNull EnrollmentDao enrollmentDao, @NonNull Flags flags) {
+ Objects.requireNonNull(enrollmentDao);
+ Objects.requireNonNull(flags);
+
+ if (flags.getDisableFledgeEnrollmentCheck()) {
+ LogUtil.d("FLEDGE enrollment check disabled; skipping enrolled buyer cleanup");
+ return CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(0)
+ .setTotalBuyerCount(0)
+ .build();
+ }
+
+ List<AdTechIdentifier> buyersToRemove = getAllCustomAudienceBuyers();
+
+ if (!buyersToRemove.isEmpty()) {
+ Set<AdTechIdentifier> allowedAdTechs = enrollmentDao.getAllFledgeEnrolledAdTechs();
+ buyersToRemove.removeAll(allowedAdTechs);
+ }
+
+ long numDisallowedBuyersFound = buyersToRemove.size();
+ long numRemovedCustomAudiences = 0;
+ if (!buyersToRemove.isEmpty()) {
+ deleteCustomAudienceBackgroundFetchDataByBuyer(buyersToRemove);
+ numRemovedCustomAudiences = deleteCustomAudiencesByBuyer(buyersToRemove);
+ }
+
+ return CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(numRemovedCustomAudiences)
+ .setTotalBuyerCount(numDisallowedBuyersFound)
+ .build();
+ }
+
/**
* Deletes ALL custom audiences from the table.
*
@@ -454,36 +610,4 @@ public abstract class CustomAudienceDao {
+ "AND :currentTime < ca.expiration_time")
public abstract int getNumActiveEligibleCustomAudienceBackgroundFetchData(
@NonNull Instant currentTime);
-
- /** Class represents custom audience stats query result. */
- public static class CustomAudienceStats {
- private final String mOwner;
- private final long mTotalCount;
- private final long mPerOwnerCount;
- private final long mOwnerCount;
-
- public CustomAudienceStats(
- String owner, long totalCount, long perOwnerCount, long ownerCount) {
- mOwner = owner;
- mTotalCount = totalCount;
- mPerOwnerCount = perOwnerCount;
- mOwnerCount = ownerCount;
- }
-
- public String getOwner() {
- return mOwner;
- }
-
- public long getTotalCount() {
- return mTotalCount;
- }
-
- public long getPerOwnerCount() {
- return mPerOwnerCount;
- }
-
- public long getOwnerCount() {
- return mOwnerCount;
- }
- }
}
diff --git a/adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceStats.java b/adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceStats.java
new file mode 100644
index 000000000..8d4fd2ec8
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/data/customaudience/CustomAudienceStats.java
@@ -0,0 +1,124 @@
+/*
+ * 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.data.customaudience;
+
+import android.adservices.common.AdTechIdentifier;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+/** This class represents the query result for custom audience stats. */
+@AutoValue
+public abstract class CustomAudienceStats {
+ public static final long UNSET_COUNT = -1;
+
+ /** @return the queried owner app's package name, or {@code null} if not set */
+ @Nullable
+ public abstract String getOwner();
+
+ /** @return the queried buyer ad tech's {@link AdTechIdentifier}, or {@code null} if not set */
+ @Nullable
+ public abstract AdTechIdentifier getBuyer();
+
+ /**
+ * @return the total number of custom audiences in the database, or {@link #UNSET_COUNT} if not
+ * set
+ */
+ public abstract long getTotalCustomAudienceCount();
+
+ /**
+ * @return the number of custom audiences in the database owned by the queried owner app, or
+ * {@link #UNSET_COUNT} if not set
+ */
+ public abstract long getPerOwnerCustomAudienceCount();
+
+ /**
+ * @return the total number of distinct owner apps in the database, or {@link #UNSET_COUNT} if
+ * not set
+ */
+ public abstract long getTotalOwnerCount();
+
+ /**
+ * @return the number of custom audiences in the database associated with the queried buyer ad
+ * tech, or {@link #UNSET_COUNT} if not set
+ */
+ public abstract long getPerBuyerCustomAudienceCount();
+
+ /**
+ * @return the total number of distinct buyer ad techs in the database, or {@link #UNSET_COUNT}
+ * if not set
+ */
+ public abstract long getTotalBuyerCount();
+
+ /** @return a {@link Builder} for a {@link CustomAudienceStats} object */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_CustomAudienceStats.Builder()
+ .setTotalCustomAudienceCount(UNSET_COUNT)
+ .setPerOwnerCustomAudienceCount(UNSET_COUNT)
+ .setTotalOwnerCount(UNSET_COUNT)
+ .setPerBuyerCustomAudienceCount(UNSET_COUNT)
+ .setTotalBuyerCount(UNSET_COUNT);
+ }
+
+ /** Builder class for a {@link CustomAudienceStats} object. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the owner app's package name which was queried on to create the {@link
+ * CustomAudienceStats} object.
+ */
+ @NonNull
+ public abstract Builder setOwner(@Nullable String value);
+
+ /**
+ * Sets the buyer ad tech's {@link AdTechIdentifier} which was queried to create the {@link
+ * CustomAudienceStats} object.
+ */
+ @NonNull
+ public abstract Builder setBuyer(@Nullable AdTechIdentifier value);
+
+ /** Sets the total number of custom audiences in the database. */
+ @NonNull
+ public abstract Builder setTotalCustomAudienceCount(long value);
+
+ /** Sets the number of custom audiences in the database owned by the queried owner. */
+ @NonNull
+ public abstract Builder setPerOwnerCustomAudienceCount(long value);
+
+ /** Sets the total number of distinct owner apps in the database. */
+ @NonNull
+ public abstract Builder setTotalOwnerCount(long value);
+
+ /**
+ * Sets the number of custom audiences in the database associated with the queried buyer ad
+ * tech.
+ */
+ @NonNull
+ public abstract Builder setPerBuyerCustomAudienceCount(long value);
+
+ /** Sets the total number of distinct buyer ad techs in the database. */
+ @NonNull
+ public abstract Builder setTotalBuyerCount(long value);
+
+ /** Builds the {@link CustomAudienceStats} object and returns it. */
+ @NonNull
+ public abstract CustomAudienceStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/data/enrollment/EnrollmentDao.java b/adservices/service-core/java/com/android/adservices/data/enrollment/EnrollmentDao.java
index bd1df46bf..602a856d9 100644
--- a/adservices/service-core/java/com/android/adservices/data/enrollment/EnrollmentDao.java
+++ b/adservices/service-core/java/com/android/adservices/data/enrollment/EnrollmentDao.java
@@ -17,23 +17,30 @@
package com.android.adservices.data.enrollment;
import android.adservices.common.AdTechIdentifier;
-import android.annotation.NonNull;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.adservices.LogUtil;
import com.android.adservices.data.DbHelper;
import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.util.Web;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
/** Data Access Object for the EnrollmentData. */
public class EnrollmentDao implements IEnrollmentDao {
@@ -124,24 +131,29 @@ public class EnrollmentDao implements IEnrollmentDao {
@Override
@Nullable
- public EnrollmentData getEnrollmentDataFromMeasurementUrl(String url) {
+ public EnrollmentData getEnrollmentDataFromMeasurementUrl(Uri url) {
+ Optional<Uri> registrationBaseUri = Web.topPrivateDomainSchemeAndPath(url);
SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
- if (db == null) {
+ if (!registrationBaseUri.isPresent() || db == null) {
return null;
}
+
+ String selectionQuery =
+ getAttributionUrlSelection(
+ EnrollmentTables.EnrollmentDataContract
+ .ATTRIBUTION_SOURCE_REGISTRATION_URL,
+ registrationBaseUri.get())
+ + " OR "
+ + getAttributionUrlSelection(
+ EnrollmentTables.EnrollmentDataContract
+ .ATTRIBUTION_TRIGGER_REGISTRATION_URL,
+ registrationBaseUri.get());
+
try (Cursor cursor =
db.query(
EnrollmentTables.EnrollmentDataContract.TABLE,
/*columns=*/ null,
- EnrollmentTables.EnrollmentDataContract.ATTRIBUTION_SOURCE_REGISTRATION_URL
- + " LIKE '%"
- + url
- + "%' OR "
- + EnrollmentTables.EnrollmentDataContract
- .ATTRIBUTION_TRIGGER_REGISTRATION_URL
- + " LIKE '%"
- + url
- + "%'",
+ selectionQuery,
null,
/*groupBy=*/ null,
/*having=*/ null,
@@ -151,9 +163,43 @@ public class EnrollmentDao implements IEnrollmentDao {
LogUtil.d("Failed to match enrollment for url \"%s\"", url);
return null;
}
- cursor.moveToNext();
- return SqliteObjectMapper.constructEnrollmentDataFromCursor(cursor);
+
+ while (cursor.moveToNext()) {
+ EnrollmentData data = SqliteObjectMapper.constructEnrollmentDataFromCursor(cursor);
+ if (validateAttributionUrl(
+ data.getAttributionSourceRegistrationUrl(), registrationBaseUri)
+ || validateAttributionUrl(
+ data.getAttributionTriggerRegistrationUrl(), registrationBaseUri)) {
+ return data;
+ }
+ }
+ return null;
+ }
+ }
+
+ private boolean validateAttributionUrl(
+ List<String> enrolledUris, Optional<Uri> registrationBaseUri) {
+ for (String uri : enrolledUris) {
+ Optional<Uri> enrolledBaseUri = Web.topPrivateDomainSchemeAndPath(Uri.parse(uri));
+ if (registrationBaseUri.equals(enrolledBaseUri)) {
+ return true;
+ }
}
+ return false;
+ }
+
+ private String getAttributionUrlSelection(String field, Uri baseUri) {
+ return String.format(
+ Locale.ENGLISH,
+ "(%1$s LIKE %2$s OR %1$s LIKE %3$s)",
+ field,
+ DatabaseUtils.sqlEscapeString("%" + baseUri.toString() + "%"),
+ DatabaseUtils.sqlEscapeString(
+ baseUri.getScheme()
+ + "://%."
+ + baseUri.getEncodedAuthority()
+ + baseUri.getPath()
+ + "%"));
}
@Override
@@ -220,6 +266,52 @@ public class EnrollmentDao implements IEnrollmentDao {
}
@Override
+ @NonNull
+ public Set<AdTechIdentifier> getAllFledgeEnrolledAdTechs() {
+ Set<AdTechIdentifier> enrolledAdTechIdentifiers = new HashSet<>();
+
+ SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
+ if (db == null) {
+ return enrolledAdTechIdentifiers;
+ }
+
+ try (Cursor cursor =
+ db.query(
+ /*distinct=*/ true,
+ /*table=*/ EnrollmentTables.EnrollmentDataContract.TABLE,
+ /*columns=*/ new String[] {
+ EnrollmentTables.EnrollmentDataContract
+ .REMARKETING_RESPONSE_BASED_REGISTRATION_URL
+ },
+ /*selection=*/ EnrollmentTables.EnrollmentDataContract
+ .REMARKETING_RESPONSE_BASED_REGISTRATION_URL
+ + " IS NOT NULL",
+ /*selectionArgs=*/ null,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null,
+ /*limit=*/ null)) {
+ if (cursor == null || cursor.getCount() <= 0) {
+ LogUtil.d("Failed to find any FLEDGE-enrolled ad techs");
+ return enrolledAdTechIdentifiers;
+ }
+
+ LogUtil.v("Found %d FLEDGE enrollment entries", cursor.getCount());
+
+ while (cursor.moveToNext()) {
+ enrolledAdTechIdentifiers.addAll(
+ SqliteObjectMapper.getAdTechIdentifiersFromFledgeCursor(cursor));
+ }
+
+ LogUtil.v(
+ "Found %d FLEDGE enrolled ad tech identifiers",
+ enrolledAdTechIdentifiers.size());
+
+ return enrolledAdTechIdentifiers;
+ }
+ }
+
+ @Override
@Nullable
public EnrollmentData getEnrollmentDataFromSdkName(String sdkName) {
if (sdkName.contains(" ") || sdkName.contains(",")) {
diff --git a/adservices/service-core/java/com/android/adservices/data/enrollment/IEnrollmentDao.java b/adservices/service-core/java/com/android/adservices/data/enrollment/IEnrollmentDao.java
index 684b1d914..91454720f 100644
--- a/adservices/service-core/java/com/android/adservices/data/enrollment/IEnrollmentDao.java
+++ b/adservices/service-core/java/com/android/adservices/data/enrollment/IEnrollmentDao.java
@@ -17,9 +17,12 @@
package com.android.adservices.data.enrollment;
import android.adservices.common.AdTechIdentifier;
+import android.net.Uri;
import com.android.adservices.service.enrollment.EnrollmentData;
+import java.util.Set;
+
/** Interface for enrollment related data access operations. */
public interface IEnrollmentDao {
@@ -37,7 +40,7 @@ public interface IEnrollmentDao {
* @param url could be source registration url or trigger registration url.
* @return the EnrollmentData; Null in case of SQL failure.
*/
- EnrollmentData getEnrollmentDataFromMeasurementUrl(String url);
+ EnrollmentData getEnrollmentDataFromMeasurementUrl(Uri url);
/**
* Returns the {@link EnrollmentData} with FLEDGE response-based registration URLs that match
@@ -52,6 +55,14 @@ public interface IEnrollmentDao {
EnrollmentData getEnrollmentDataForFledgeByAdTechIdentifier(AdTechIdentifier adTechIdentifier);
/**
+ * Returns a set of {@link AdTechIdentifier} objects for all ad techs enrolled with FLEDGE.
+ *
+ * @return a set of all enrolled ad techs' {@link AdTechIdentifier} if they enrolled in FLEDGE;
+ * empty if none found
+ */
+ Set<AdTechIdentifier> getAllFledgeEnrolledAdTechs();
+
+ /**
* Returns the {@link EnrollmentData} given AdTech SDK Name.
*
* @param sdkName List of SDKs belonging to the same enrollment.
diff --git a/adservices/service-core/java/com/android/adservices/data/enrollment/SqliteObjectMapper.java b/adservices/service-core/java/com/android/adservices/data/enrollment/SqliteObjectMapper.java
index dc85ea567..a6dd4c383 100644
--- a/adservices/service-core/java/com/android/adservices/data/enrollment/SqliteObjectMapper.java
+++ b/adservices/service-core/java/com/android/adservices/data/enrollment/SqliteObjectMapper.java
@@ -16,10 +16,15 @@
package com.android.adservices.data.enrollment;
+import android.adservices.common.AdTechIdentifier;
import android.database.Cursor;
+import android.net.Uri;
+import com.android.adservices.LogUtil;
import com.android.adservices.service.enrollment.EnrollmentData;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Function;
/** Helper class for SQLite operations. */
@@ -58,6 +63,36 @@ public class SqliteObjectMapper {
return builder.build();
}
+ /**
+ * Transforms the FLEDGE RBR URLs found at the given {@code cursor} to a list of {@link
+ * AdTechIdentifier} objects.
+ */
+ public static List<AdTechIdentifier> getAdTechIdentifiersFromFledgeCursor(Cursor cursor) {
+ List<AdTechIdentifier> enrolledFledgeAdTechIdentifiers = new ArrayList<>();
+ List<String> fledgeRbrUrls = new ArrayList<>();
+
+ setTextColumn(
+ cursor,
+ EnrollmentTables.EnrollmentDataContract.REMARKETING_RESPONSE_BASED_REGISTRATION_URL,
+ input -> {
+ fledgeRbrUrls.addAll(EnrollmentData.splitEnrollmentInputToList(input));
+ return null;
+ });
+
+ for (String fledgeRbrUrl : fledgeRbrUrls) {
+ try {
+ if (fledgeRbrUrl != null && !fledgeRbrUrl.trim().isEmpty()) {
+ enrolledFledgeAdTechIdentifiers.add(
+ AdTechIdentifier.fromString(Uri.parse(fledgeRbrUrl).getHost()));
+ }
+ } catch (Exception exception) {
+ LogUtil.d(exception, "Failure parsing RBR URL \"%s\"; skipping", fledgeRbrUrl);
+ }
+ }
+
+ return enrolledFledgeAdTechIdentifiers;
+ }
+
private static <BuilderType> void setTextColumn(
Cursor cursor, String column, Function<String, BuilderType> setter) {
setColumnValue(cursor, column, cursor::getString, setter);
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/DatastoreManager.java b/adservices/service-core/java/com/android/adservices/data/measurement/DatastoreManager.java
index a652f7264..10b09d3c9 100644
--- a/adservices/service-core/java/com/android/adservices/data/measurement/DatastoreManager.java
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/DatastoreManager.java
@@ -85,7 +85,7 @@ public abstract class DatastoreManager {
Optional<T> result;
try {
- result = Optional.of(execute.apply(measurementDao));
+ result = Optional.ofNullable(execute.apply(measurementDao));
} catch (DatastoreException ex) {
result = Optional.empty();
LogUtil.e(ex, "DatastoreException thrown during transaction");
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/IMeasurementDao.java b/adservices/service-core/java/com/android/adservices/data/measurement/IMeasurementDao.java
index 36a60adaf..697d10f01 100644
--- a/adservices/service-core/java/com/android/adservices/data/measurement/IMeasurementDao.java
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/IMeasurementDao.java
@@ -22,6 +22,7 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.adservices.service.measurement.AsyncRegistration;
import com.android.adservices.service.measurement.Attribution;
import com.android.adservices.service.measurement.EventReport;
import com.android.adservices.service.measurement.EventSurfaceType;
@@ -51,6 +52,14 @@ public interface IMeasurementDao {
List<String> getPendingTriggerIds() throws DatastoreException;
/**
+ * Queries and returns the {@link Source}.
+ *
+ * @param sourceId ID of the requested Source
+ * @return the requested Source
+ */
+ Source getSource(@NonNull String sourceId) throws DatastoreException;
+
+ /**
* Queries and returns the {@link Trigger}.
*
* @param triggerId Id of the request Trigger
@@ -59,9 +68,36 @@ public interface IMeasurementDao {
Trigger getTrigger(String triggerId) throws DatastoreException;
/**
- * Gets the number of sources a registrant has registered.
+ * Fetches the count of aggregate reports for the provided destination.
+ *
+ * @param attributionDestination Uri for the destination
+ * @param destinationType DestinationType App/Web
+ * @return number of aggregate reports in the database attributed to the provided destination
+ */
+ int getNumAggregateReportsPerDestination(
+ @NonNull Uri attributionDestination, @EventSurfaceType int destinationType)
+ throws DatastoreException;
+
+ /**
+ * Fetches the count of event reports for the provided destination.
+ *
+ * @param attributionDestination Uri for the destination
+ * @param destinationType DestinationType App/Web
+ * @return number of event reports in the database attributed to the provided destination
+ */
+ int getNumEventReportsPerDestination(
+ @NonNull Uri attributionDestination, @EventSurfaceType int destinationType)
+ throws DatastoreException;
+
+ /**
+ * Gets the number of sources associated to a publisher.
+ *
+ * @param publisherUri Uri for the publisher
+ * @param publisherType PublisherType App/Web
+ * @return Number of sources registered for the given publisher
*/
- long getNumSourcesPerRegistrant(Uri registrant) throws DatastoreException;
+ long getNumSourcesPerPublisher(Uri publisherUri, @EventSurfaceType int publisherType)
+ throws DatastoreException;
/**
* Gets the number of triggers a registrant has registered.
@@ -93,10 +129,15 @@ public interface IMeasurementDao {
@EventSurfaceType int publisherType, Uri destination, String enrollmentId,
long windowStartTime, long windowEndTime) throws DatastoreException;
- /**
+ /**
* Updates the {@link Trigger.Status} value for the provided {@link Trigger}.
+ *
+ * @param triggerIds trigger to update
+ * @param status status to apply
+ * @throws DatastoreException database transaction related issues
*/
- void updateTriggerStatus(Trigger trigger) throws DatastoreException;
+ void updateTriggerStatus(@NonNull List<String> triggerIds, @Trigger.Status int status)
+ throws DatastoreException;
/**
* Add an entry to the Source datastore.
@@ -113,10 +154,10 @@ public interface IMeasurementDao {
/**
* Updates the {@link Source.Status} value for the provided list of {@link Source}
*
- * @param sources list of sources.
- * @param status value to be set
+ * @param sourceIds list of sources.
+ * @param status value to be set
*/
- void updateSourceStatus(List<Source> sources, @Source.Status int status)
+ void updateSourceStatus(@NonNull List<String> sourceIds, @Source.Status int status)
throws DatastoreException;
/**
@@ -124,14 +165,14 @@ public interface IMeasurementDao {
*
* @param source the {@link Source} object.
*/
- void updateSourceDedupKeys(Source source) throws DatastoreException;
+ void updateSourceDedupKeys(@NonNull Source source) throws DatastoreException;
/**
* Updates the value of aggregate contributions for the corresponding {@link Source}
*
* @param source the {@link Source} object.
*/
- void updateSourceAggregateContributions(Source source) throws DatastoreException;
+ void updateSourceAggregateContributions(@NonNull Source source) throws DatastoreException;
/**
* Returns list of all the reports associated with the {@link Source}.
@@ -163,15 +204,33 @@ public interface IMeasurementDao {
* Change the status of an event report to DELIVERED
*
* @param eventReportId the id of the event report to be updated
+ * @param status status of the event report
*/
- void markEventReportDelivered(String eventReportId) throws DatastoreException;
+ void markEventReportStatus(String eventReportId, @EventReport.Status int status)
+ throws DatastoreException;
+
+ /**
+ * Change the status of an event debug report to DELIVERED
+ *
+ * @param eventReportId the id of the event report to be updated
+ */
+ void markEventDebugReportDelivered(String eventReportId) throws DatastoreException;
/**
* Change the status of an aggregate report to DELIVERED
*
* @param aggregateReportId the id of the event report to be updated
+ * @param status new status to set
*/
- void markAggregateReportDelivered(String aggregateReportId) throws DatastoreException;
+ void markAggregateReportStatus(String aggregateReportId, @AggregateReport.Status int status)
+ throws DatastoreException;
+
+ /**
+ * Change the status of an aggregate debug report to DELIVERED
+ *
+ * @param aggregateReportId the id of the event report to be updated
+ */
+ void markAggregateDebugReportDelivered(String aggregateReportId) throws DatastoreException;
/**
* Saves the {@link EventReport} to datastore.
@@ -189,6 +248,9 @@ public interface IMeasurementDao {
List<String> getPendingEventReportIdsInWindow(long windowStartTime, long windowEndTime)
throws DatastoreException;
+ /** Returns list of all debug event reports. */
+ List<String> getPendingDebugEventReportIds() throws DatastoreException;
+
/**
* Returns list of all pending event reports for a given app right away.
*/
@@ -213,34 +275,10 @@ public interface IMeasurementDao {
*/
void deleteAppRecords(Uri uri) throws DatastoreException;
- /**
- * Deletes all expired records in measurement tables.
- */
+ /** Deletes all expired records in measurement tables. */
void deleteExpiredRecords() throws DatastoreException;
/**
- * Deletes all measurement data owned by a registrant and optionally providing an origin uri
- * and/or a range of dates.
- *
- * @param registrant who owns the data
- * @param start time for deletion range. Set to Instant.MIN to delete everything up to the end
- * @param end time for deletion range. Set to Instant.MAX to delete everything after the start
- * @param origins list of origins which should be used for matching
- * @param domains list of domains which should be used for matching
- * @param matchBehavior {@link DeletionRequest.MatchBehavior} to be used for matching
- * @param deletionMode {@link DeletionRequest.DeletionMode} for selecting data to be deleted
- */
- void deleteMeasurementData(
- @NonNull Uri registrant,
- @NonNull Instant start,
- @NonNull Instant end,
- @NonNull List<Uri> origins,
- @NonNull List<Uri> domains,
- @DeletionRequest.MatchBehavior int matchBehavior,
- @DeletionRequest.DeletionMode int deletionMode)
- throws DatastoreException;
-
- /**
* Mark relevant source as install attributed.
*
* @param uri package identifier
@@ -285,6 +323,9 @@ public interface IMeasurementDao {
List<String> getPendingAggregateReportIdsInWindow(long windowStartTime, long windowEndTime)
throws DatastoreException;
+ /** Returns list of all aggregate debug reports. */
+ List<String> getPendingAggregateDebugReportIds() throws DatastoreException;
+
/**
* Returns list of all pending aggregate reports for a given app right away.
*/
@@ -297,4 +338,142 @@ public interface IMeasurementDao {
* delete every table.
*/
void deleteAllMeasurementData(List<String> tablesToExclude) throws DatastoreException;
+
+ /**
+ * Delete records from source table that match provided source IDs.
+ *
+ * @param sourceIds source IDs to match
+ * @throws DatastoreException database transaction issues
+ */
+ void deleteSources(@NonNull List<String> sourceIds) throws DatastoreException;
+
+ /**
+ * Delete records from source table that match provided trigger IDs.
+ *
+ * @param triggerIds trigger IDs to match
+ * @throws DatastoreException database transaction issues
+ */
+ void deleteTriggers(@NonNull List<String> triggerIds) throws DatastoreException;
+
+ /**
+ * Insert a record into the Async Registration Table.
+ *
+ * @param asyncRegistration a {@link AsyncRegistration} to insert into the Async Registration
+ * table
+ */
+ void insertAsyncRegistration(@NonNull AsyncRegistration asyncRegistration)
+ throws DatastoreException;
+
+ /**
+ * Delete a record from the AsyncRegistration table.
+ *
+ * @param id a {@link String} id of the record to delete from the AsyncRegistration table.
+ */
+ void deleteAsyncRegistration(@NonNull String id) throws DatastoreException;
+
+ /**
+ * Get the record with the earliest request time and a valid retry count.
+ *
+ * @param retryLimit a long that is used for determining the next valid record to be serviced
+ * @param failedAdTechEnrollmentIds a String that contains the Ids of records that have been
+ * serviced during the current run
+ */
+ AsyncRegistration fetchNextQueuedAsyncRegistration(
+ short retryLimit, List<String> failedAdTechEnrollmentIds) throws DatastoreException;
+
+ /**
+ * Update the retry count for a record in the Async Registration table.
+ *
+ * @param asyncRegistration a {@link AsyncRegistration} for which the retryCount will be updated
+ */
+ void updateRetryCount(@NonNull AsyncRegistration asyncRegistration) throws DatastoreException;
+
+ /**
+ * Deletes all records in measurement tables that correspond with a Uri not in the provided
+ * list.
+ *
+ * @param uriList a {@link List} of Uris whos related records won't be deleted.
+ * @throws DatastoreException
+ */
+ void deleteAppRecordsNotPresent(List<Uri> uriList) throws DatastoreException;
+
+ /**
+ * Fetches aggregate reports that match either given source or trigger IDs. If A1 is set of
+ * aggregate reports that match any of sourceIds and A2 is set of aggregate reports that match
+ * any of triggerIds, then we delete (A1 U A2).
+ *
+ * @param sourceIds sources to be matched with aggregate reports
+ * @param triggerIds triggers to be matched with aggregate reports
+ */
+ List<AggregateReport> fetchMatchingAggregateReports(
+ @NonNull List<String> sourceIds, @NonNull List<String> triggerIds)
+ throws DatastoreException;
+
+ /**
+ * Fetches event reports that match either given source or trigger IDs. If A1 is set of event
+ * reports that match any of sourceIds and A2 is set of event reports that match any of
+ * triggerIds, then we delete (A1 U A2).
+ *
+ * @param sourceIds sources to be matched with event reports
+ * @param triggerIds triggers to be matched with event reports
+ */
+ List<EventReport> fetchMatchingEventReports(
+ @NonNull List<String> sourceIds, @NonNull List<String> triggerIds)
+ throws DatastoreException;
+
+ /**
+ * Returns list of sources matching registrant, publishers and also in the provided time frame.
+ * It matches registrant and time range (start & end) irrespective of the {@code matchBehavior}.
+ * In the resulting set, if matchBehavior is {@link
+ * android.adservices.measurement.DeletionRequest.MatchBehavior#MATCH_BEHAVIOR_DELETE}, then it
+ * matches origins and domains. In case of {@link
+ * android.adservices.measurement.DeletionRequest.MatchBehavior#MATCH_BEHAVIOR_PRESERVE}, it
+ * returns the records that don't match origins or domain.
+ *
+ * @param registrant registrant to match
+ * @param start event time should be after this instant (inclusive)
+ * @param end event time should be after this instant (inclusive)
+ * @param origins publisher site match
+ * @param domains publisher top level domain matches
+ * @param matchBehavior indicates whether to return matching or inversely matching (everything
+ * except matching) data
+ * @return list of source IDs
+ * @throws DatastoreException database transaction level issues
+ */
+ List<String> fetchMatchingSources(
+ @NonNull Uri registrant,
+ @NonNull Instant start,
+ @NonNull Instant end,
+ @NonNull List<Uri> origins,
+ @NonNull List<Uri> domains,
+ @DeletionRequest.MatchBehavior int matchBehavior)
+ throws DatastoreException;
+
+ /**
+ * Returns list of triggers matching registrant, publishers and also in the provided time frame.
+ * It matches registrant and time range (start & end) irrespective of the {@code matchBehavior}.
+ * In the resulting set, if matchBehavior is {@link
+ * android.adservices.measurement.DeletionRequest.MatchBehavior#MATCH_BEHAVIOR_DELETE}, then it
+ * matches origins and domains. In case of {@link
+ * android.adservices.measurement.DeletionRequest.MatchBehavior#MATCH_BEHAVIOR_PRESERVE}, it
+ * returns the records that don't match origins or domain.
+ *
+ * @param registrant registrant to match
+ * @param start trigger time should be after this instant (inclusive)
+ * @param end trigger time should be after this instant (inclusive)
+ * @param origins destination site match
+ * @param domains destination top level domain matches
+ * @param matchBehavior indicates whether to return matching or inversely matching (everything
+ * except matching) data
+ * @return list of trigger IDs
+ * @throws DatastoreException database transaction level issues
+ */
+ List<String> fetchMatchingTriggers(
+ @NonNull Uri registrant,
+ @NonNull Instant start,
+ @NonNull Instant end,
+ @NonNull List<Uri> origins,
+ @NonNull List<Uri> domains,
+ @DeletionRequest.MatchBehavior int matchBehavior)
+ throws DatastoreException;
}
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/MeasurementDao.java b/adservices/service-core/java/com/android/adservices/data/measurement/MeasurementDao.java
index 1e46d803b..f4f6d5b3f 100644
--- a/adservices/service-core/java/com/android/adservices/data/measurement/MeasurementDao.java
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/MeasurementDao.java
@@ -23,7 +23,6 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.util.Pair;
@@ -31,6 +30,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.adservices.LogUtil;
+import com.android.adservices.service.measurement.AsyncRegistration;
import com.android.adservices.service.measurement.Attribution;
import com.android.adservices.service.measurement.EventReport;
import com.android.adservices.service.measurement.EventSurfaceType;
@@ -40,8 +40,11 @@ import com.android.adservices.service.measurement.Trigger;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
import com.android.adservices.service.measurement.aggregation.AggregateReport;
import com.android.adservices.service.measurement.util.BaseUriExtractor;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import com.android.adservices.service.measurement.util.Web;
+import com.google.common.collect.ImmutableList;
+
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@@ -101,7 +104,9 @@ class MeasurementDao implements IMeasurementDao {
values.put(MeasurementTables.TriggerContract.AGGREGATE_VALUES,
trigger.getAggregateValues());
values.put(MeasurementTables.TriggerContract.FILTERS, trigger.getFilters());
- values.put(MeasurementTables.TriggerContract.DEBUG_KEY, trigger.getDebugKey());
+ values.put(MeasurementTables.TriggerContract.NOT_FILTERS, trigger.getNotFilters());
+ values.put(MeasurementTables.TriggerContract.DEBUG_KEY,
+ getNullableUnsignedLong(trigger.getDebugKey()));
long rowId = mSQLTransaction.getDatabase()
.insert(MeasurementTables.TriggerContract.TABLE,
/*nullColumnHack=*/null, values);
@@ -129,6 +134,28 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
+ public Source getSource(@NonNull String sourceId) throws DatastoreException {
+ try (Cursor cursor =
+ mSQLTransaction
+ .getDatabase()
+ .query(
+ MeasurementTables.SourceContract.TABLE,
+ /*columns=*/ null,
+ MeasurementTables.SourceContract.ID + " = ? ",
+ new String[] {sourceId},
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null,
+ /*limit=*/ null)) {
+ if (cursor.getCount() == 0) {
+ throw new DatastoreException("Source retrieval failed. Id: " + sourceId);
+ }
+ cursor.moveToNext();
+ return SqliteObjectMapper.constructSourceFromCursor(cursor);
+ }
+ }
+
+ @Override
public Trigger getTrigger(@NonNull String triggerId) throws DatastoreException {
try (Cursor cursor = mSQLTransaction.getDatabase().query(
MeasurementTables.TriggerContract.TABLE,
@@ -145,6 +172,28 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
+ public int getNumAggregateReportsPerDestination(
+ @NonNull Uri attributionDestination, @EventSurfaceType int destinationType)
+ throws DatastoreException {
+ return getNumReportsPerDestination(
+ MeasurementTables.AggregateReport.TABLE,
+ MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
+ attributionDestination,
+ destinationType);
+ }
+
+ @Override
+ public int getNumEventReportsPerDestination(
+ @NonNull Uri attributionDestination, @EventSurfaceType int destinationType)
+ throws DatastoreException {
+ return getNumReportsPerDestination(
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
+ attributionDestination,
+ destinationType);
+ }
+
+ @Override
public EventReport getEventReport(@NonNull String eventReportId) throws DatastoreException {
try (Cursor cursor = mSQLTransaction.getDatabase().query(
MeasurementTables.EventReportContract.TABLE,
@@ -194,7 +243,7 @@ class MeasurementDao implements IMeasurementDao {
ContentValues values = new ContentValues();
values.put(MeasurementTables.SourceContract.ID, UUID.randomUUID().toString());
- values.put(MeasurementTables.SourceContract.EVENT_ID, source.getEventId());
+ values.put(MeasurementTables.SourceContract.EVENT_ID, source.getEventId().getValue());
values.put(MeasurementTables.SourceContract.PUBLISHER, source.getPublisher().toString());
values.put(MeasurementTables.SourceContract.PUBLISHER_TYPE, source.getPublisherType());
values.put(
@@ -216,9 +265,10 @@ class MeasurementDao implements IMeasurementDao {
source.getInstallCooldownWindow());
values.put(MeasurementTables.SourceContract.ATTRIBUTION_MODE, source.getAttributionMode());
values.put(MeasurementTables.SourceContract.AGGREGATE_SOURCE, source.getAggregateSource());
- values.put(MeasurementTables.SourceContract.FILTER_DATA, source.getAggregateFilterData());
+ values.put(MeasurementTables.SourceContract.FILTER_DATA, source.getFilterData());
values.put(MeasurementTables.SourceContract.AGGREGATE_CONTRIBUTIONS, 0);
- values.put(MeasurementTables.SourceContract.DEBUG_KEY, source.getDebugKey());
+ values.put(MeasurementTables.SourceContract.DEBUG_KEY,
+ getNullableUnsignedLong(source.getDebugKey()));
long rowId = mSQLTransaction.getDatabase()
.insert(MeasurementTables.SourceContract.TABLE,
/*nullColumnHack=*/null, values);
@@ -252,23 +302,18 @@ class MeasurementDao implements IMeasurementDao {
+ " = ? AND "
+ MeasurementTables.SourceContract.ENROLLMENT_ID
+ " = ? AND "
- // EventTime should be strictly less than TriggerTime as it
- // is highly
- // unlikely for matching Source and Trigger to happen at
- // same instant
- // in milliseconds.
+ MeasurementTables.SourceContract.EVENT_TIME
- + " < ? AND "
+ + " <= ? AND "
+ MeasurementTables.SourceContract.EXPIRY_TIME
- + " >= ? AND "
+ + " > ? AND "
+ MeasurementTables.SourceContract.STATUS
- + " != ?",
+ + " = ?",
new String[] {
triggerDestinationValue,
trigger.getEnrollmentId(),
String.valueOf(trigger.getTriggerTime()),
String.valueOf(trigger.getTriggerTime()),
- String.valueOf(Source.Status.IGNORED)
+ String.valueOf(Source.Status.ACTIVE)
},
/*groupBy=*/ null,
/*having=*/ null,
@@ -282,37 +327,54 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
- public void updateTriggerStatus(Trigger trigger) throws DatastoreException {
+ public void updateTriggerStatus(List<String> triggerIds, @Trigger.Status int status)
+ throws DatastoreException {
ContentValues values = new ContentValues();
- values.put(MeasurementTables.TriggerContract.STATUS, trigger.getStatus());
- long rows = mSQLTransaction.getDatabase()
- .update(MeasurementTables.TriggerContract.TABLE, values,
- MeasurementTables.TriggerContract.ID + " = ?",
- new String[]{trigger.getId()});
- if (rows != 1) {
+ values.put(MeasurementTables.TriggerContract.STATUS, status);
+ long rows =
+ mSQLTransaction
+ .getDatabase()
+ .update(
+ MeasurementTables.TriggerContract.TABLE,
+ values,
+ MeasurementTables.TriggerContract.ID
+ + " IN ("
+ + Stream.generate(() -> "?")
+ .limit(triggerIds.size())
+ .collect(Collectors.joining(","))
+ + ")",
+ triggerIds.toArray(new String[0]));
+ if (rows != triggerIds.size()) {
throw new DatastoreException("Trigger status update failed.");
}
}
@Override
- public void updateSourceStatus(List<Source> sources, @Source.Status int status)
+ public void updateSourceStatus(@NonNull List<String> sourceIds, @Source.Status int status)
throws DatastoreException {
ContentValues values = new ContentValues();
values.put(MeasurementTables.SourceContract.STATUS, status);
- long rows = mSQLTransaction.getDatabase()
- .update(MeasurementTables.SourceContract.TABLE, values,
- MeasurementTables.SourceContract.ID + " IN ("
- + Stream.generate(() -> "?").limit(sources.size())
- .collect(Collectors.joining(",")) + ")",
- sources.stream().map(Source::getId).toArray(String[]::new)
- );
- if (rows != sources.size()) {
+ long rows =
+ mSQLTransaction
+ .getDatabase()
+ .update(
+ MeasurementTables.SourceContract.TABLE,
+ values,
+ MeasurementTables.SourceContract.ID
+ + " IN ("
+ + Stream.generate(() -> "?")
+ .limit(sourceIds.size())
+ .collect(Collectors.joining(","))
+ + ")",
+ sourceIds.toArray(new String[0]));
+ if (rows != sourceIds.size()) {
throw new DatastoreException("Source status update failed.");
}
}
@Override
- public void updateSourceAggregateContributions(Source source) throws DatastoreException {
+ public void updateSourceAggregateContributions(@NonNull Source source)
+ throws DatastoreException {
ContentValues values = new ContentValues();
values.put(MeasurementTables.SourceContract.AGGREGATE_CONTRIBUTIONS,
source.getAggregateContributions());
@@ -326,9 +388,10 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
- public void markEventReportDelivered(String eventReportId) throws DatastoreException {
+ public void markEventReportStatus(@NonNull String eventReportId, @EventReport.Status int status)
+ throws DatastoreException {
ContentValues values = new ContentValues();
- values.put(MeasurementTables.EventReportContract.STATUS, EventReport.Status.DELIVERED);
+ values.put(MeasurementTables.EventReportContract.STATUS, status);
long rows = mSQLTransaction.getDatabase()
.update(MeasurementTables.EventReportContract.TABLE, values,
MeasurementTables.EventReportContract.ID + " = ?",
@@ -339,10 +402,11 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
- public void markAggregateReportDelivered(String aggregateReportId) throws DatastoreException {
+ public void markAggregateReportStatus(
+ String aggregateReportId, @AggregateReport.Status int status)
+ throws DatastoreException {
ContentValues values = new ContentValues();
- values.put(MeasurementTables.AggregateReport.STATUS,
- AggregateReport.Status.DELIVERED);
+ values.put(MeasurementTables.AggregateReport.STATUS, status);
long rows = mSQLTransaction.getDatabase().update(MeasurementTables.AggregateReport.TABLE,
values, MeasurementTables.AggregateReport.ID + " = ? ",
new String[]{aggregateReportId});
@@ -352,6 +416,45 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
+ public void markEventDebugReportDelivered(String eventReportId) throws DatastoreException {
+ ContentValues values = new ContentValues();
+ values.put(
+ MeasurementTables.EventReportContract.DEBUG_REPORT_STATUS,
+ EventReport.DebugReportStatus.DELIVERED);
+ long rows =
+ mSQLTransaction
+ .getDatabase()
+ .update(
+ MeasurementTables.EventReportContract.TABLE,
+ values,
+ MeasurementTables.EventReportContract.ID + " = ?",
+ new String[] {eventReportId});
+ if (rows != 1) {
+ throw new DatastoreException("EventReport update failed.");
+ }
+ }
+
+ @Override
+ public void markAggregateDebugReportDelivered(String aggregateReportId)
+ throws DatastoreException {
+ ContentValues values = new ContentValues();
+ values.put(
+ MeasurementTables.AggregateReport.DEBUG_REPORT_STATUS,
+ AggregateReport.DebugReportStatus.DELIVERED);
+ long rows =
+ mSQLTransaction
+ .getDatabase()
+ .update(
+ MeasurementTables.AggregateReport.TABLE,
+ values,
+ MeasurementTables.AggregateReport.ID + " = ? ",
+ new String[] {aggregateReportId});
+ if (rows != 1) {
+ throw new DatastoreException("AggregateReport update failed");
+ }
+ }
+
+ @Override
@Nullable
public List<EventReport> getSourceEventReports(Source source) throws DatastoreException {
List<EventReport> eventReports = new ArrayList<>();
@@ -361,14 +464,8 @@ class MeasurementDao implements IMeasurementDao {
.query(
MeasurementTables.EventReportContract.TABLE,
/*columns=*/ null,
- MeasurementTables.EventReportContract.SOURCE_ID
- + " = ? "
- + " AND "
- + MeasurementTables.EventReportContract.ENROLLMENT_ID
- + " = ?",
- new String[] {
- String.valueOf(source.getEventId()), source.getEnrollmentId()
- },
+ MeasurementTables.EventReportContract.SOURCE_ID + " = ? ",
+ new String[] {source.getId()},
/*groupBy=*/ null,
/*having=*/ null,
/*orderBy=*/ null,
@@ -413,6 +510,32 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
+ public List<String> getPendingDebugEventReportIds() throws DatastoreException {
+ List<String> eventReports = new ArrayList<>();
+ try (Cursor cursor =
+ mSQLTransaction
+ .getDatabase()
+ .query(
+ MeasurementTables.EventReportContract.TABLE,
+ /*columns=*/ null,
+ MeasurementTables.EventReportContract.DEBUG_REPORT_STATUS + " = ? ",
+ new String[] {
+ String.valueOf(EventReport.DebugReportStatus.PENDING)
+ },
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ "RANDOM()",
+ /*limit=*/ null)) {
+ while (cursor.moveToNext()) {
+ eventReports.add(
+ cursor.getString(
+ cursor.getColumnIndex(MeasurementTables.EventReportContract.ID)));
+ }
+ return eventReports;
+ }
+ }
+
+ @Override
public List<String> getPendingEventReportIdsForGivenApp(Uri appName)
throws DatastoreException {
List<String> eventReports = new ArrayList<>();
@@ -429,7 +552,7 @@ class MeasurementDao implements IMeasurementDao {
MeasurementTables.EventReportContract.TABLE,
MeasurementTables.SourceContract.TABLE,
MeasurementTables.EventReportContract.SOURCE_ID,
- MeasurementTables.SourceContract.EVENT_ID,
+ MeasurementTables.SourceContract.ID,
MeasurementTables.EventReportContract.STATUS,
MeasurementTables.SourceContract.REGISTRANT),
new String[] {
@@ -449,22 +572,25 @@ class MeasurementDao implements IMeasurementDao {
ContentValues values = new ContentValues();
values.put(MeasurementTables.EventReportContract.ID,
UUID.randomUUID().toString());
- values.put(MeasurementTables.EventReportContract.SOURCE_ID,
- eventReport.getSourceId());
+ values.put(
+ MeasurementTables.EventReportContract.SOURCE_EVENT_ID,
+ eventReport.getSourceEventId().getValue());
values.put(MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
eventReport.getAttributionDestination().toString());
values.put(MeasurementTables.EventReportContract.TRIGGER_TIME,
eventReport.getTriggerTime());
- values.put(MeasurementTables.EventReportContract.TRIGGER_DATA,
- eventReport.getTriggerData());
+ values.put(
+ MeasurementTables.EventReportContract.TRIGGER_DATA,
+ getNullableUnsignedLong(eventReport.getTriggerData()));
values.put(MeasurementTables.EventReportContract.TRIGGER_DEDUP_KEY,
- eventReport.getTriggerDedupKey());
+ getNullableUnsignedLong(eventReport.getTriggerDedupKey()));
values.put(MeasurementTables.EventReportContract.ENROLLMENT_ID,
eventReport.getEnrollmentId());
- values.put(MeasurementTables.EventReportContract.STATUS,
- eventReport.getStatus());
- values.put(MeasurementTables.EventReportContract.REPORT_TIME,
- eventReport.getReportTime());
+ values.put(MeasurementTables.EventReportContract.STATUS, eventReport.getStatus());
+ values.put(
+ MeasurementTables.EventReportContract.DEBUG_REPORT_STATUS,
+ eventReport.getDebugReportStatus());
+ values.put(MeasurementTables.EventReportContract.REPORT_TIME, eventReport.getReportTime());
values.put(MeasurementTables.EventReportContract.TRIGGER_PRIORITY,
eventReport.getTriggerPriority());
values.put(MeasurementTables.EventReportContract.SOURCE_TYPE,
@@ -473,10 +599,12 @@ class MeasurementDao implements IMeasurementDao {
eventReport.getRandomizedTriggerRate());
values.put(
MeasurementTables.EventReportContract.SOURCE_DEBUG_KEY,
- eventReport.getSourceDebugKey());
+ getNullableUnsignedLong(eventReport.getSourceDebugKey()));
values.put(
MeasurementTables.EventReportContract.TRIGGER_DEBUG_KEY,
- eventReport.getTriggerDebugKey());
+ getNullableUnsignedLong(eventReport.getTriggerDebugKey()));
+ values.put(MeasurementTables.EventReportContract.SOURCE_ID, eventReport.getSourceId());
+ values.put(MeasurementTables.EventReportContract.TRIGGER_ID, eventReport.getTriggerId());
long rowId = mSQLTransaction.getDatabase()
.insert(MeasurementTables.EventReportContract.TABLE,
/*nullColumnHack=*/null, values);
@@ -486,11 +614,14 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
- public void updateSourceDedupKeys(Source source) throws DatastoreException {
+ public void updateSourceDedupKeys(@NonNull Source source) throws DatastoreException {
ContentValues values = new ContentValues();
- values.put(MeasurementTables.SourceContract.DEDUP_KEYS,
- source.getDedupKeys().stream().map(String::valueOf).collect(
- Collectors.joining(",")));
+ values.put(
+ MeasurementTables.SourceContract.DEDUP_KEYS,
+ source.getDedupKeys().stream()
+ .map(UnsignedLong::getValue)
+ .map(String::valueOf)
+ .collect(Collectors.joining(",")));
long rows = mSQLTransaction.getDatabase()
.update(MeasurementTables.SourceContract.TABLE, values,
MeasurementTables.SourceContract.ID + " = ?",
@@ -519,6 +650,8 @@ class MeasurementDao implements IMeasurementDao {
values.put(
MeasurementTables.AttributionContract.TRIGGER_TIME, attribution.getTriggerTime());
values.put(MeasurementTables.AttributionContract.REGISTRANT, attribution.getRegistrant());
+ values.put(MeasurementTables.AttributionContract.SOURCE_ID, attribution.getSourceId());
+ values.put(MeasurementTables.AttributionContract.TRIGGER_ID, attribution.getTriggerId());
long rowId =
mSQLTransaction
.getDatabase()
@@ -562,7 +695,7 @@ class MeasurementDao implements IMeasurementDao {
+ MeasurementTables.AttributionContract.ENROLLMENT_ID
+ " = ? AND "
+ MeasurementTables.AttributionContract.TRIGGER_TIME
- + " >= ? AND "
+ + " > ? AND "
+ MeasurementTables.AttributionContract.TRIGGER_TIME
+ " <= ? ",
new String[] {
@@ -576,12 +709,12 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
- public long getNumSourcesPerRegistrant(Uri registrant) throws DatastoreException {
+ public long getNumSourcesPerPublisher(Uri publisherUri, @EventSurfaceType int publisherType)
+ throws DatastoreException {
return DatabaseUtils.queryNumEntries(
mSQLTransaction.getDatabase(),
MeasurementTables.SourceContract.TABLE,
- MeasurementTables.SourceContract.REGISTRANT + " = ? ",
- new String[]{registrant.toString()});
+ getPublisherWhereStatement(publisherUri, publisherType));
}
@Override
@@ -602,7 +735,7 @@ class MeasurementDao implements IMeasurementDao {
Locale.ENGLISH,
"SELECT COUNT(DISTINCT %1$s) FROM %2$s "
+ "WHERE %3$s = ? AND %4$s = ? AND %1s != ? "
- + "AND %5$s < ? AND %5$s >= ?",
+ + "AND %5$s > ? AND %5$s <= ?",
MeasurementTables.AttributionContract.ENROLLMENT_ID,
MeasurementTables.AttributionContract.TABLE,
MeasurementTables.AttributionContract.SOURCE_SITE,
@@ -616,8 +749,8 @@ class MeasurementDao implements IMeasurementDao {
sourceSite.toString(),
destinationSite.toString(),
excludedEnrollmentId,
- String.valueOf(windowEndTime),
- String.valueOf(windowStartTime)
+ String.valueOf(windowStartTime),
+ String.valueOf(windowEndTime)
});
}
@@ -634,13 +767,15 @@ class MeasurementDao implements IMeasurementDao {
Locale.ENGLISH,
"SELECT COUNT(DISTINCT %1$s) FROM %2$s "
+ "WHERE %3$s AND %4$s = ? AND %5$s = ? AND %1$s != ? "
- + "AND %6$s < ? AND %6$s >= ?",
+ + "AND %6$s > ? AND %6$s <= ?"
+ + "AND %7$s > ?",
destinationColumn,
MeasurementTables.SourceContract.TABLE,
getPublisherWhereStatement(publisher, publisherType),
MeasurementTables.SourceContract.ENROLLMENT_ID,
MeasurementTables.SourceContract.STATUS,
- MeasurementTables.SourceContract.EVENT_TIME);
+ MeasurementTables.SourceContract.EVENT_TIME,
+ MeasurementTables.SourceContract.EXPIRY_TIME);
return (int) DatabaseUtils.longForQuery(
mSQLTransaction.getDatabase(),
query,
@@ -648,8 +783,9 @@ class MeasurementDao implements IMeasurementDao {
enrollmentId,
String.valueOf(Source.Status.ACTIVE),
excludedDestination.toString(),
+ String.valueOf(windowStartTime),
String.valueOf(windowEndTime),
- String.valueOf(windowStartTime) });
+ String.valueOf(windowEndTime) });
}
@Override
@@ -661,13 +797,15 @@ class MeasurementDao implements IMeasurementDao {
Locale.ENGLISH,
"SELECT COUNT(DISTINCT %1$s) FROM %2$s "
+ "WHERE %3$s AND (%4$s = ? OR %5$s = ?) AND %1s != ? "
- + "AND %6$s < ? AND %6$s >= ?",
+ + "AND %6$s > ? AND %6$s <= ?"
+ + "AND %7$s > ?",
MeasurementTables.SourceContract.ENROLLMENT_ID,
MeasurementTables.SourceContract.TABLE,
getPublisherWhereStatement(publisher, publisherType),
MeasurementTables.SourceContract.APP_DESTINATION,
MeasurementTables.SourceContract.WEB_DESTINATION,
- MeasurementTables.SourceContract.EVENT_TIME);
+ MeasurementTables.SourceContract.EVENT_TIME,
+ MeasurementTables.SourceContract.EXPIRY_TIME);
return (int)
DatabaseUtils.longForQuery(
mSQLTransaction.getDatabase(),
@@ -676,8 +814,9 @@ class MeasurementDao implements IMeasurementDao {
destination.toString(),
destination.toString(),
excludedEnrollmentId,
+ String.valueOf(windowStartTime),
String.valueOf(windowEndTime),
- String.valueOf(windowStartTime)
+ String.valueOf(windowEndTime)
});
}
@@ -702,13 +841,14 @@ class MeasurementDao implements IMeasurementDao {
MeasurementTables.EventReportContract.TABLE,
MeasurementTables.SourceContract.TABLE,
MeasurementTables.EventReportContract.SOURCE_ID,
- MeasurementTables.SourceContract.EVENT_ID,
+ MeasurementTables.SourceContract.ID,
MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
MeasurementTables.SourceContract.APP_DESTINATION,
MeasurementTables.EventReportContract.ENROLLMENT_ID,
MeasurementTables.SourceContract.ENROLLMENT_ID,
MeasurementTables.SourceContract.REGISTRANT),
new String[] {uriStr});
+
// EventReport table
db.delete(MeasurementTables.EventReportContract.TABLE,
MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION + " = ?",
@@ -746,26 +886,160 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
+ public void deleteAppRecordsNotPresent(List<Uri> uriList) throws DatastoreException {
+ SQLiteDatabase db = mSQLTransaction.getDatabase();
+
+ String inQuery = constructDeleteQueryAppsNotPresent(uriList);
+
+ // For all Source records not in the given list
+ // as REGISTRANT, obtains EventReport records whose SOURCE_ID
+ // matches Source records' SOURCE_ID.
+ db.delete(
+ MeasurementTables.EventReportContract.TABLE,
+ String.format(
+ Locale.ENGLISH,
+ "%1$s IN ("
+ + "SELECT e.%1$s FROM %2$s e"
+ + " INNER JOIN %3$s s"
+ + " ON (e.%4$s = s.%5$s AND e.%6$s = s.%7$s AND e.%8$s = s.%9$s)"
+ + " WHERE s.%10$s NOT IN "
+ + inQuery
+ + ")",
+ MeasurementTables.EventReportContract.ID,
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.SourceContract.TABLE,
+ MeasurementTables.EventReportContract.SOURCE_ID,
+ MeasurementTables.SourceContract.ID,
+ MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
+ MeasurementTables.SourceContract.APP_DESTINATION,
+ MeasurementTables.EventReportContract.ENROLLMENT_ID,
+ MeasurementTables.SourceContract.ENROLLMENT_ID,
+ MeasurementTables.SourceContract.REGISTRANT),
+ /* whereArgs */ null);
+
+ // Event Report table
+ db.delete(
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION
+ + " NOT IN "
+ + inQuery,
+ /* whereArgs */ null);
+
+ // AggregateReport table
+ db.delete(
+ MeasurementTables.AggregateReport.TABLE,
+ MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION
+ + " NOT IN "
+ + inQuery.toString()
+ + " OR "
+ + MeasurementTables.AggregateReport.PUBLISHER
+ + " NOT IN "
+ + inQuery.toString(),
+ /* whereArgs */ null);
+
+ // Source table
+ db.delete(
+ MeasurementTables.SourceContract.TABLE,
+ "(("
+ + MeasurementTables.SourceContract.REGISTRANT
+ + " NOT IN "
+ + inQuery
+ + ") OR ("
+ + MeasurementTables.SourceContract.STATUS
+ + " = ? AND "
+ + MeasurementTables.SourceContract.APP_DESTINATION
+ + " NOT IN "
+ + inQuery
+ + "))",
+ new String[] {String.valueOf(Source.Status.IGNORED)});
+
+ // Trigger table
+ db.delete(
+ MeasurementTables.TriggerContract.TABLE,
+ MeasurementTables.TriggerContract.REGISTRANT + " NOT IN " + inQuery,
+ /* whereArgs */ null);
+
+ // Attribution table
+ db.delete(
+ MeasurementTables.AttributionContract.TABLE,
+ MeasurementTables.AttributionContract.SOURCE_SITE
+ + " NOT IN "
+ + inQuery.toString()
+ + " OR "
+ + MeasurementTables.AttributionContract.DESTINATION_SITE
+ + " NOT IN "
+ + inQuery.toString(),
+ /* whereArgs */ null);
+ }
+
+ @Override
+ public List<AggregateReport> fetchMatchingAggregateReports(
+ @NonNull List<String> sourceIds, @NonNull List<String> triggerIds)
+ throws DatastoreException {
+ return fetchRecordsMatchingWithParameters(
+ MeasurementTables.AggregateReport.TABLE,
+ MeasurementTables.AggregateReport.SOURCE_ID,
+ sourceIds,
+ MeasurementTables.AggregateReport.TRIGGER_ID,
+ triggerIds,
+ SqliteObjectMapper::constructAggregateReport);
+ }
+
+ @Override
+ public List<EventReport> fetchMatchingEventReports(
+ @NonNull List<String> sourceIds, @NonNull List<String> triggerIds)
+ throws DatastoreException {
+ return fetchRecordsMatchingWithParameters(
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.EventReportContract.SOURCE_ID,
+ sourceIds,
+ MeasurementTables.EventReportContract.TRIGGER_ID,
+ triggerIds,
+ SqliteObjectMapper::constructEventReportFromCursor);
+ }
+
+ private String constructDeleteQueryAppsNotPresent(List<Uri> uriList) {
+ // Construct query, as list of all packages present on the device
+ StringBuilder inQuery = new StringBuilder();
+ inQuery.append("(");
+ inQuery.append(
+ uriList.stream()
+ .map((uri) -> DatabaseUtils.sqlEscapeString(uri.toString()))
+ .collect(Collectors.joining(", ")));
+ inQuery.append(")");
+ return inQuery.toString();
+ }
+
+ @Override
public void deleteExpiredRecords() throws DatastoreException {
SQLiteDatabase db = mSQLTransaction.getDatabase();
long earliestValidInsertion =
System.currentTimeMillis() - MEASUREMENT_DELETE_EXPIRED_WINDOW_MS;
String earliestValidInsertionStr = String.valueOf(earliestValidInsertion);
+ // Deleting the sources and triggers will take care of deleting records from
+ // event report, aggregate report and attribution tables as well. No explicit deletion is
+ // required for them. Although, having proactive deletion of expired records help clean up
+ // space.
// Source table
- db.delete(MeasurementTables.SourceContract.TABLE,
+ db.delete(
+ MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.EVENT_TIME + " < ?",
- new String[]{earliestValidInsertionStr});
+ new String[] {earliestValidInsertionStr});
// Trigger table
- db.delete(MeasurementTables.TriggerContract.TABLE,
+ db.delete(
+ MeasurementTables.TriggerContract.TABLE,
MeasurementTables.TriggerContract.TRIGGER_TIME + " < ?",
- new String[]{earliestValidInsertionStr});
+ new String[] {earliestValidInsertionStr});
// EventReport table
- db.delete(MeasurementTables.EventReportContract.TABLE,
- MeasurementTables.EventReportContract.STATUS + " = ? OR "
- + MeasurementTables.EventReportContract.REPORT_TIME + " < ?",
- new String[]{
- String.valueOf(EventReport.Status.DELIVERED),
- earliestValidInsertionStr});
+ db.delete(
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.EventReportContract.STATUS
+ + " = ? OR "
+ + MeasurementTables.EventReportContract.REPORT_TIME
+ + " < ?",
+ new String[] {
+ String.valueOf(EventReport.Status.DELIVERED), earliestValidInsertionStr
+ });
// AggregateReport table
db.delete(
MeasurementTables.AggregateReport.TABLE,
@@ -783,14 +1057,14 @@ class MeasurementDao implements IMeasurementDao {
}
@Override
- public void deleteMeasurementData(
+ public List<String> fetchMatchingSources(
@NonNull Uri registrant,
@NonNull Instant start,
@NonNull Instant end,
@NonNull List<Uri> origins,
@NonNull List<Uri> domains,
- @DeletionRequest.MatchBehavior int matchBehavior,
- @DeletionRequest.DeletionMode int deletionMode)
+ // TODO: change this to selection and invert selection mode
+ @DeletionRequest.MatchBehavior int matchBehavior)
throws DatastoreException {
Objects.requireNonNull(registrant);
Objects.requireNonNull(origins);
@@ -805,140 +1079,88 @@ class MeasurementDao implements IMeasurementDao {
if (domains.isEmpty()
&& origins.isEmpty()
&& matchBehavior == DeletionRequest.MATCH_BEHAVIOR_PRESERVE) {
- return;
+ return ImmutableList.of();
}
- final SQLiteDatabase db = mSQLTransaction.getDatabase();
Function<String, String> registrantMatcher = getRegistrantMatcher(registrant);
Function<String, String> siteMatcher = getSiteMatcher(origins, domains, matchBehavior);
Function<String, String> timeMatcher = getTimeMatcher(cappedStart, cappedEnd);
- if (deletionMode == DeletionRequest.DELETION_MODE_ALL) {
- deleteAttribution(db, registrantMatcher, siteMatcher, timeMatcher);
+ final SQLiteDatabase db = mSQLTransaction.getDatabase();
+ ImmutableList.Builder<String> sourceIds = new ImmutableList.Builder<>();
+ try (Cursor cursor =
+ db.query(
+ MeasurementTables.SourceContract.TABLE,
+ new String[] {MeasurementTables.SourceContract.ID},
+ mergeConditions(
+ " AND ",
+ registrantMatcher.apply(
+ MeasurementTables.SourceContract.REGISTRANT),
+ siteMatcher.apply(MeasurementTables.SourceContract.PUBLISHER),
+ timeMatcher.apply(MeasurementTables.SourceContract.EVENT_TIME)),
+ null,
+ null,
+ null,
+ null)) {
+ while (cursor.moveToNext()) {
+ sourceIds.add(cursor.getString(0));
+ }
}
- deleteEventReport(db, registrantMatcher, siteMatcher, timeMatcher);
- deleteTrigger(db, registrantMatcher, siteMatcher, timeMatcher);
- deleteSource(db, registrantMatcher, siteMatcher, timeMatcher);
- }
- private void deleteSource(
- SQLiteDatabase db,
- Function<String, String> registrantMatcher,
- Function<String, String> siteMatcher,
- Function<String, String> timeMatcher) {
- db.delete(
- MeasurementTables.SourceContract.TABLE,
- mergeConditions(
- " AND ",
- registrantMatcher.apply(MeasurementTables.SourceContract.REGISTRANT),
- siteMatcher.apply(MeasurementTables.SourceContract.PUBLISHER),
- timeMatcher.apply(MeasurementTables.SourceContract.EVENT_TIME)),
- null);
+ return sourceIds.build();
}
- private void deleteTrigger(
- SQLiteDatabase db,
- Function<String, String> registrantMatcher,
- Function<String, String> siteMatcher,
- Function<String, String> timeMatcher) {
- // Where Statement:
- // (registrant - RegistrantMatching) AND
- // (attributionStatement - OriginMatching) AND
- // (triggerTime - TimeMatching)
- db.delete(
- MeasurementTables.TriggerContract.TABLE,
- mergeConditions(
- " AND ",
- registrantMatcher.apply(MeasurementTables.TriggerContract.REGISTRANT),
- siteMatcher.apply(
- MeasurementTables.TriggerContract.ATTRIBUTION_DESTINATION),
- timeMatcher.apply(MeasurementTables.TriggerContract.TRIGGER_TIME)),
- null);
- }
+ @Override
+ public List<String> fetchMatchingTriggers(
+ @NonNull Uri registrant,
+ @NonNull Instant start,
+ @NonNull Instant end,
+ @NonNull List<Uri> origins,
+ @NonNull List<Uri> domains,
+ // TODO: change this to selection and invert selection mode
+ @DeletionRequest.MatchBehavior int matchBehavior)
+ throws DatastoreException {
+ Objects.requireNonNull(registrant);
+ Objects.requireNonNull(origins);
+ Objects.requireNonNull(domains);
+ Objects.requireNonNull(start);
+ Objects.requireNonNull(end);
+ validateRange(start, end);
+ Instant cappedStart = capDeletionRange(start);
+ Instant cappedEnd = capDeletionRange(end);
+ // Handle no-op case
+ // Preserving everything => Do Nothing
+ if (domains.isEmpty()
+ && origins.isEmpty()
+ && matchBehavior == DeletionRequest.MATCH_BEHAVIOR_PRESERVE) {
+ return ImmutableList.of();
+ }
+ Function<String, String> registrantMatcher = getRegistrantMatcher(registrant);
+ Function<String, String> siteMatcher = getSiteMatcher(origins, domains, matchBehavior);
+ Function<String, String> timeMatcher = getTimeMatcher(cappedStart, cappedEnd);
- private void deleteEventReport(
- SQLiteDatabase db,
- Function<String, String> registrantMatcher,
- Function<String, String> siteMatcher,
- Function<String, String> timeMatcher) {
- String sourceSiteColumn = "s." + MeasurementTables.SourceContract.PUBLISHER;
- String sourceTimeColumn = "s." + MeasurementTables.SourceContract.EVENT_TIME;
- String eventReportSiteColumn =
- "e." + MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION;
- String eventReportTimeColumn = "e." + MeasurementTables.EventReportContract.TRIGGER_TIME;
-
- // Where Statement:
- // evenReport.ID IN (
- // SELECT e.ID FROM event_report e INNER JOIN source s ON
- // (e.event_id = s.event_id) WHERE (
- // (registrant - RegistrantMatching) AND
- // (((s.publisher - OriginMatching) AND (s.eventTime - TimeMatching)) OR
- // ((e.destination - OriginMatching) AND (e.triggerTime - TimeMatching)))
- // )
- // )
- // )
- String whereString =
- MeasurementTables.EventReportContract.ID
- + " IN ("
- + "SELECT e."
- + MeasurementTables.EventReportContract.ID
- + " FROM "
- + MeasurementTables.EventReportContract.TABLE
- + " e "
- + "INNER JOIN "
- + MeasurementTables.SourceContract.TABLE
- + " s "
- + "ON (e."
- + MeasurementTables.EventReportContract.SOURCE_ID
- + " = "
- + " s."
- + MeasurementTables.SourceContract.EVENT_ID
- + ") "
- // Where string
- + " WHERE "
- + mergeConditions(
- /* operator = */ " AND ",
- registrantMatcher.apply(
- MeasurementTables.SourceContract.REGISTRANT),
- mergeConditions(
- /* operator = */ " OR ",
- mergeConditions(
- /* operator = */ " AND ",
- siteMatcher.apply(sourceSiteColumn),
- timeMatcher.apply(sourceTimeColumn)),
- mergeConditions(
- /* operator = */ " AND ",
- siteMatcher.apply(eventReportSiteColumn),
- timeMatcher.apply(eventReportTimeColumn))))
- + ")";
- db.delete(MeasurementTables.EventReportContract.TABLE, whereString, null);
- }
-
- private void deleteAttribution(
- SQLiteDatabase db,
- Function<String, String> registrantMatcher,
- Function<String, String> siteMatcher,
- Function<String, String> timeMatcher) {
- // Where Statement:
- // (registrant - RegistrantMatching) AND
- // ((destinationOrigin - OriginMatching) OR (sourceOrigin - OriginMatching)) AND
- // (triggerTime - TimeMatching)
- db.delete(
- MeasurementTables.AttributionContract.TABLE,
- mergeConditions(
- " AND ",
- registrantMatcher.apply(
- MeasurementTables.AttributionContract.REGISTRANT),
+ final SQLiteDatabase db = mSQLTransaction.getDatabase();
+ ImmutableList.Builder<String> triggerIds = new ImmutableList.Builder<>();
+ try (Cursor cursor =
+ db.query(
+ MeasurementTables.TriggerContract.TABLE,
+ new String[] {MeasurementTables.TriggerContract.ID},
mergeConditions(
- " OR ",
- siteMatcher.apply(
- MeasurementTables.AttributionContract
- .DESTINATION_ORIGIN),
+ " AND ",
+ registrantMatcher.apply(
+ MeasurementTables.TriggerContract.REGISTRANT),
siteMatcher.apply(
- MeasurementTables.AttributionContract
- .SOURCE_ORIGIN)),
- timeMatcher.apply(
- MeasurementTables.AttributionContract.TRIGGER_TIME)),
- null);
+ MeasurementTables.TriggerContract.ATTRIBUTION_DESTINATION),
+ timeMatcher.apply(MeasurementTables.TriggerContract.TRIGGER_TIME)),
+ null,
+ null,
+ null,
+ null)) {
+ while (cursor.moveToNext()) {
+ triggerIds.add(cursor.getString(0));
+ }
+ }
+
+ return triggerIds.build();
}
private static Function<String, String> getRegistrantMatcher(Uri registrant) {
@@ -1090,42 +1312,54 @@ class MeasurementDao implements IMeasurementDao {
public void doInstallAttribution(Uri uri, long eventTimestamp) throws DatastoreException {
SQLiteDatabase db = mSQLTransaction.getDatabase();
- SQLiteQueryBuilder sqb = new SQLiteQueryBuilder();
- sqb.setTables(MeasurementTables.SourceContract.TABLE);
- // Sub query for selecting relevant source ids.
- // Selecting the highest priority, most recent source with eventTimestamp falling in the
- // source's install attribution window.
- String subQuery =
- sqb.buildQuery(
- new String[] {MeasurementTables.SourceContract.ID},
- String.format(
- Locale.ENGLISH,
- MeasurementTables.SourceContract.APP_DESTINATION
- + " = %1$s AND "
- + MeasurementTables.SourceContract.EVENT_TIME
- + " <= %2$d AND "
- + MeasurementTables.SourceContract.EXPIRY_TIME
- + " > %2$d AND "
- + MeasurementTables.SourceContract.EVENT_TIME
+ String whereString =
+ String.format(
+ Locale.ENGLISH,
+ mergeConditions(
+ " AND ",
+ MeasurementTables.SourceContract.APP_DESTINATION + " = %1$s",
+ MeasurementTables.SourceContract.EVENT_TIME + " <= %2$d",
+ MeasurementTables.SourceContract.EXPIRY_TIME + " > %2$d",
+ MeasurementTables.SourceContract.EVENT_TIME
+ " + "
+ MeasurementTables.SourceContract
.INSTALL_ATTRIBUTION_WINDOW
+ " >= %2$d",
- DatabaseUtils.sqlEscapeString(uri.toString()),
- eventTimestamp),
- /* groupBy= */ null,
- /* having= */ null,
- /* sortOrder= */ MeasurementTables.SourceContract.PRIORITY
- + " DESC, "
- + MeasurementTables.SourceContract.EVENT_TIME
- + " DESC",
- /* limit = */ "1");
+ MeasurementTables.SourceContract.STATUS + " = %3$d"),
+ DatabaseUtils.sqlEscapeString(uri.toString()),
+ eventTimestamp,
+ Source.Status.ACTIVE);
+
+ // Will generate the records that we are interested in
+ String filterQuery =
+ String.format(
+ Locale.ENGLISH,
+ " ( SELECT * from %1$s WHERE %2$s )",
+ MeasurementTables.SourceContract.TABLE,
+ whereString);
+
+ // The inner query picks the top record based on priority and recency order after applying
+ // the filter. But first_value generates one value per partition but applies to all the rows
+ // that input has, so we have to nest it with distinct in order to get unique source_ids.
+ String sourceIdsProjection =
+ String.format(
+ Locale.ENGLISH,
+ "SELECT DISTINCT(first_source_id) from "
+ + "(SELECT first_value(%1$s) "
+ + "OVER (PARTITION BY %2$s ORDER BY %3$s DESC, %4$s DESC) "
+ + "first_source_id FROM %5$s)",
+ MeasurementTables.SourceContract.ID,
+ MeasurementTables.SourceContract.ENROLLMENT_ID,
+ MeasurementTables.SourceContract.PRIORITY,
+ MeasurementTables.SourceContract.EVENT_TIME,
+ filterQuery);
ContentValues values = new ContentValues();
values.put(MeasurementTables.SourceContract.IS_INSTALL_ATTRIBUTED, true);
- db.update(MeasurementTables.SourceContract.TABLE,
+ db.update(
+ MeasurementTables.SourceContract.TABLE,
values,
- MeasurementTables.SourceContract.ID + " IN (" + subQuery + ")",
+ MeasurementTables.SourceContract.ID + " IN (" + sourceIdsProjection + ")",
null);
}
@@ -1150,11 +1384,16 @@ class MeasurementDao implements IMeasurementDao {
aggregateEncryptionKey.getKeyId());
values.put(MeasurementTables.AggregateEncryptionKey.PUBLIC_KEY,
aggregateEncryptionKey.getPublicKey());
- values.put(MeasurementTables.AggregateEncryptionKey.EXPIRY,
+ values.put(
+ MeasurementTables.AggregateEncryptionKey.EXPIRY,
aggregateEncryptionKey.getExpiry());
- long rowId = mSQLTransaction.getDatabase()
- .insert(MeasurementTables.AggregateEncryptionKey.TABLE,
- /*nullColumnHack=*/null, values);
+ long rowId =
+ mSQLTransaction
+ .getDatabase()
+ .insert(
+ MeasurementTables.AggregateEncryptionKey.TABLE,
+ /*nullColumnHack=*/ null,
+ values);
if (rowId == -1) {
throw new DatastoreException("Aggregate encryption key insertion failed.");
}
@@ -1164,12 +1403,18 @@ class MeasurementDao implements IMeasurementDao {
public List<AggregateEncryptionKey> getNonExpiredAggregateEncryptionKeys(long expiry)
throws DatastoreException {
List<AggregateEncryptionKey> aggregateEncryptionKeys = new ArrayList<>();
- try (Cursor cursor = mSQLTransaction.getDatabase().query(
- MeasurementTables.AggregateEncryptionKey.TABLE,
- /*columns=*/null,
- MeasurementTables.AggregateEncryptionKey.EXPIRY + " >= ?",
- new String[]{String.valueOf(expiry)},
- /*groupBy=*/null, /*having=*/null, /*orderBy=*/null, /*limit=*/null)) {
+ try (Cursor cursor =
+ mSQLTransaction
+ .getDatabase()
+ .query(
+ MeasurementTables.AggregateEncryptionKey.TABLE,
+ /*columns=*/ null,
+ MeasurementTables.AggregateEncryptionKey.EXPIRY + " >= ?",
+ new String[] {String.valueOf(expiry)},
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null,
+ /*limit=*/ null)) {
while (cursor.moveToNext()) {
aggregateEncryptionKeys
.add(SqliteObjectMapper.constructAggregateEncryptionKeyFromCursor(cursor));
@@ -1205,14 +1450,19 @@ class MeasurementDao implements IMeasurementDao {
aggregateReport.getDebugCleartextPayload());
values.put(MeasurementTables.AggregateReport.STATUS,
aggregateReport.getStatus());
+ values.put(
+ MeasurementTables.AggregateReport.DEBUG_REPORT_STATUS,
+ aggregateReport.getDebugReportStatus());
values.put(MeasurementTables.AggregateReport.API_VERSION,
aggregateReport.getApiVersion());
values.put(
MeasurementTables.AggregateReport.SOURCE_DEBUG_KEY,
- aggregateReport.getSourceDebugKey());
+ getNullableUnsignedLong(aggregateReport.getSourceDebugKey()));
values.put(
MeasurementTables.AggregateReport.TRIGGER_DEBUG_KEY,
- aggregateReport.getTriggerDebugKey());
+ getNullableUnsignedLong(aggregateReport.getTriggerDebugKey()));
+ values.put(MeasurementTables.AggregateReport.SOURCE_ID, aggregateReport.getSourceId());
+ values.put(MeasurementTables.AggregateReport.TRIGGER_ID, aggregateReport.getTriggerId());
long rowId = mSQLTransaction.getDatabase()
.insert(MeasurementTables.AggregateReport.TABLE,
/*nullColumnHack=*/null, values);
@@ -1235,8 +1485,35 @@ class MeasurementDao implements IMeasurementDao {
String.valueOf(AggregateReport.Status.PENDING)},
/*groupBy=*/null, /*having=*/null, /*orderBy=*/"RANDOM()", /*limit=*/null)) {
while (cursor.moveToNext()) {
- aggregateReports.add(cursor.getString(cursor.getColumnIndex(
- MeasurementTables.EventReportContract.ID)));
+ aggregateReports.add(
+ cursor.getString(
+ cursor.getColumnIndex(MeasurementTables.AggregateReport.ID)));
+ }
+ return aggregateReports;
+ }
+ }
+
+ @Override
+ public List<String> getPendingAggregateDebugReportIds() throws DatastoreException {
+ List<String> aggregateReports = new ArrayList<>();
+ try (Cursor cursor =
+ mSQLTransaction
+ .getDatabase()
+ .query(
+ MeasurementTables.AggregateReport.TABLE,
+ /*columns=*/ null,
+ MeasurementTables.AggregateReport.DEBUG_REPORT_STATUS + " = ? ",
+ new String[] {
+ String.valueOf(AggregateReport.DebugReportStatus.PENDING)
+ },
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ "RANDOM()",
+ /*limit=*/ null)) {
+ while (cursor.moveToNext()) {
+ aggregateReports.add(
+ cursor.getString(
+ cursor.getColumnIndex(MeasurementTables.AggregateReport.ID)));
}
return aggregateReports;
}
@@ -1272,11 +1549,49 @@ class MeasurementDao implements IMeasurementDao {
}
}
+ @Override
+ public void deleteSources(@NonNull List<String> sourceIds) throws DatastoreException {
+ deleteRecordsColumnBased(
+ sourceIds,
+ MeasurementTables.SourceContract.TABLE,
+ MeasurementTables.SourceContract.ID);
+ }
+
+ @Override
+ public void deleteTriggers(@NonNull List<String> triggerIds) throws DatastoreException {
+ deleteRecordsColumnBased(
+ triggerIds,
+ MeasurementTables.TriggerContract.TABLE,
+ MeasurementTables.TriggerContract.ID);
+ }
+
+ private void deleteRecordsColumnBased(
+ List<String> columnValues, String tableName, String columnName)
+ throws DatastoreException {
+ long rows =
+ mSQLTransaction
+ .getDatabase()
+ .delete(
+ tableName,
+ columnName
+ + " IN ("
+ + Stream.generate(() -> "?")
+ .limit(columnValues.size())
+ .collect(Collectors.joining(","))
+ + ")",
+ columnValues.toArray(new String[0]));
+ if (rows < 0) {
+ throw new DatastoreException(
+ String.format("Deletion failed from %1s on %2s.", tableName, columnName));
+ }
+ }
+
private static Optional<Pair<String, String>> getDestinationColumnAndValue(Trigger trigger) {
if (trigger.getDestinationType() == EventSurfaceType.APP) {
- return Optional.of(Pair.create(
- MeasurementTables.SourceContract.APP_DESTINATION,
- trigger.getAttributionDestination().toString()));
+ return Optional.of(
+ Pair.create(
+ MeasurementTables.SourceContract.APP_DESTINATION,
+ trigger.getAttributionDestination().toString()));
} else {
Optional<Uri> topPrivateDomainAndScheme =
Web.topPrivateDomainAndScheme(trigger.getAttributionDestination());
@@ -1317,6 +1632,10 @@ class MeasurementDao implements IMeasurementDao {
return Optional.ofNullable(uri).map(Uri::toString).orElse(null);
}
+ private static Long getNullableUnsignedLong(@Nullable UnsignedLong ulong) {
+ return Optional.ofNullable(ulong).map(UnsignedLong::getValue).orElse(null);
+ }
+
/**
* Returns the min or max possible long value to avoid the ArithmeticException thrown when
* calling toEpochMilli() on Instant.MAX or Instant.MIN
@@ -1328,4 +1647,248 @@ class MeasurementDao implements IMeasurementDao {
Arrays.sort(instants);
return instants[1];
}
+
+ public void insertAsyncRegistration(@NonNull AsyncRegistration asyncRegistration)
+ throws DatastoreException {
+ ContentValues values = new ContentValues();
+ values.put(MeasurementTables.AsyncRegistrationContract.ID, asyncRegistration.getId());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.ENROLLMENT_ID,
+ asyncRegistration.getEnrollmentId());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.REGISTRATION_URI,
+ asyncRegistration.getRegistrationUri().toString());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.WEB_DESTINATION,
+ getNullableUriString(asyncRegistration.getWebDestination()));
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.VERIFIED_DESTINATION,
+ getNullableUriString(asyncRegistration.getVerifiedDestination()));
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.OS_DESTINATION,
+ getNullableUriString(asyncRegistration.getOsDestination()));
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.REGISTRANT,
+ getNullableUriString(asyncRegistration.getRegistrant()));
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.TOP_ORIGIN,
+ asyncRegistration.getTopOrigin().toString());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.REDIRECT_TYPE,
+ asyncRegistration.getRedirectType());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.REDIRECT_COUNT,
+ asyncRegistration.getRedirectCount());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.SOURCE_TYPE,
+ asyncRegistration.getSourceType() == null
+ ? null
+ : asyncRegistration.getSourceType().ordinal());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.REQUEST_TIME,
+ asyncRegistration.getRequestTime());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.RETRY_COUNT,
+ asyncRegistration.getRetryCount());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.LAST_PROCESSING_TIME,
+ asyncRegistration.getLastProcessingTime());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.TYPE,
+ asyncRegistration.getType().ordinal());
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.DEBUG_KEY_ALLOWED,
+ asyncRegistration.getDebugKeyAllowed());
+ long rowId =
+ mSQLTransaction
+ .getDatabase()
+ .insert(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ /*nullColumnHack=*/ null,
+ values);
+ LogUtil.d("MeasurementDao: insertAsyncRegistration: rowId=" + rowId);
+ if (rowId == -1) {
+ throw new DatastoreException("Async Registration insertion failed.");
+ }
+ }
+
+ @Override
+ public void deleteAsyncRegistration(@NonNull String id) throws DatastoreException {
+ SQLiteDatabase db = mSQLTransaction.getDatabase();
+ int rows =
+ db.delete(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ MeasurementTables.AsyncRegistrationContract.ID + " = ?",
+ new String[] {id});
+ LogUtil.d("MeasurementDao: deleteAsyncRegistration: rows affected=" + rows);
+ }
+
+ @Override
+ public AsyncRegistration fetchNextQueuedAsyncRegistration(
+ short retryLimit, List<String> failedAdTechEnrollmentIds) throws DatastoreException {
+ StringBuilder notIn = new StringBuilder();
+ StringBuilder lessThanRetryLimit = new StringBuilder();
+ lessThanRetryLimit.append(" < ? ");
+
+ if (!failedAdTechEnrollmentIds.isEmpty()) {
+ lessThanRetryLimit.append(
+ "AND " + MeasurementTables.AsyncRegistrationContract.ENROLLMENT_ID);
+ notIn.append(" NOT IN ");
+ notIn.append(
+ "("
+ + failedAdTechEnrollmentIds.stream()
+ .map((o) -> "'" + o + "'")
+ .collect(Collectors.joining(", "))
+ + ")");
+ }
+ try (Cursor cursor =
+ mSQLTransaction
+ .getDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ /*columns=*/ null,
+ MeasurementTables.AsyncRegistrationContract.RETRY_COUNT
+ + lessThanRetryLimit.toString()
+ + notIn.toString(),
+ new String[] {String.valueOf(retryLimit)},
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ MeasurementTables.AsyncRegistrationContract
+ .REQUEST_TIME,
+ /*limit=*/ "1")) {
+ if (cursor.getCount() == 0) {
+ return null;
+ }
+ cursor.moveToNext();
+ return SqliteObjectMapper.constructAsyncRegistration(cursor);
+ }
+ }
+
+ @Override
+ public void updateRetryCount(AsyncRegistration asyncRegistration) throws DatastoreException {
+ ContentValues values = new ContentValues();
+ values.put(
+ MeasurementTables.AsyncRegistrationContract.RETRY_COUNT,
+ asyncRegistration.getRetryCount());
+ long rows =
+ mSQLTransaction
+ .getDatabase()
+ .update(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ values,
+ MeasurementTables.AsyncRegistrationContract.ID + " = ?",
+ new String[] {asyncRegistration.getId()});
+ if (rows != 1) {
+ throw new DatastoreException("Retry Count update failed.");
+ }
+ }
+
+ private int getNumReportsPerDestination(
+ String tableName,
+ String columnName,
+ Uri attributionDestination,
+ @EventSurfaceType int destinationType)
+ throws DatastoreException {
+ Optional<Uri> destinationBaseUri = extractBaseUri(attributionDestination, destinationType);
+ if (!destinationBaseUri.isPresent()) {
+ throw new IllegalStateException("extractBaseUri failed for destination.");
+ }
+
+ // Example: https://destination.com
+ String noSubdomainOrPostfixMatch =
+ DatabaseUtils.sqlEscapeString(
+ destinationBaseUri.get().getScheme()
+ + "://"
+ + destinationBaseUri.get().getHost());
+
+ // Example: https://subdomain.destination.com/path
+ String subdomainAndPostfixMatch =
+ DatabaseUtils.sqlEscapeString(
+ destinationBaseUri.get().getScheme()
+ + "://%."
+ + destinationBaseUri.get().getHost()
+ + "/%");
+
+ // Example: https://subdomain.destination.com
+ String subdomainMatch =
+ DatabaseUtils.sqlEscapeString(
+ destinationBaseUri.get().getScheme()
+ + "://%."
+ + destinationBaseUri.get().getHost());
+
+ // Example: https://destination.com/path
+ String postfixMatch =
+ DatabaseUtils.sqlEscapeString(
+ destinationBaseUri.get().getScheme()
+ + "://"
+ + destinationBaseUri.get().getHost()
+ + "/%");
+ String query;
+ if (destinationType == EventSurfaceType.WEB) {
+ query =
+ String.format(
+ Locale.ENGLISH,
+ "SELECT COUNT(*) FROM %2$s WHERE %1$s = %3$s"
+ + " OR %1$s LIKE %4$s"
+ + " OR %1$s LIKE %5$s"
+ + " OR %1$s LIKE %6$s",
+ columnName,
+ tableName,
+ noSubdomainOrPostfixMatch,
+ subdomainAndPostfixMatch,
+ subdomainMatch,
+ postfixMatch);
+ } else {
+ query =
+ String.format(
+ Locale.ENGLISH,
+ "SELECT COUNT(*) FROM %2$s WHERE"
+ + " %1$s = %3$s"
+ + " OR %1$s LIKE %4$s",
+ columnName,
+ tableName,
+ noSubdomainOrPostfixMatch,
+ postfixMatch);
+ }
+ return (int) DatabaseUtils.longForQuery(mSQLTransaction.getDatabase(), query, null);
+ }
+
+ private <T> List<T> fetchRecordsMatchingWithParameters(
+ String tableName,
+ String sourceColumnName,
+ List<String> sourceIds,
+ String triggerColumnName,
+ List<String> triggerIds,
+ Function<Cursor, T> sqlMapperFunction)
+ throws DatastoreException {
+ List<T> reports = new ArrayList<>();
+ String delimitedSourceIds =
+ sourceIds.stream()
+ .map(DatabaseUtils::sqlEscapeString)
+ .collect(Collectors.joining(","));
+ String delimitedTriggerIds =
+ triggerIds.stream()
+ .map(DatabaseUtils::sqlEscapeString)
+ .collect(Collectors.joining(","));
+
+ String whereString =
+ mergeConditions(
+ /* operator = */ " OR ",
+ sourceColumnName + " IN (" + delimitedSourceIds + ")",
+ triggerColumnName + " IN (" + delimitedTriggerIds + ")");
+ try (Cursor cursor =
+ mSQLTransaction
+ .getDatabase()
+ .rawQuery(
+ String.format(
+ Locale.ENGLISH,
+ "SELECT * FROM %1$s WHERE " + whereString,
+ tableName),
+ null)) {
+ while (cursor.moveToNext()) {
+ reports.add(sqlMapperFunction.apply(cursor));
+ }
+ }
+ return reports;
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/MeasurementTables.java b/adservices/service-core/java/com/android/adservices/data/measurement/MeasurementTables.java
index 6e8873191..e4795e31b 100644
--- a/adservices/service-core/java/com/android/adservices/data/measurement/MeasurementTables.java
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/MeasurementTables.java
@@ -16,6 +16,8 @@
package com.android.adservices.data.measurement;
+import com.android.adservices.data.measurement.migration.MeasurementTablesDeprecated;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -47,18 +49,21 @@ public final class MeasurementTables {
public interface AsyncRegistrationContract {
String TABLE = MSMT_TABLE_PREFIX + "async_registration_contract";
String ID = "_id";
+ String ENROLLMENT_ID = "enrollment_id";
String REGISTRATION_URI = "registration_uri";
String TOP_ORIGIN = "top_origin";
- String INPUT_EVENT = "input_event";
- String REDIRECT = "redirect";
+ String SOURCE_TYPE = "source_type";
+ String REDIRECT_TYPE = "redirect_type";
+ String REDIRECT_COUNT = "redirect_count";
String REGISTRANT = "registrant";
- String SCHEDULE_TIME = "scheduled_time";
+ String REQUEST_TIME = "request_time";
String RETRY_COUNT = "retry_count";
- String LAST_TIME_PROCESSING = "last_processing_time";
+ String LAST_PROCESSING_TIME = "last_processing_time";
String TYPE = "type";
String WEB_DESTINATION = "web_destination";
String OS_DESTINATION = "os_destination";
String VERIFIED_DESTINATION = "verified_destination";
+ String DEBUG_KEY_ALLOWED = "debug_key_allowed";
}
/** Contract for Source. */
@@ -102,6 +107,7 @@ public final class MeasurementTables {
String AGGREGATE_TRIGGER_DATA = "aggregate_trigger_data";
String AGGREGATE_VALUES = "aggregate_values";
String FILTERS = "filters";
+ String NOT_FILTERS = "not_filters";
String DEBUG_KEY = "debug_key";
}
@@ -109,7 +115,7 @@ public final class MeasurementTables {
public interface EventReportContract {
String TABLE = MSMT_TABLE_PREFIX + "event_report";
String ID = "_id";
- String SOURCE_ID = "source_id";
+ String SOURCE_EVENT_ID = "source_event_id";
String ATTRIBUTION_DESTINATION = "attribution_destination";
String REPORT_TIME = "report_time";
String TRIGGER_DATA = "trigger_data";
@@ -117,11 +123,14 @@ public final class MeasurementTables {
String TRIGGER_DEDUP_KEY = "trigger_dedup_key";
String TRIGGER_TIME = "trigger_time";
String STATUS = "status";
+ String DEBUG_REPORT_STATUS = "debug_report_status";
String SOURCE_TYPE = "source_type";
String ENROLLMENT_ID = "enrollment_id";
String RANDOMIZED_TRIGGER_RATE = "randomized_trigger_rate";
String SOURCE_DEBUG_KEY = "source_debug_key";
String TRIGGER_DEBUG_KEY = "trigger_debug_key";
+ String SOURCE_ID = "source_id";
+ String TRIGGER_ID = "trigger_id";
}
/** Contract for Attribution rate limit. */
@@ -135,6 +144,8 @@ public final class MeasurementTables {
String TRIGGER_TIME = "trigger_time";
String REGISTRANT = "registrant";
String ENROLLMENT_ID = "enrollment_id";
+ String SOURCE_ID = "source_id";
+ String TRIGGER_ID = "trigger_id";
}
/** Contract for Unencrypted aggregate payload. */
@@ -148,9 +159,12 @@ public final class MeasurementTables {
String ENROLLMENT_ID = "enrollment_id";
String DEBUG_CLEARTEXT_PAYLOAD = "debug_cleartext_payload";
String STATUS = "status";
+ String DEBUG_REPORT_STATUS = "debug_report_status";
String API_VERSION = "api_version";
String SOURCE_DEBUG_KEY = "source_debug_key";
String TRIGGER_DEBUG_KEY = "trigger_debug_key";
+ String SOURCE_ID = "source_id";
+ String TRIGGER_ID = "trigger_id";
}
/** Contract for aggregate encryption key. */
@@ -178,19 +192,57 @@ public final class MeasurementTables {
+ " TEXT, "
+ AsyncRegistrationContract.TOP_ORIGIN
+ " TEXT, "
- + AsyncRegistrationContract.REDIRECT
+ + MeasurementTablesDeprecated.AsyncRegistration.REDIRECT
+ + " INTEGER, "
+ + MeasurementTablesDeprecated.AsyncRegistration.INPUT_EVENT
+ + " INTEGER, "
+ + AsyncRegistrationContract.REGISTRANT
+ + " TEXT, "
+ + AsyncRegistrationContract.REQUEST_TIME
+ + " INTEGER, "
+ + AsyncRegistrationContract.RETRY_COUNT
+ + " INTEGER, "
+ + AsyncRegistrationContract.LAST_PROCESSING_TIME
+ + " INTEGER, "
+ + AsyncRegistrationContract.TYPE
+ + " INTEGER "
+ + ")";
+
+ public static final String CREATE_TABLE_ASYNC_REGISTRATION_V2 =
+ "CREATE TABLE "
+ + AsyncRegistrationContract.TABLE
+ + " ("
+ + AsyncRegistrationContract.ID
+ + " TEXT PRIMARY KEY NOT NULL, "
+ + AsyncRegistrationContract.ENROLLMENT_ID
+ + " TEXT, "
+ + AsyncRegistrationContract.REGISTRATION_URI
+ + " TEXT, "
+ + AsyncRegistrationContract.WEB_DESTINATION
+ + " TEXT, "
+ + AsyncRegistrationContract.OS_DESTINATION
+ + " TEXT, "
+ + AsyncRegistrationContract.VERIFIED_DESTINATION
+ + " TEXT, "
+ + AsyncRegistrationContract.TOP_ORIGIN
+ + " TEXT, "
+ + AsyncRegistrationContract.REDIRECT_TYPE
+ " INTEGER, "
- + AsyncRegistrationContract.INPUT_EVENT
+ + AsyncRegistrationContract.REDIRECT_COUNT
+ + " INTEGER, "
+ + AsyncRegistrationContract.SOURCE_TYPE
+ " INTEGER, "
+ AsyncRegistrationContract.REGISTRANT
+ " TEXT, "
- + AsyncRegistrationContract.SCHEDULE_TIME
+ + AsyncRegistrationContract.REQUEST_TIME
+ " INTEGER, "
+ AsyncRegistrationContract.RETRY_COUNT
+ " INTEGER, "
- + AsyncRegistrationContract.LAST_TIME_PROCESSING
+ + AsyncRegistrationContract.LAST_PROCESSING_TIME
+ " INTEGER, "
+ AsyncRegistrationContract.TYPE
+ + " INTEGER, "
+ + AsyncRegistrationContract.DEBUG_KEY_ALLOWED
+ " INTEGER "
+ ")";
@@ -274,6 +326,38 @@ public final class MeasurementTables {
+ " INTEGER "
+ ")";
+ public static final String CREATE_TABLE_TRIGGER_V2 =
+ "CREATE TABLE "
+ + TriggerContract.TABLE
+ + " ("
+ + TriggerContract.ID
+ + " TEXT PRIMARY KEY NOT NULL, "
+ + TriggerContract.ATTRIBUTION_DESTINATION
+ + " TEXT, "
+ + TriggerContract.DESTINATION_TYPE
+ + " INTEGER, "
+ + TriggerContract.ENROLLMENT_ID
+ + " TEXT, "
+ + TriggerContract.TRIGGER_TIME
+ + " INTEGER, "
+ + TriggerContract.EVENT_TRIGGERS
+ + " TEXT, "
+ + TriggerContract.STATUS
+ + " INTEGER, "
+ + TriggerContract.REGISTRANT
+ + " TEXT, "
+ + TriggerContract.AGGREGATE_TRIGGER_DATA
+ + " TEXT, "
+ + TriggerContract.AGGREGATE_VALUES
+ + " TEXT, "
+ + TriggerContract.FILTERS
+ + " TEXT, "
+ + TriggerContract.NOT_FILTERS
+ + " TEXT, "
+ + TriggerContract.DEBUG_KEY
+ + " INTEGER "
+ + ")";
+
public static final String CREATE_TABLE_EVENT_REPORT_V1 =
"CREATE TABLE "
+ EventReportContract.TABLE
@@ -304,13 +388,13 @@ public final class MeasurementTables {
+ " DOUBLE "
+ ")";
- public static final String CREATE_TABLE_EVENT_REPORT_V2 =
+ public static final String CREATE_TABLE_EVENT_REPORT_V3 =
"CREATE TABLE "
+ EventReportContract.TABLE
+ " ("
+ EventReportContract.ID
+ " TEXT PRIMARY KEY NOT NULL, "
- + EventReportContract.SOURCE_ID
+ + EventReportContract.SOURCE_EVENT_ID
+ " INTEGER, "
+ EventReportContract.ENROLLMENT_ID
+ " TEXT, "
@@ -328,6 +412,8 @@ public final class MeasurementTables {
+ " INTEGER, "
+ EventReportContract.STATUS
+ " INTEGER, "
+ + EventReportContract.DEBUG_REPORT_STATUS
+ + " INTEGER, "
+ EventReportContract.SOURCE_TYPE
+ " TEXT, "
+ EventReportContract.RANDOMIZED_TRIGGER_RATE
@@ -335,7 +421,25 @@ public final class MeasurementTables {
+ EventReportContract.SOURCE_DEBUG_KEY
+ " INTEGER, "
+ EventReportContract.TRIGGER_DEBUG_KEY
- + " INTEGER "
+ + " INTEGER, "
+ + EventReportContract.SOURCE_ID
+ + " TEXT, "
+ + EventReportContract.TRIGGER_ID
+ + " TEXT, "
+ + "FOREIGN KEY ("
+ + EventReportContract.SOURCE_ID
+ + ") REFERENCES "
+ + SourceContract.TABLE
+ + "("
+ + SourceContract.ID
+ + ") ON DELETE CASCADE, "
+ + "FOREIGN KEY ("
+ + EventReportContract.TRIGGER_ID
+ + ") REFERENCES "
+ + TriggerContract.TABLE
+ + "("
+ + TriggerContract.ID
+ + ") ON DELETE CASCADE"
+ ")";
public static final String CREATE_TABLE_ATTRIBUTION_V1 =
@@ -360,6 +464,46 @@ public final class MeasurementTables {
+ " TEXT "
+ ")";
+ public static final String CREATE_TABLE_ATTRIBUTION_V3 =
+ "CREATE TABLE "
+ + AttributionContract.TABLE
+ + " ("
+ + AttributionContract.ID
+ + " TEXT PRIMARY KEY NOT NULL, "
+ + AttributionContract.SOURCE_SITE
+ + " TEXT, "
+ + AttributionContract.SOURCE_ORIGIN
+ + " TEXT, "
+ + AttributionContract.DESTINATION_SITE
+ + " TEXT, "
+ + AttributionContract.DESTINATION_ORIGIN
+ + " TEXT, "
+ + AttributionContract.ENROLLMENT_ID
+ + " TEXT, "
+ + AttributionContract.TRIGGER_TIME
+ + " INTEGER, "
+ + AttributionContract.REGISTRANT
+ + " TEXT, "
+ + AttributionContract.SOURCE_ID
+ + " TEXT, "
+ + AttributionContract.TRIGGER_ID
+ + " TEXT, "
+ + "FOREIGN KEY ("
+ + AttributionContract.SOURCE_ID
+ + ") REFERENCES "
+ + SourceContract.TABLE
+ + "("
+ + SourceContract.ID
+ + ") ON DELETE CASCADE, "
+ + "FOREIGN KEY ("
+ + AttributionContract.TRIGGER_ID
+ + ") REFERENCES "
+ + TriggerContract.TABLE
+ + "("
+ + TriggerContract.ID
+ + ") ON DELETE CASCADE"
+ + ")";
+
public static final String CREATE_TABLE_AGGREGATE_REPORT_V1 =
"CREATE TABLE "
+ AggregateReport.TABLE
@@ -384,7 +528,7 @@ public final class MeasurementTables {
+ " TEXT "
+ ")";
- public static final String CREATE_TABLE_AGGREGATE_REPORT_V2 =
+ public static final String CREATE_TABLE_AGGREGATE_REPORT_V3 =
"CREATE TABLE "
+ AggregateReport.TABLE
+ " ("
@@ -404,12 +548,32 @@ public final class MeasurementTables {
+ " TEXT, "
+ AggregateReport.STATUS
+ " INTEGER, "
+ + AggregateReport.DEBUG_REPORT_STATUS
+ + " INTEGER, "
+ AggregateReport.API_VERSION
+ " TEXT, "
+ AggregateReport.SOURCE_DEBUG_KEY
+ " INTEGER, "
+ AggregateReport.TRIGGER_DEBUG_KEY
- + " INTEGER "
+ + " INTEGER, "
+ + AggregateReport.SOURCE_ID
+ + " TEXT, "
+ + AggregateReport.TRIGGER_ID
+ + " TEXT, "
+ + "FOREIGN KEY ("
+ + AggregateReport.SOURCE_ID
+ + ") REFERENCES "
+ + SourceContract.TABLE
+ + "("
+ + SourceContract.ID
+ + ") ON DELETE CASCADE "
+ + "FOREIGN KEY ("
+ + AggregateReport.TRIGGER_ID
+ + ") REFERENCES "
+ + TriggerContract.TABLE
+ + "("
+ + TriggerContract.ID
+ + ") ON DELETE CASCADE"
+ ")";
public static final String CREATE_TABLE_AGGREGATE_ENCRYPTION_KEY_V1 =
@@ -515,12 +679,12 @@ public final class MeasurementTables {
Collections.unmodifiableList(
Arrays.asList(
CREATE_TABLE_SOURCE_V1,
- CREATE_TABLE_TRIGGER_V1,
- CREATE_TABLE_EVENT_REPORT_V2,
- CREATE_TABLE_ATTRIBUTION_V1,
- CREATE_TABLE_AGGREGATE_REPORT_V2,
+ CREATE_TABLE_TRIGGER_V2,
+ CREATE_TABLE_EVENT_REPORT_V3,
+ CREATE_TABLE_ATTRIBUTION_V3,
+ CREATE_TABLE_AGGREGATE_REPORT_V3,
CREATE_TABLE_AGGREGATE_ENCRYPTION_KEY_V1,
- CREATE_TABLE_ASYNC_REGISTRATION_V1));
+ CREATE_TABLE_ASYNC_REGISTRATION_V2));
// Private constructor to prevent instantiation.
private MeasurementTables() {
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/SQLDatastoreManager.java b/adservices/service-core/java/com/android/adservices/data/measurement/SQLDatastoreManager.java
index 5c6ebfeef..5cf44720a 100644
--- a/adservices/service-core/java/com/android/adservices/data/measurement/SQLDatastoreManager.java
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/SQLDatastoreManager.java
@@ -16,6 +16,7 @@
package com.android.adservices.data.measurement;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
@@ -24,17 +25,14 @@ import com.android.adservices.data.DbHelper;
import com.android.adservices.service.FlagsFactory;
import com.android.internal.annotations.VisibleForTesting;
-/**
- * Datastore manager for SQLite database.
- */
-class SQLDatastoreManager extends DatastoreManager {
+/** Datastore manager for SQLite database. */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class SQLDatastoreManager extends DatastoreManager {
private final DbHelper mDbHelper;
private static SQLDatastoreManager sSingleton;
- /**
- * Acquire an instance of {@link SQLDatastoreManager}.
- */
+ /** Acquire an instance of {@link SQLDatastoreManager}. */
static synchronized SQLDatastoreManager getInstance(Context context) {
if (sSingleton == null) {
sSingleton = new SQLDatastoreManager(context);
@@ -46,6 +44,12 @@ class SQLDatastoreManager extends DatastoreManager {
mDbHelper = DbHelper.getInstance(context);
}
+ /** Get {@link DatastoreManager} instance with a {@link DbHelper}. */
+ @VisibleForTesting
+ public SQLDatastoreManager(@NonNull DbHelper dbHelper) {
+ mDbHelper = dbHelper;
+ }
+
@Override
@Nullable
protected ITransaction createNewTransaction() {
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/SqliteObjectMapper.java b/adservices/service-core/java/com/android/adservices/data/measurement/SqliteObjectMapper.java
index 2fa28cf2d..e8a7319fb 100644
--- a/adservices/service-core/java/com/android/adservices/data/measurement/SqliteObjectMapper.java
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/SqliteObjectMapper.java
@@ -21,11 +21,13 @@ import static java.util.function.Predicate.not;
import android.database.Cursor;
import android.net.Uri;
+import com.android.adservices.service.measurement.AsyncRegistration;
import com.android.adservices.service.measurement.EventReport;
import com.android.adservices.service.measurement.Source;
import com.android.adservices.service.measurement.Trigger;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import java.util.Arrays;
import java.util.function.Function;
@@ -41,15 +43,21 @@ public class SqliteObjectMapper {
EventReport.Builder builder = new EventReport.Builder();
setTextColumn(cursor, MeasurementTables.EventReportContract.ID,
builder::setId);
- setLongColumn(cursor, MeasurementTables.EventReportContract.SOURCE_ID,
- builder::setSourceId);
+ setUnsignedLongColumn(
+ cursor,
+ MeasurementTables.EventReportContract.SOURCE_EVENT_ID,
+ builder::setSourceEventId);
setLongColumn(cursor, MeasurementTables.EventReportContract.TRIGGER_PRIORITY,
builder::setTriggerPriority);
setIntColumn(cursor, MeasurementTables.EventReportContract.STATUS,
builder::setStatus);
- setLongColumn(cursor, MeasurementTables.EventReportContract.TRIGGER_DATA,
+ setIntColumn(
+ cursor,
+ MeasurementTables.EventReportContract.DEBUG_REPORT_STATUS,
+ builder::setDebugReportStatus);
+ setUnsignedLongColumn(cursor, MeasurementTables.EventReportContract.TRIGGER_DATA,
builder::setTriggerData);
- setLongColumn(cursor, MeasurementTables.EventReportContract.TRIGGER_DEDUP_KEY,
+ setUnsignedLongColumn(cursor, MeasurementTables.EventReportContract.TRIGGER_DEDUP_KEY,
builder::setTriggerDedupKey);
setUriColumn(cursor, MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
builder::setAttributionDestination);
@@ -63,14 +71,18 @@ public class SqliteObjectMapper {
(enumValue) -> builder.setSourceType(Source.SourceType.valueOf(enumValue)));
setDoubleColumn(cursor, MeasurementTables.EventReportContract.RANDOMIZED_TRIGGER_RATE,
builder::setRandomizedTriggerRate);
- setLongColumn(
+ setUnsignedLongColumn(
cursor,
MeasurementTables.EventReportContract.SOURCE_DEBUG_KEY,
builder::setSourceDebugKey);
- setLongColumn(
+ setUnsignedLongColumn(
cursor,
MeasurementTables.EventReportContract.TRIGGER_DEBUG_KEY,
builder::setTriggerDebugKey);
+ setTextColumn(
+ cursor, MeasurementTables.EventReportContract.SOURCE_ID, builder::setSourceId);
+ setTextColumn(
+ cursor, MeasurementTables.EventReportContract.TRIGGER_ID, builder::setTriggerId);
return builder.build();
}
@@ -81,7 +93,7 @@ public class SqliteObjectMapper {
Source.Builder builder = new Source.Builder();
setTextColumn(cursor, MeasurementTables.SourceContract.ID,
builder::setId);
- setLongColumn(cursor, MeasurementTables.SourceContract.EVENT_ID,
+ setUnsignedLongColumn(cursor, MeasurementTables.SourceContract.EVENT_ID,
builder::setEventId);
setLongColumn(cursor, MeasurementTables.SourceContract.PRIORITY,
builder::setPriority);
@@ -106,10 +118,10 @@ public class SqliteObjectMapper {
setLongColumn(cursor, MeasurementTables.SourceContract.EVENT_TIME,
builder::setEventTime);
setTextColumn(cursor, MeasurementTables.SourceContract.DEDUP_KEYS,
- (concatArray) -> builder.setDedupKeys(Arrays.stream(concatArray.split(","))
+ (concatArray) -> builder.setDedupKeys(Arrays.stream(concatArray.split(","))
.map(String::trim)
.filter(not(String::isEmpty))
- .map(Long::parseLong)
+ .map(UnsignedLong::new)
.collect(Collectors.toList())));
setIntColumn(cursor, MeasurementTables.SourceContract.STATUS,
builder::setStatus);
@@ -124,12 +136,13 @@ public class SqliteObjectMapper {
setBooleanColumn(cursor, MeasurementTables.SourceContract.IS_INSTALL_ATTRIBUTED,
builder::setInstallAttributed);
setTextColumn(cursor, MeasurementTables.SourceContract.FILTER_DATA,
- builder::setAggregateFilterData);
+ builder::setFilterData);
setTextColumn(cursor, MeasurementTables.SourceContract.AGGREGATE_SOURCE,
builder::setAggregateSource);
setIntColumn(cursor, MeasurementTables.SourceContract.AGGREGATE_CONTRIBUTIONS,
builder::setAggregateContributions);
- setLongColumn(cursor, MeasurementTables.SourceContract.DEBUG_KEY, builder::setDebugKey);
+ setUnsignedLongColumn(cursor, MeasurementTables.SourceContract.DEBUG_KEY,
+ builder::setDebugKey);
return builder.build();
}
@@ -159,7 +172,10 @@ public class SqliteObjectMapper {
setTextColumn(cursor, MeasurementTables.TriggerContract.AGGREGATE_VALUES,
builder::setAggregateValues);
setTextColumn(cursor, MeasurementTables.TriggerContract.FILTERS, builder::setFilters);
- setLongColumn(cursor, MeasurementTables.TriggerContract.DEBUG_KEY, builder::setDebugKey);
+ setTextColumn(cursor, MeasurementTables.TriggerContract.NOT_FILTERS,
+ builder::setNotFilters);
+ setUnsignedLongColumn(cursor, MeasurementTables.TriggerContract.DEBUG_KEY,
+ builder::setDebugKey);
return builder.build();
}
@@ -184,16 +200,22 @@ public class SqliteObjectMapper {
builder::setDebugCleartextPayload);
setIntColumn(cursor, MeasurementTables.AggregateReport.STATUS,
builder::setStatus);
+ setIntColumn(
+ cursor,
+ MeasurementTables.AggregateReport.DEBUG_REPORT_STATUS,
+ builder::setDebugReportStatus);
setTextColumn(cursor, MeasurementTables.AggregateReport.API_VERSION,
builder::setApiVersion);
- setLongColumn(
+ setUnsignedLongColumn(
cursor,
MeasurementTables.AggregateReport.SOURCE_DEBUG_KEY,
builder::setSourceDebugKey);
- setLongColumn(
+ setUnsignedLongColumn(
cursor,
MeasurementTables.AggregateReport.TRIGGER_DEBUG_KEY,
builder::setTriggerDebugKey);
+ setTextColumn(cursor, MeasurementTables.AggregateReport.SOURCE_ID, builder::setSourceId);
+ setTextColumn(cursor, MeasurementTables.AggregateReport.TRIGGER_ID, builder::setTriggerId);
return builder.build();
}
@@ -213,6 +235,72 @@ public class SqliteObjectMapper {
return builder.build();
}
+ /** Create {@link AsyncRegistration} object from SQLite datastore. */
+ public static AsyncRegistration constructAsyncRegistration(Cursor cursor) {
+ AsyncRegistration.Builder builder = new AsyncRegistration.Builder();
+ setTextColumn(cursor, MeasurementTables.AsyncRegistrationContract.ID, builder::setId);
+ setTextColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.ENROLLMENT_ID,
+ builder::setEnrollmentId);
+ setUriColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.WEB_DESTINATION,
+ builder::setWebDestination);
+ setUriColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.OS_DESTINATION,
+ builder::setOsDestination);
+ setUriColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.REGISTRATION_URI,
+ builder::setRegistrationUri);
+ setUriColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.VERIFIED_DESTINATION,
+ builder::setVerifiedDestination);
+ setUriColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.TOP_ORIGIN,
+ builder::setTopOrigin);
+ setIntColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.REDIRECT_TYPE,
+ builder::setRedirectType);
+ setIntColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.REDIRECT_COUNT,
+ builder::setRedirectCount);
+ setIntColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.SOURCE_TYPE,
+ (enumValue) ->
+ builder.setSourceType(
+ enumValue == null ? null : Source.SourceType.values()[enumValue]));
+ setUriColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.REGISTRANT,
+ builder::setRegistrant);
+ setLongColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.REQUEST_TIME,
+ builder::setRequestTime);
+ setLongColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.RETRY_COUNT,
+ builder::setRetryCount);
+ setLongColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.LAST_PROCESSING_TIME,
+ builder::setLastProcessingTime);
+ setIntColumn(cursor, MeasurementTables.AsyncRegistrationContract.TYPE, builder::setType);
+ setBooleanColumn(
+ cursor,
+ MeasurementTables.AsyncRegistrationContract.DEBUG_KEY_ALLOWED,
+ builder::setDebugKeyAllowed);
+ return builder.build();
+ }
+
private static <BuilderType> void setUriColumn(Cursor cursor, String column, Function<Uri,
BuilderType> setter) {
setColumnValue(cursor, column, cursor::getString, (x) -> setter.apply(Uri.parse(x)));
@@ -233,6 +321,12 @@ public class SqliteObjectMapper {
setColumnValue(cursor, column, cursor::getLong, setter);
}
+ private static <BuilderType> void setUnsignedLongColumn(Cursor cursor, String column,
+ Function<UnsignedLong, BuilderType> setter) {
+ setColumnValue(cursor, column, cursor::getLong, signedLong ->
+ setter.apply(new UnsignedLong(signedLong)));
+ }
+
private static <BuilderType> void setTextColumn(Cursor cursor, String column,
Function<String, BuilderType> setter) {
setColumnValue(cursor, column, cursor::getString, setter);
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/deletion/MeasurementDataDeleter.java b/adservices/service-core/java/com/android/adservices/data/measurement/deletion/MeasurementDataDeleter.java
new file mode 100644
index 000000000..af13f872d
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/deletion/MeasurementDataDeleter.java
@@ -0,0 +1,160 @@
+/*
+ * 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.data.measurement.deletion;
+
+import android.adservices.measurement.DeletionParam;
+import android.adservices.measurement.DeletionRequest;
+import android.annotation.NonNull;
+import android.net.Uri;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.IMeasurementDao;
+import com.android.adservices.service.measurement.EventReport;
+import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.Trigger;
+import com.android.adservices.service.measurement.aggregation.AggregateHistogramContribution;
+import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * Facilitates deletion of measurement data from the database, for e.g. deletion of sources,
+ * triggers, reports, attributions.
+ */
+public class MeasurementDataDeleter {
+ static final String ANDROID_APP_SCHEME = "android-app";
+ private static final int AGGREGATE_CONTRIBUTIONS_VALUE_MINIMUM_LIMIT = 0;
+
+ private final DatastoreManager mDatastoreManager;
+
+ public MeasurementDataDeleter(DatastoreManager datastoreManager) {
+ mDatastoreManager = datastoreManager;
+ }
+
+ /**
+ * Deletes all measurement data owned by a registrant and optionally providing an origin uri
+ * and/or a range of dates.
+ *
+ * @param deletionParam contains registrant, time range, sites to consider for deletion
+ * @return true if deletion was successful, false otherwise
+ */
+ public boolean delete(@NonNull DeletionParam deletionParam) {
+ return mDatastoreManager.runInTransaction(
+ (dao) -> {
+ List<String> sourceIds =
+ dao.fetchMatchingSources(
+ getRegistrant(deletionParam.getPackageName()),
+ deletionParam.getStart(),
+ deletionParam.getEnd(),
+ deletionParam.getOriginUris(),
+ deletionParam.getDomainUris(),
+ deletionParam.getMatchBehavior());
+ List<String> triggerIds =
+ dao.fetchMatchingTriggers(
+ getRegistrant(deletionParam.getPackageName()),
+ deletionParam.getStart(),
+ deletionParam.getEnd(),
+ deletionParam.getOriginUris(),
+ deletionParam.getDomainUris(),
+ deletionParam.getMatchBehavior());
+
+ // Rest aggregate contributions and dedup keys on sources for triggers to be
+ // deleted.
+ List<AggregateReport> aggregateReports =
+ dao.fetchMatchingAggregateReports(sourceIds, triggerIds);
+ resetAggregateContributions(dao, aggregateReports);
+
+ List<EventReport> eventReports =
+ dao.fetchMatchingEventReports(sourceIds, triggerIds);
+ resetDedupKeys(dao, eventReports);
+
+ // Delete sources and triggers, that'll take care of deleting related reports
+ // and attributions
+ if (deletionParam.getDeletionMode() == DeletionRequest.DELETION_MODE_ALL) {
+ dao.deleteSources(sourceIds);
+ dao.deleteTriggers(triggerIds);
+ return;
+ }
+
+ // Mark reports for deletion for DELETION_MODE_EXCLUDE_INTERNAL_DATA
+ for (EventReport eventReport : eventReports) {
+ dao.markEventReportStatus(
+ eventReport.getId(), EventReport.Status.MARKED_TO_DELETE);
+ }
+
+ for (AggregateReport aggregateReport : aggregateReports) {
+ dao.markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.MARKED_TO_DELETE);
+ }
+
+ // Finally mark sources and triggers for deletion
+ dao.updateSourceStatus(sourceIds, Source.Status.MARKED_TO_DELETE);
+ dao.updateTriggerStatus(triggerIds, Trigger.Status.MARKED_TO_DELETE);
+ });
+ }
+
+ @VisibleForTesting
+ void resetAggregateContributions(
+ @NonNull IMeasurementDao dao, @NonNull List<AggregateReport> aggregateReports)
+ throws DatastoreException {
+ for (AggregateReport report : aggregateReports) {
+ if (report.getSourceId() == null) {
+ LogUtil.e("SourceId is null on event report.");
+ return;
+ }
+
+ Source source = dao.getSource(report.getSourceId());
+ int aggregateHistogramContributionsSum =
+ report.getAggregateAttributionData().getContributions().stream()
+ .mapToInt(AggregateHistogramContribution::getValue)
+ .sum();
+
+ int newAggregateContributionsSum =
+ Math.max(
+ (source.getAggregateContributions()
+ - aggregateHistogramContributionsSum),
+ AGGREGATE_CONTRIBUTIONS_VALUE_MINIMUM_LIMIT);
+
+ source.setAggregateContributions(newAggregateContributionsSum);
+
+ // Update in the DB
+ dao.updateSourceAggregateContributions(source);
+ }
+ }
+
+ @VisibleForTesting
+ void resetDedupKeys(@NonNull IMeasurementDao dao, @NonNull List<EventReport> eventReports)
+ throws DatastoreException {
+ for (EventReport report : eventReports) {
+ if (report.getSourceId() == null) {
+ LogUtil.e("SourceId on the event report is null.");
+ return;
+ }
+
+ Source source = dao.getSource(report.getSourceId());
+ source.getDedupKeys().remove(report.getTriggerDedupKey());
+ dao.updateSourceDedupKeys(source);
+ }
+ }
+
+ private Uri getRegistrant(String packageName) {
+ return Uri.parse(ANDROID_APP_SCHEME + "://" + packageName);
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/migration/MeasurementDbMigratorV3.java b/adservices/service-core/java/com/android/adservices/data/measurement/migration/MeasurementDbMigratorV3.java
new file mode 100644
index 000000000..de43dd1f9
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/migration/MeasurementDbMigratorV3.java
@@ -0,0 +1,220 @@
+/*
+ * 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.data.measurement.migration;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.measurement.MeasurementTables;
+import com.android.adservices.service.measurement.util.BaseUriExtractor;
+import com.android.adservices.service.measurement.util.Web;
+
+import java.util.Optional;
+
+/** Migrates Measurement DB from user version 2 to 3. */
+public class MeasurementDbMigratorV3 extends AbstractMeasurementDbMigrator {
+ private static final String ANDROID_APP_SCHEME = "android-app";
+ private static final String EVENT_REPORT_CONTRACT_BACKUP =
+ MeasurementTables.EventReportContract.TABLE + "_backup";
+ private static final String AGGREGATE_REPORT_CONTRACT_BACKUP =
+ MeasurementTables.AggregateReport.TABLE + "_backup";
+ private static final String ATTRIBUTION_CONTRACT_BACKUP =
+ MeasurementTables.AttributionContract.TABLE + "_backup";
+ private static final String ATTRIBUTION_CREATE_INDEX_SS_SO_DS_DO_EI_TT =
+ "CREATE INDEX "
+ + MeasurementTables.INDEX_PREFIX
+ + MeasurementTables.AttributionContract.TABLE
+ + "_ss_so_ds_do_ei_tt"
+ + " ON "
+ + MeasurementTables.AttributionContract.TABLE
+ + "("
+ + MeasurementTables.AttributionContract.SOURCE_SITE
+ + ", "
+ + MeasurementTables.AttributionContract.SOURCE_ORIGIN
+ + ", "
+ + MeasurementTables.AttributionContract.DESTINATION_SITE
+ + ", "
+ + MeasurementTables.AttributionContract.DESTINATION_ORIGIN
+ + ", "
+ + MeasurementTables.AttributionContract.ENROLLMENT_ID
+ + ", "
+ + MeasurementTables.AttributionContract.TRIGGER_TIME
+ + ")";
+
+ private static final String[] ALTER_STATEMENTS_VER_3 = {
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ MeasurementTables.AsyncRegistrationContract.DEBUG_KEY_ALLOWED),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s TEXT",
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ MeasurementTables.AsyncRegistrationContract.ENROLLMENT_ID),
+ String.format(
+ "ALTER TABLE %1$s " + "RENAME COLUMN %2$s TO %3$s",
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ MeasurementTablesDeprecated.AsyncRegistration.INPUT_EVENT,
+ MeasurementTables.AsyncRegistrationContract.SOURCE_TYPE),
+ String.format(
+ "ALTER TABLE %1$s " + "RENAME COLUMN %2$s TO %3$s",
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ MeasurementTablesDeprecated.AsyncRegistration.REDIRECT,
+ MeasurementTables.AsyncRegistrationContract.REDIRECT_TYPE),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ MeasurementTables.AsyncRegistrationContract.REDIRECT_COUNT),
+ String.format(
+ "ALTER TABLE %1$s RENAME COLUMN %2$s TO %3$s",
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.EventReportContract.SOURCE_ID,
+ MeasurementTables.EventReportContract.SOURCE_EVENT_ID),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.EventReportContract.SOURCE_ID),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.EventReportContract.TRIGGER_ID),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.AggregateReport.TABLE,
+ MeasurementTables.AggregateReport.SOURCE_ID),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.AggregateReport.TABLE,
+ MeasurementTables.AggregateReport.TRIGGER_ID),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.AttributionContract.TABLE,
+ MeasurementTables.AttributionContract.SOURCE_ID),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.AttributionContract.TABLE,
+ MeasurementTables.AttributionContract.TRIGGER_ID),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.EventReportContract.TABLE,
+ MeasurementTables.EventReportContract.DEBUG_REPORT_STATUS),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s INTEGER",
+ MeasurementTables.AggregateReport.TABLE,
+ MeasurementTables.AggregateReport.DEBUG_REPORT_STATUS),
+ String.format(
+ "ALTER TABLE %1$s ADD %2$s TEXT",
+ MeasurementTables.TriggerContract.TABLE,
+ MeasurementTables.TriggerContract.NOT_FILTERS),
+
+ // SQLite does not support ALTER TABLE statement with foreign keys
+ String.format(
+ "ALTER TABLE %1$s RENAME TO %2$s",
+ MeasurementTables.EventReportContract.TABLE, EVENT_REPORT_CONTRACT_BACKUP),
+ MeasurementTables.CREATE_TABLE_EVENT_REPORT_V3,
+ String.format(
+ "INSERT INTO %1$s SELECT * FROM %2$s",
+ MeasurementTables.EventReportContract.TABLE, EVENT_REPORT_CONTRACT_BACKUP),
+ String.format("DROP TABLE %1$s", EVENT_REPORT_CONTRACT_BACKUP),
+ String.format(
+ "ALTER TABLE %1$s RENAME TO %2$s",
+ MeasurementTables.AggregateReport.TABLE, AGGREGATE_REPORT_CONTRACT_BACKUP),
+ MeasurementTables.CREATE_TABLE_AGGREGATE_REPORT_V3,
+ String.format(
+ "INSERT INTO %1$s SELECT * FROM %2$s",
+ MeasurementTables.AggregateReport.TABLE, AGGREGATE_REPORT_CONTRACT_BACKUP),
+ String.format("DROP TABLE %1$s", AGGREGATE_REPORT_CONTRACT_BACKUP),
+ String.format(
+ "ALTER TABLE %1$s RENAME TO %2$s",
+ MeasurementTables.AttributionContract.TABLE, ATTRIBUTION_CONTRACT_BACKUP),
+ MeasurementTables.CREATE_TABLE_ATTRIBUTION_V3,
+ String.format(
+ "INSERT INTO %1$s SELECT * FROM %2$s",
+ MeasurementTables.AttributionContract.TABLE, ATTRIBUTION_CONTRACT_BACKUP),
+ String.format("DROP TABLE %1$s", ATTRIBUTION_CONTRACT_BACKUP),
+ ATTRIBUTION_CREATE_INDEX_SS_SO_DS_DO_EI_TT
+ };
+
+ public MeasurementDbMigratorV3() {
+ super(3);
+ }
+
+ @Override
+ protected void performMigration(SQLiteDatabase db) {
+ for (String sql : ALTER_STATEMENTS_VER_3) {
+ db.execSQL(sql);
+ }
+ migrateEventReportData(db);
+ }
+
+ private static void migrateEventReportData(SQLiteDatabase db) {
+ try (Cursor cursor = db.query(
+ MeasurementTables.EventReportContract.TABLE,
+ new String[] {
+ MeasurementTables.EventReportContract.ID,
+ MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION
+ },
+ null, null, null, null, null, null)) {
+ while (cursor.moveToNext()) {
+ updateEventReport(db, cursor);
+ }
+ }
+ }
+
+ private static void updateEventReport(SQLiteDatabase db, Cursor cursor) {
+ String id = cursor.getString(cursor.getColumnIndex(
+ MeasurementTables.EventReportContract.ID));
+ String destination = cursor.getString(cursor.getColumnIndex(
+ MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION));
+ Optional<String> baseUri = extractBaseUri(destination);
+ if (baseUri.isPresent()) {
+ ContentValues values = new ContentValues();
+ values.put(MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
+ baseUri.get());
+ long rowCount = db.update(
+ MeasurementTables.EventReportContract.TABLE,
+ values,
+ MeasurementTables.EventReportContract.ID + " = ?",
+ new String[]{id});
+ if (rowCount != 1) {
+ LogUtil.d("MeasurementDbMigratorV3: failed to update event report record.");
+ }
+ } else {
+ LogUtil.d("MeasurementDbMigratorV3: baseUri not present. %s", destination);
+ }
+ }
+
+ private static Optional<String> extractBaseUri(String destination) {
+ if (destination == null) {
+ return Optional.empty();
+ }
+ Uri uri = Uri.parse(destination);
+ if (uri.getScheme() == null || !uri.isHierarchical() || !uri.isAbsolute()) {
+ return Optional.empty();
+ }
+ if (uri.getScheme().equals(ANDROID_APP_SCHEME)) {
+ return Optional.of(BaseUriExtractor.getBaseUri(uri).toString());
+ }
+ Optional<Uri> topPrivateDomainAndScheme = Web.topPrivateDomainAndScheme(uri);
+ if (topPrivateDomainAndScheme.isPresent()) {
+ return Optional.of(topPrivateDomainAndScheme.get().toString());
+ }
+ return Optional.empty();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/migration/MeasurementTablesDeprecated.java b/adservices/service-core/java/com/android/adservices/data/measurement/migration/MeasurementTablesDeprecated.java
new file mode 100644
index 000000000..d635a0713
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/data/measurement/migration/MeasurementTablesDeprecated.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 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.data.measurement.migration;
+
+/**
+ * Container class for deprecated Measurement PPAPI table definitions and constants.
+ */
+public final class MeasurementTablesDeprecated {
+
+ /** Contract for asynchronous Registration. */
+ public interface AsyncRegistration {
+ String INPUT_EVENT = "input_event";
+ String REDIRECT = "redirect";
+ }
+
+ // Private constructor to prevent instantiation.
+ private MeasurementTablesDeprecated() {
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/data/topics/TopicsDao.java b/adservices/service-core/java/com/android/adservices/data/topics/TopicsDao.java
index db303d494..ec2822bc6 100644
--- a/adservices/service-core/java/com/android/adservices/data/topics/TopicsDao.java
+++ b/adservices/service-core/java/com/android/adservices/data/topics/TopicsDao.java
@@ -53,6 +53,7 @@ public class TopicsDao {
TopicsTables.TopTopicsContract.TABLE,
TopicsTables.BlockedTopicsContract.TABLE,
TopicsTables.EpochOriginContract.TABLE,
+ TopicsTables.TopicContributorsContract.TABLE
};
private final DbHelper mDbHelper; // Used in tests.
@@ -84,7 +85,6 @@ public class TopicsDao {
* @param epochId the epoch ID to persist
* @param appClassificationTopicsMap Map of app -> classified topics
*/
- @VisibleForTesting
public void persistAppClassificationTopics(
long epochId, @NonNull Map<String, List<Topic>> appClassificationTopicsMap) {
Objects.requireNonNull(appClassificationTopicsMap);
@@ -128,7 +128,6 @@ public class TopicsDao {
* @param epochId the epoch ID to retrieve
* @return {@link Map} a map of app -> topics
*/
- @VisibleForTesting
@NonNull
public Map<String, List<Topic>> retrieveAppClassificationTopics(long epochId) {
SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
@@ -193,7 +192,6 @@ public class TopicsDao {
* @param epochId ID of current epoch
* @param topTopics the topics list to persist into DB
*/
- @VisibleForTesting
public void persistTopTopics(long epochId, @NonNull List<Topic> topTopics) {
// topTopics the Top Topics: a list of 5 top topics and the 6th topic
// which was selected randomly. We can refer this 6th topic as the random-topic.
@@ -235,7 +233,6 @@ public class TopicsDao {
* @param epochId the epochId to retrieve the top topics.
* @return {@link List} a {@link List} of {@link Topic}
*/
- @VisibleForTesting
@NonNull
public List<Topic> retrieveTopTopics(long epochId) {
SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
@@ -564,7 +561,6 @@ public class TopicsDao {
* @param numberOfLookBackEpochs Look back numberOfLookBackEpochs.
* @return {@link Map} a Map<Topic, Set<Caller>> where Caller = App or Sdk.
*/
- @VisibleForTesting
@NonNull
public Map<Topic, Set<String>> retrieveCallerCanLearnTopicsMap(
long epochId, int numberOfLookBackEpochs) {
@@ -748,7 +744,7 @@ public class TopicsDao {
cursor.getColumnIndexOrThrow(
TopicsTables.ReturnedTopicContract.TAXONOMY_VERSION));
long modelVersion =
- cursor.getInt(
+ cursor.getLong(
cursor.getColumnIndexOrThrow(
TopicsTables.ReturnedTopicContract.MODEL_VERSION));
int topicId =
@@ -936,40 +932,121 @@ public class TopicsDao {
}
/**
- * Erase all data in a table that associates with a certain application
+ * Delete by column for the given values. Allow passing in multiple tables with their
+ * corresponding column names to delete by.
*
- * @param tableName the table to remove data from
- * @param appColumnName the column name in the table that represents the name of an application
- * @param appNames a {@link List} of apps to wipe out data for
+ * @param tableNamesAndColumnNamePairs the tables and corresponding column names to remove
+ * entries from
+ * @param valuesToDelete a {@link List} of values to delete if the entry has such value in
+ * {@code columnNameToDeleteFrom}
*/
- public void deleteAppFromTable(
- @NonNull String tableName,
- @NonNull String appColumnName,
- @NonNull List<String> appNames) {
- Objects.requireNonNull(tableName);
- Objects.requireNonNull(appColumnName);
- Objects.requireNonNull(appNames);
+ public void deleteFromTableByColumn(
+ @NonNull List<Pair<String, String>> tableNamesAndColumnNamePairs,
+ @NonNull List<String> valuesToDelete) {
+ Objects.requireNonNull(tableNamesAndColumnNamePairs);
+ Objects.requireNonNull(valuesToDelete);
SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
- if (db == null || appNames.isEmpty()) {
+ // If valuesToDelete is empty, do nothing.
+ if (db == null || valuesToDelete.isEmpty()) {
return;
}
- // Construct the "IN" part of SQL Query
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append("(?");
- for (int i = 0; i < appNames.size() - 1; i++) {
- stringBuilder.append(",?");
+ for (Pair<String, String> tableAndColumnNamePair : tableNamesAndColumnNamePairs) {
+ String tableName = tableAndColumnNamePair.first;
+ String columnNameToDeleteFrom = tableAndColumnNamePair.second;
+
+ // Construct the "IN" part of SQL Query
+ StringBuilder whereClauseBuilder = new StringBuilder();
+ whereClauseBuilder.append("(?");
+ for (int i = 0; i < valuesToDelete.size() - 1; i++) {
+ whereClauseBuilder.append(",?");
+ }
+ whereClauseBuilder.append(')');
+
+ String whereClause = columnNameToDeleteFrom + " IN " + whereClauseBuilder;
+ String[] whereArgs = valuesToDelete.toArray(new String[0]);
+
+ try {
+ db.delete(tableName, whereClause, whereArgs);
+ } catch (SQLException e) {
+ LogUtil.e(
+ e,
+ String.format(
+ "Failed to delete %s in table %s.", valuesToDelete, tableName));
+ }
}
- stringBuilder.append(')');
+ }
- String whereClause = appColumnName + " IN " + stringBuilder;
- String[] whereArgs = appNames.toArray(new String[0]);
+ /**
+ * Delete an entry from tables if the value in the column of this entry exists in the given
+ * values.
+ *
+ * <p>Similar to deleteEntriesFromTableByColumn but only delete entries that satisfy the equal
+ * condition.
+ *
+ * @param tableNamesAndColumnNamePairs the tables and corresponding column names to remove
+ * entries from
+ * @param valuesToDelete a {@link List} of values to delete if the entry has such value in
+ * {@code columnNameToDeleteFrom}
+ * @param equalConditionColumnName the column name of the equal condition
+ * @param equalConditionColumnValue the value in {@code equalConditionColumnName} of the equal
+ * condition
+ * @param isStringEqualConditionColumnValue whether the value of {@code
+ * equalConditionColumnValue} is a string
+ */
+ public void deleteEntriesFromTableByColumnWithEqualCondition(
+ @NonNull List<Pair<String, String>> tableNamesAndColumnNamePairs,
+ @NonNull List<String> valuesToDelete,
+ @NonNull String equalConditionColumnName,
+ @NonNull String equalConditionColumnValue,
+ boolean isStringEqualConditionColumnValue) {
+ Objects.requireNonNull(tableNamesAndColumnNamePairs);
+ Objects.requireNonNull(valuesToDelete);
+ Objects.requireNonNull(equalConditionColumnName);
+ Objects.requireNonNull(equalConditionColumnValue);
- try {
- db.delete(tableName, whereClause, whereArgs);
- } catch (SQLException e) {
- LogUtil.e(e, String.format("Failed to delete %s in table %s.", appNames, tableName));
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ // If valuesToDelete is empty, do nothing.
+ if (db == null || valuesToDelete.isEmpty()) {
+ return;
+ }
+
+ for (Pair<String, String> tableAndColumnNamePair : tableNamesAndColumnNamePairs) {
+ String tableName = tableAndColumnNamePair.first;
+ String columnNameToDeleteFrom = tableAndColumnNamePair.second;
+
+ // Construct the "IN" part of SQL Query
+ StringBuilder whereClauseBuilder = new StringBuilder();
+ whereClauseBuilder.append("(?");
+ for (int i = 0; i < valuesToDelete.size() - 1; i++) {
+ whereClauseBuilder.append(",?");
+ }
+ whereClauseBuilder.append(')');
+
+ // Add equal condition to sql query. If the value is a string, bound it with single
+ // quotes.
+ String whereClause =
+ columnNameToDeleteFrom
+ + " IN "
+ + whereClauseBuilder
+ + " AND "
+ + equalConditionColumnName
+ + " = ";
+ if (isStringEqualConditionColumnValue) {
+ whereClause += "'" + equalConditionColumnValue + "'";
+ } else {
+ whereClause += equalConditionColumnValue;
+ }
+
+ try {
+ db.delete(tableName, whereClause, valuesToDelete.toArray(new String[0]));
+ } catch (SQLException e) {
+ LogUtil.e(
+ e,
+ String.format(
+ "Failed to delete %s in table %s.", valuesToDelete, tableName));
+ }
}
}
@@ -1034,4 +1111,104 @@ public class TopicsDao {
return origin;
}
+
+ /**
+ * Persist topic to contributor mappings to the database. In an epoch, an app is a contributor
+ * to a topic if the app has called Topics API in this epoch and is classified to the topic.
+ *
+ * @param epochId the epochId
+ * @param topicToContributorsMap a {@link Map} of topic to a @{@link Set} of its contributor
+ * apps.
+ */
+ public void persistTopicContributors(
+ long epochId, @NonNull Map<Integer, Set<String>> topicToContributorsMap) {
+ Objects.requireNonNull(topicToContributorsMap);
+
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ return;
+ }
+
+ for (Map.Entry<Integer, Set<String>> topicToContributors :
+ topicToContributorsMap.entrySet()) {
+ Integer topicId = topicToContributors.getKey();
+
+ for (String app : topicToContributors.getValue()) {
+ ContentValues values = new ContentValues();
+ values.put(TopicsTables.TopicContributorsContract.EPOCH_ID, epochId);
+ values.put(TopicsTables.TopicContributorsContract.TOPIC, topicId);
+ values.put(TopicsTables.TopicContributorsContract.APP, app);
+
+ try {
+ db.insert(
+ TopicsTables.TopicContributorsContract.TABLE,
+ /* nullColumnHack */ null,
+ values);
+ } catch (SQLException e) {
+ LogUtil.e(e, "Failed to persist topic contributors.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieve topic to contributor mappings from database. In an epoch, an app is a contributor to
+ * a topic if the app has called Topics API in this epoch and is classified to the topic.
+ *
+ * @param epochId the epochId
+ * @return a {@link Map} of topic to its contributors
+ */
+ @NonNull
+ public Map<Integer, Set<String>> retrieveTopicToContributorsMap(long epochId) {
+ Map<Integer, Set<String>> topicToContributorsMap = new HashMap<>();
+ SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
+ if (db == null) {
+ return topicToContributorsMap;
+ }
+
+ String[] projection = {
+ TopicsTables.TopicContributorsContract.EPOCH_ID,
+ TopicsTables.TopicContributorsContract.TOPIC,
+ TopicsTables.TopicContributorsContract.APP
+ };
+
+ String selection = TopicsTables.TopicContributorsContract.EPOCH_ID + " = ?";
+ String[] selectionArgs = {String.valueOf(epochId)};
+
+ try (Cursor cursor =
+ db.query(
+ TopicsTables.TopicContributorsContract.TABLE, // The table to query
+ projection, // The array of columns to return (pass null to get all)
+ selection, // The columns for the WHERE clause
+ selectionArgs, // The values for the WHERE clause
+ null, // don't group the rows
+ null, // don't filter by row groups
+ null // The sort order
+ )) {
+ if (cursor == null) {
+ return topicToContributorsMap;
+ }
+
+ while (cursor.moveToNext()) {
+ String app =
+ cursor.getString(
+ cursor.getColumnIndexOrThrow(
+ TopicsTables.TopicContributorsContract.APP));
+ int topicId =
+ cursor.getInt(
+ cursor.getColumnIndexOrThrow(
+ TopicsTables.TopicContributorsContract.TOPIC));
+
+ topicToContributorsMap.putIfAbsent(topicId, new HashSet<>());
+ topicToContributorsMap.get(topicId).add(app);
+ }
+ }
+
+ return topicToContributorsMap;
+ }
+
+ /** Check whether TopContributors Table is supported in current database. */
+ public boolean supportsTopicContributorsTable() {
+ return mDbHelper.supportsTopicContributorsTable();
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/data/topics/TopicsTables.java b/adservices/service-core/java/com/android/adservices/data/topics/TopicsTables.java
index 020309576..abbf6259e 100644
--- a/adservices/service-core/java/com/android/adservices/data/topics/TopicsTables.java
+++ b/adservices/service-core/java/com/android/adservices/data/topics/TopicsTables.java
@@ -37,8 +37,9 @@ public final class TopicsTables {
String TOPIC = "topic";
}
- // Table Create Statement for the Topics Epoch table
- private static final String CREATE_TABLE_TOPICS_TAXONOMY =
+ /** Table Create Statement for the Topics Epoch table */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_TOPICS_TAXONOMY =
"CREATE TABLE "
+ TaxonomyContract.TABLE
+ "("
@@ -68,8 +69,9 @@ public final class TopicsTables {
String TOPIC = "topic";
}
- // Create Statement for the returned Topics table
- private static final String CREATE_TABLE_APP_CLASSIFICATION_TOPICS =
+ /** Create Statement for the returned Topics table */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_APP_CLASSIFICATION_TOPICS =
"CREATE TABLE "
+ AppClassificationTopicsContract.TABLE
+ "("
@@ -102,8 +104,9 @@ public final class TopicsTables {
String MODEL_VERSION = "model_version";
}
- // Create Statement for the Caller Learned Topic table.
- private static final String CREATE_TABLE_CALLER_CAN_LEARN_TOPICS =
+ /** Create Statement for the Caller Learned Topic table. */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_CALLER_CAN_LEARN_TOPICS =
"CREATE TABLE "
+ CallerCanLearnTopicsContract.TABLE
+ "("
@@ -142,8 +145,9 @@ public final class TopicsTables {
String MODEL_VERSION = "model_version";
}
- // Table Create Statement for the Top Topics table
- private static final String CREATE_TABLE_TOP_TOPICS =
+ /** Table Create Statement for the Top Topics table */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_TOP_TOPICS =
"CREATE TABLE "
+ TopTopicsContract.TABLE
+ "("
@@ -173,7 +177,6 @@ public final class TopicsTables {
* The returned topic for the app or for the sdk. Note: for App usages directly without any SDK,
* the SDK Name is set to empty string.
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public interface ReturnedTopicContract {
String TABLE = TOPICS_TABLE_PREFIX + "returned_topics";
String ID = "_id";
@@ -185,8 +188,9 @@ public final class TopicsTables {
String TOPIC = "topic";
}
- // Create Statement for the returned Topics table
- private static final String CREATE_TABLE_RETURNED_TOPIC =
+ /** Create Statement for the returned Topics table */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_RETURNED_TOPIC =
"CREATE TABLE "
+ ReturnedTopicContract.TABLE
+ "("
@@ -218,8 +222,9 @@ public final class TopicsTables {
String SDK = "sdk";
}
- // Create Statement for the Usage History table
- private static final String CREATE_TABLE_USAGE_HISTORY =
+ /** Create Statement for the Usage History table */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_USAGE_HISTORY =
"CREATE TABLE "
+ UsageHistoryContract.TABLE
+ "("
@@ -243,8 +248,9 @@ public final class TopicsTables {
String APP = "app";
}
- // Create Statement for the Usage History App Only table
- private static final String CREATE_TABLE_APP_USAGE_HISTORY =
+ /** Create Statement for the Usage History App Only table */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_APP_USAGE_HISTORY =
"CREATE TABLE "
+ AppUsageHistoryContract.TABLE
+ "("
@@ -257,7 +263,6 @@ public final class TopicsTables {
+ ")";
/** Table to store all blocked {@link Topic}s. Blocked topics are controlled by user. */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public interface BlockedTopicsContract {
String TABLE = TOPICS_TABLE_PREFIX + "blocked";
String ID = "_id";
@@ -266,8 +271,9 @@ public final class TopicsTables {
String TOPIC = "topic";
}
- // Create Statement for the blocked topics table.
- private static final String CREATE_TABLE_BLOCKED_TOPICS =
+ /** Create Statement for the blocked topics table. */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_BLOCKED_TOPICS =
"CREATE TABLE "
+ BlockedTopicsContract.TABLE
+ "("
@@ -291,11 +297,14 @@ public final class TopicsTables {
String ORIGIN = "origin";
}
- // At the first time inserting a record, it won't persist one_row_check field so that this first
- // entry will have one_row_check = 1. Therefore, further persisting is not allowed as primary
- // key cannot be duplicated value and one_row_check is constrained to only equal to 1 to forbid
- // any increment.
- private static final String CREATE_TABLE_EPOCH_ORIGIN =
+ /**
+ * At the first time inserting a record, it won't persist one_row_check field so that this first
+ * entry will have one_row_check = 1. Therefore, further persisting is not allowed as primary
+ * key cannot be duplicated value and one_row_check is constrained to only equal to 1 to forbid
+ * any increment.
+ */
+ @VisibleForTesting
+ public static final String CREATE_TABLE_EPOCH_ORIGIN =
"CREATE TABLE "
+ EpochOriginContract.TABLE
+ "("
@@ -308,7 +317,34 @@ public final class TopicsTables {
+ " = 1) "
+ ")";
- // Consolidated list of create statements for all tables.
+ /**
+ * Table to store classified topic to apps mapping. In an epoch, an app is a contributor to a
+ * topic if the app has called Topics API in this epoch and is classified to the topic.
+ */
+ public interface TopicContributorsContract {
+ String TABLE = TOPICS_TABLE_PREFIX + "topic_contributors";
+ String ID = "_id";
+ String EPOCH_ID = "epoch_id";
+ String TOPIC = "topic";
+ String APP = "app";
+ }
+
+ /** The SQLite query to create topic_contributors table if it doesn't exist */
+ public static final String CREATE_TABLE_TOPIC_CONTRIBUTORS =
+ "CREATE TABLE IF NOT EXISTS "
+ + TopicContributorsContract.TABLE
+ + "("
+ + TopicContributorsContract.ID
+ + " INTEGER PRIMARY KEY, "
+ + TopicContributorsContract.EPOCH_ID
+ + " INTEGER NOT NULL, "
+ + TopicContributorsContract.TOPIC
+ + " INTEGER NOT NULL, "
+ + AppUsageHistoryContract.APP
+ + " TEXT NOT NULL"
+ + ")";
+
+ /** Consolidated list of create statements for all tables. */
public static final List<String> CREATE_STATEMENTS =
Collections.unmodifiableList(
Arrays.asList(
@@ -320,7 +356,16 @@ public final class TopicsTables {
CREATE_TABLE_APP_USAGE_HISTORY,
CREATE_TABLE_CALLER_CAN_LEARN_TOPICS,
CREATE_TABLE_BLOCKED_TOPICS,
- CREATE_TABLE_EPOCH_ORIGIN));
+ CREATE_TABLE_EPOCH_ORIGIN,
+ CREATE_TABLE_TOPIC_CONTRIBUTORS));
+ // TODO(b/227393493): Should support a test if new table is added.
+ // *******************************************************************************************
+ // * NOTE: Please check below steps before adding a new table:
+ // * 1) TopicsDao -> ALL_TOPICS_TABLES: User Consent to clear all tables
+ // * 2) EpochManager -> TABLE_INFO_FOR_EPOCH_GARBAGE_COLLECTION: GC for dat of old epochs.
+ // * 3) AppUpdateManager -> TABLE_INFO_TO_ERASE_APP_DATA: clear app data for app uninstallation
+ // * 4) DbHelper -> onUpgrade: Handle any new schema change
+ // *******************************************************************************************
// Private constructor to prevent instantiation.
private TopicsTables() {}
diff --git a/adservices/service-core/java/com/android/adservices/data/topics/migration/AbstractTopicsDbMigrator.java b/adservices/service-core/java/com/android/adservices/data/topics/migration/AbstractTopicsDbMigrator.java
new file mode 100644
index 000000000..e1f241740
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/data/topics/migration/AbstractTopicsDbMigrator.java
@@ -0,0 +1,79 @@
+/*
+ * 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.data.topics.migration;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.DbHelper;
+
+/**
+ * The abstract class to handle database migration for Topics API. Any Migrator should extend this
+ * class and implement {@code performMigration} method to create/drop/alter tables by execute SQLite
+ * queries.
+ */
+public abstract class AbstractTopicsDbMigrator implements ITopicsDbMigrator {
+ private final int mMigrationTargetVersion;
+
+ public AbstractTopicsDbMigrator(int migrationTargetVersion) {
+ mMigrationTargetVersion = migrationTargetVersion;
+ }
+
+ /**
+ * A prerequisite method to check whether to perform the migration.
+ *
+ * <p>In {@link SQLiteOpenHelper}, {@link SQLiteOpenHelper#onUpgrade(SQLiteDatabase, int, int)}
+ * is called if current device db version is older than the version to create db.
+ *
+ * <p>{@code oldVersion} is always the current database version on device before database calls
+ * onCreate(). {@code newVersion} is always the database version defined in {@link DbHelper}.
+ * Therefore, the migration should be performed only if {@code mMigrationTargetVersion} is
+ * between {@code oldVersion} and {@code newVersion}, including {@code newVersion}.
+ *
+ * @param db db to perform migration on
+ * @param oldVersion device version of database
+ * @param newVersion target version to create the database
+ * @throws IllegalArgumentException if {@code mMigrationTargetVersion} is not between {@code
+ * oldVersion} and {@code newVersion}, including {@code newVersion}.
+ */
+ @Override
+ public void performMigration(SQLiteDatabase db, int oldVersion, int newVersion)
+ throws IllegalArgumentException {
+ // Perform Migration only when targetVersion is in the between of oldVersion and newVersion.
+ // (including the case targetVersion is equal to newVersion)
+ if (oldVersion < mMigrationTargetVersion && mMigrationTargetVersion <= newVersion) {
+
+ LogUtil.d("Migrating DB to version %d for Topics API.", mMigrationTargetVersion);
+ performMigration(db);
+ return;
+ }
+
+ throw new IllegalArgumentException(
+ String.format(
+ "Stop migration to db version %d for Topics API. oldVersion=%d, "
+ + " newVersion=%d",
+ mMigrationTargetVersion, oldVersion, newVersion));
+ }
+
+ /**
+ * Execute SQLite Queries to migrate database between versions
+ *
+ * @param db db to migrate
+ */
+ protected abstract void performMigration(SQLiteDatabase db);
+}
diff --git a/adservices/service-core/java/com/android/adservices/data/topics/migration/ITopicsDbMigrator.java b/adservices/service-core/java/com/android/adservices/data/topics/migration/ITopicsDbMigrator.java
new file mode 100644
index 000000000..a4ea8ec9c
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/data/topics/migration/ITopicsDbMigrator.java
@@ -0,0 +1,36 @@
+/*
+ * 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.data.topics.migration;
+
+import android.database.sqlite.SQLiteDatabase;
+
+/**
+ * Its implementations will have logic to migration from a version to next version. The logic would
+ * essentially have pre data migration, data transfer and post migration (cleanup) steps. Caller
+ * should invoke {@link ITopicsDbMigrator#performMigration(SQLiteDatabase, int, int)} API to perform
+ * the migration.
+ */
+public interface ITopicsDbMigrator {
+ /**
+ * Migrates the database version from current version on device to another version.
+ *
+ * @param db db to perform migration on
+ * @param oldVersion device version of database
+ * @param newVersion target version to create the database
+ */
+ void performMigration(SQLiteDatabase db, int oldVersion, int newVersion);
+}
diff --git a/adservices/service-core/java/com/android/adservices/data/topics/migration/TopicDbMigratorV3.java b/adservices/service-core/java/com/android/adservices/data/topics/migration/TopicDbMigratorV3.java
new file mode 100644
index 000000000..26bb8037a
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/data/topics/migration/TopicDbMigratorV3.java
@@ -0,0 +1,46 @@
+/*
+ * 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.data.topics.migration;
+
+import static com.android.adservices.data.DbHelper.DATABASE_VERSION_V3;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.adservices.data.topics.TopicsTables;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Migrator to perform DB schema change to version 3 in Topics API. Version 3 is to add
+ * TopicContributors Table.
+ */
+public class TopicDbMigratorV3 extends AbstractTopicsDbMigrator {
+ private static final String[] QUERIES_TO_PERFORM = {
+ TopicsTables.CREATE_TABLE_TOPIC_CONTRIBUTORS
+ };
+
+ public TopicDbMigratorV3() {
+ super(DATABASE_VERSION_V3);
+ }
+
+ @Override
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public void performMigration(SQLiteDatabase db) {
+ for (String query : QUERIES_TO_PERFORM) {
+ db.execSQL(query);
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/AdServicesConfig.java b/adservices/service-core/java/com/android/adservices/service/AdServicesConfig.java
index 63334cadc..a3105e2d5 100644
--- a/adservices/service-core/java/com/android/adservices/service/AdServicesConfig.java
+++ b/adservices/service-core/java/com/android/adservices/service/AdServicesConfig.java
@@ -52,7 +52,7 @@ public class AdServicesConfig {
public static final int MEASUREMENT_DELETE_EXPIRED_JOB_ID = 4;
public static long MEASUREMENT_DELETE_EXPIRED_JOB_PERIOD_MS = TimeUnit.HOURS.toMillis(24);
- public static long MEASUREMENT_DELETE_EXPIRED_WINDOW_MS = TimeUnit.DAYS.toMillis(30);
+ public static long MEASUREMENT_DELETE_EXPIRED_WINDOW_MS = TimeUnit.DAYS.toMillis(37);
/**
* Returns the min time period (in millis) between each expired-record deletion maintenance job
@@ -140,4 +140,32 @@ public class AdServicesConfig {
/** Job ID for Mdd Wifi Charging Task ({@link com.android.adservices.download.MddJobService}) */
public static final int MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID = 14;
+
+ /**
+ * Returns the min time period (in millis) between each uninstalled-record deletion maintenance
+ * job run.
+ */
+ public static long getMeasurementDeleteUninstalledJobPeriodMs() {
+ return MEASUREMENT_DELETE_UNINSTALLED_JOB_PERIOD_MS;
+ }
+
+ public static long MEASUREMENT_DELETE_UNINSTALLED_JOB_PERIOD_MS = TimeUnit.HOURS.toMillis(24);
+
+ /**
+ * Job ID for the Async Registration Queue JobService ({@link
+ * com.android.adservices.service.measurement.AsyncRegistrationQueueJobService})
+ */
+ public static final int ASYNC_REGISTRATION_QUEUE_JOB_ID = 15;
+
+ /**
+ * Job ID for Measurement Delete Records From UninstalledApps Job ({@link
+ * com.android.adservices.service.measurement.DeleteUninstalledJobService})
+ */
+ public static final int MEASUREMENT_DELETE_UNINSTALLED_JOB_ID = 16;
+
+ /**
+ * Job ID for Measurement Debug Reporting Job ({@link
+ * com.android.adservices.service.measurement.reporting.DebugReportingJobService})
+ */
+ public static final int MEASUREMENT_DEBUG_REPORT_JOB_ID = 17;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/Flags.java b/adservices/service-core/java/com/android/adservices/service/Flags.java
index 318566c76..78ef1b85f 100644
--- a/adservices/service-core/java/com/android/adservices/service/Flags.java
+++ b/adservices/service-core/java/com/android/adservices/service/Flags.java
@@ -24,6 +24,8 @@ import android.util.Dumpable;
import androidx.annotation.Nullable;
+import com.google.common.collect.ImmutableList;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -147,6 +149,19 @@ public interface Flags extends Dumpable {
return CLASSIFIER_DESCRIPTION_MAX_LENGTH;
}
+ // TODO(b/243829477): Remove this flag when flow of pushing models is refined.
+ /**
+ * Whether classifier should force using bundled files. This flag is mainly used in CTS tests to
+ * force using precomputed_app_list to avoid model mismatch due to update. Default value is
+ * false which means to use downloaded files.
+ */
+ boolean CLASSIFIER_FORCE_USE_BUNDLED_FILES = false;
+
+ /** Returns whether to force using bundled files */
+ default boolean getClassifierForceUseBundledFiles() {
+ return CLASSIFIER_FORCE_USE_BUNDLED_FILES;
+ }
+
/* The default period for the Maintenance job. */
long MAINTENANCE_JOB_PERIOD_MS = 86_400_000; // 1 day.
@@ -462,11 +477,14 @@ public interface Flags extends Dumpable {
return FLEDGE_AD_SELECTION_CONCURRENT_BIDDING_COUNT;
}
- // TODO(b/240647148): Limits are increased temporarily, decrease these numbers after
- // implementing a solution for more accurately scoped timeout
+ // TODO(b/240647148): Limits are increased temporarily, re-evaluate these numbers after
+ // getting real world data from telemetry & set accurately scoped timeout
long FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_CA_MS = 5000;
+ long FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS = 10000;
long FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS = 5000;
+ // For *on device* ad selection.
long FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS = 10000;
+ long FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS = 10_000;
long FLEDGE_REPORT_IMPRESSION_OVERALL_TIMEOUT_MS = 2000;
@@ -475,20 +493,33 @@ public interface Flags extends Dumpable {
return FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_CA_MS;
}
+ /** Returns the timeout constant in milliseconds that limits the bidding per Buyer */
+ default long getAdSelectionBiddingTimeoutPerBuyerMs() {
+ return FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS;
+ }
+
/** Returns the timeout constant in milliseconds that limits the scoring */
default long getAdSelectionScoringTimeoutMs() {
return FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS;
}
/**
- * Returns the timeout constant in milliseconds that limits the overall ad selection
- * orchestration
+ * Returns the timeout constant in milliseconds that limits the overall *on device* ad selection
+ * orchestration.
*/
default long getAdSelectionOverallTimeoutMs() {
return FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS;
}
/**
+ * Returns the timeout constant in milliseconds that limits the overall off device ad selection
+ * orchestration.
+ */
+ default long getAdSelectionOffDeviceOverallTimeoutMs() {
+ return FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS;
+ }
+
+ /**
* Returns the time out constant in milliseconds that limits the overall impression reporting
* execution
*/
@@ -496,6 +527,30 @@ public interface Flags extends Dumpable {
return FLEDGE_REPORT_IMPRESSION_OVERALL_TIMEOUT_MS;
}
+ // 24 hours in seconds
+ long FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S = 60 * 60 * 24;
+
+ /**
+ * Returns the amount of time in seconds after which ad selection data is considered expired.
+ */
+ default long getAdSelectionExpirationWindowS() {
+ return FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S;
+ }
+
+ boolean FLEDGE_AD_SELECTION_OFF_DEVICE_ENABLED = false;
+
+ /** @return whether to call trusted servers for off device ad selection. */
+ default boolean getAdSelectionOffDeviceEnabled() {
+ return FLEDGE_AD_SELECTION_OFF_DEVICE_ENABLED;
+ }
+
+ boolean FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED = true;
+
+ /** Returns whether to compress requests sent off device for ad selection. */
+ default boolean getAdSelectionOffDeviceRequestCompressionEnabled() {
+ return FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED;
+ }
+
boolean ADSERVICES_ENABLED = false;
default boolean getAdServicesEnabled() {
@@ -808,6 +863,24 @@ public interface Flags extends Dumpable {
}
/**
+ * Measurement Job Delete Uninstalled Kill Switch. The default value is false which means Delete
+ * Uninstalled Job is enabled. This flag is used for emergency turning off the Delete
+ * Uninstalled Job.
+ */
+ boolean MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH = false;
+
+ /**
+ * Returns the kill switch value for Measurement Job Delete Uninstalled. The API will be
+ * disabled if either the Global Kill Switch, Measurement Kill Switch, or the Measurement Job
+ * Delete Uninstalled Kill Switch value is true.
+ */
+ default boolean getMeasurementJobDeleteUninstalledKillSwitch() {
+ return getGlobalKillSwitch()
+ || getMeasurementKillSwitch()
+ || MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH;
+ }
+
+ /**
* Measurement Job Event Fallback Reporting Kill Switch. The default value is false which means
* Event Fallback Reporting Job is enabled. This flag is used for emergency turning off the
* Event Fallback Reporting Job.
@@ -844,6 +917,24 @@ public interface Flags extends Dumpable {
|| getMeasurementKillSwitch()
|| MEASUREMENT_JOB_EVENT_REPORTING_KILL_SWITCH;
}
+ /**
+ * Measurement Job Debug Reporting Kill Switch. The default value is false which means Debug
+ * Reporting Job is enabled. This flag is used for emergency turning off the Debug Reporting
+ * Job.
+ */
+ boolean MEASUREMENT_JOB_DEBUG_REPORTING_KILL_SWITCH = false;
+
+ /**
+ * Returns the kill switch value for Measurement Job Debug Reporting. The API will be disabled
+ * if either the Global Kill Switch, Measurement Kill Switch, or the Measurement Job Debug
+ * Reporting Kill Switch value is true.
+ */
+ default boolean getMeasurementJobDebugReportingKillSwitch() {
+ // We check the Global Kill Switch first. As a result, it overrides all other kill Switches.
+ return getGlobalKillSwitch()
+ || getMeasurementKillSwitch()
+ || MEASUREMENT_JOB_DEBUG_REPORTING_KILL_SWITCH;
+ }
/**
* Measurement Broadcast Receiver Install Attribution Kill Switch. The default value is false
@@ -890,10 +981,9 @@ public interface Flags extends Dumpable {
*/
boolean ADID_KILL_SWITCH = false; // By default, the AdId API is enabled.
- /** Gets the state of the global and adId kill switch. */
+ /** Gets the state of adId kill switch. */
default boolean getAdIdKillSwitch() {
- // We check the Global Killswitch first. As a result, it overrides all other killswitches.
- return getGlobalKillSwitch() || ADID_KILL_SWITCH;
+ return ADID_KILL_SWITCH;
}
// APPSETID Killswitch.
@@ -905,8 +995,7 @@ public interface Flags extends Dumpable {
/** Gets the state of the global and appSetId kill switch. */
default boolean getAppSetIdKillSwitch() {
- // We check the Global Killswitch first. As a result, it overrides all other killswitches.
- return getGlobalKillSwitch() || APPSETID_KILL_SWITCH;
+ return APPSETID_KILL_SWITCH;
}
// TOPICS Killswitches
@@ -993,6 +1082,9 @@ public interface Flags extends Dumpable {
+ "com.android.adservices.tests.permissions.valid,"
+ "com.android.adservices.tests.adid,"
+ "com.android.adservices.tests.appsetid,"
+ + "com.android.sdksandboxclient,"
+ + "com.android.tests.sandbox.adid,"
+ + "com.android.tests.sandbox.appsetid,"
+ "com.android.tests.sandbox.fledge,"
+ "com.android.tests.sandbox.measurement,"
+ "com.example.adservices.samples.adid.app,"
@@ -1002,6 +1094,8 @@ public interface Flags extends Dumpable {
+ "com.example.adservices.samples.fledge.sampleapp2,"
+ "com.example.adservices.samples.fledge.sampleapp3,"
+ "com.example.adservices.samples.fledge.sampleapp4,"
+ + "com.example.measurement.sampleapp,"
+ + "com.example.measurement.sampleapp2,"
+ "com.android.adservices.tests.cts.endtoendtest.measurement";
/**
@@ -1228,4 +1322,69 @@ public interface Flags extends Dumpable {
default long getMaxResponseBasedRegistrationPayloadSizeBytes() {
return MAX_RESPONSE_BASED_REGISTRATION_SIZE_BYTES;
}
+
+ /** UI Dialogs feature enabled. */
+ boolean UI_DIALOGS_FEATURE_ENABLED = false;
+
+ /** Returns if the UI Dialogs feature is enabled. */
+ default boolean getUIDialogsFeatureEnabled() {
+ return UI_DIALOGS_FEATURE_ENABLED;
+ }
+
+ long ASYNC_REGISTRATION_JOB_QUEUE_INTERVAL_MS = (int) TimeUnit.HOURS.toMillis(1);
+ /** Returns the interval in which to run Registration Job Queue Service. */
+ default long getRegistrationJobQueueIntervalMs() {
+ return ASYNC_REGISTRATION_JOB_QUEUE_INTERVAL_MS;
+ }
+
+ /**
+ * Registration Job Queue Kill Switch. The default value is false which means Registration Job
+ * Queue is enabled. This flag is used for emergency shutdown of the Registration Job Queue.
+ */
+ boolean MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH = false;
+
+ /**
+ * Returns the kill switch value for Registration Job Queue. The API will be disabled if either
+ * the Global Kill Switch, Measurement Kill Switch, or the Registration Job Queue Kill Switch
+ * value is true.
+ */
+ default boolean getRegistrationJobQueueKillSwitch() {
+ // We check the Global Killswitch first. As a result, it overrides all other killswitches.
+ return getGlobalKillSwitch()
+ || getMeasurementKillSwitch()
+ || MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH;
+ }
+
+ /**
+ * A feature flag to enable the feature of handling topics without any contributors. Note that
+ * in an epoch, an app is a contributor to a topic if the app has called Topics API in this
+ * epoch and is classified to the topic.
+ *
+ * <p>Default value is false, which means the feature is disabled by default and needs to be
+ * ramped up.
+ */
+ boolean ENABLE_TOPIC_CONTRIBUTORS_CHECK = false;
+
+ /** @return if to enable topic contributors check. */
+ default boolean getEnableTopicContributorsCheck() {
+ return ENABLE_TOPIC_CONTRIBUTORS_CHECK;
+ }
+
+ /** Whether to enable database schema version 3 */
+ boolean ENABLE_DATABASE_SCHEMA_VERSION_3 = false;
+
+ /** @return if to enable database schema version 3. */
+ default boolean getEnableDatabaseSchemaVersion3() {
+ return ENABLE_DATABASE_SCHEMA_VERSION_3;
+ }
+
+ /** Returns true if the given enrollmentId is blocked from using PP-API. */
+ default boolean isEnrollmentBlocklisted(String enrollmentId) {
+ return false;
+ }
+
+ /** Returns a list of enrollmentId blocked from using PP-API. */
+ default ImmutableList<String> getEnrollmentBlocklist() {
+ return ImmutableList.of();
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/FlagsFactory.java b/adservices/service-core/java/com/android/adservices/service/FlagsFactory.java
index 4acb938ad..def563ce0 100644
--- a/adservices/service-core/java/com/android/adservices/service/FlagsFactory.java
+++ b/adservices/service-core/java/com/android/adservices/service/FlagsFactory.java
@@ -54,6 +54,16 @@ public class FlagsFactory {
public boolean getDisableFledgeEnrollmentCheck() {
return true;
}
+
+ @Override
+ public boolean getEnableDatabaseSchemaVersion3() {
+ return true;
+ }
+
+ @Override
+ public boolean getEnableTopicContributorsCheck() {
+ return true;
+ }
};
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/MaintenanceJobService.java b/adservices/service-core/java/com/android/adservices/service/MaintenanceJobService.java
index c63cee11f..adb4bd040 100644
--- a/adservices/service-core/java/com/android/adservices/service/MaintenanceJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/MaintenanceJobService.java
@@ -30,35 +30,72 @@ import android.content.Context;
import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.service.common.FledgeMaintenanceTasksWorker;
import com.android.adservices.service.topics.TopicsWorker;
+import com.android.internal.annotations.VisibleForTesting;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.List;
+import java.util.Objects;
+
/** Maintenance job to clean up. */
public final class MaintenanceJobService extends JobService {
+ private FledgeMaintenanceTasksWorker mFledgeMaintenanceTasksWorker;
+
+ /** Injects a {@link FledgeMaintenanceTasksWorker to be used during testing} */
+ @VisibleForTesting
+ public void injectFledgeMaintenanceTasksWorker(
+ @NonNull FledgeMaintenanceTasksWorker fledgeMaintenanceTasksWorker) {
+ mFledgeMaintenanceTasksWorker = fledgeMaintenanceTasksWorker;
+ }
+
@Override
public boolean onStartJob(JobParameters params) {
LogUtil.d("MaintenanceJobService.onStartJob");
- if (FlagsFactory.getFlags().getTopicsKillSwitch()) {
- LogUtil.e("Topics API is disabled, skipping and cancelling MaintenanceJobService");
+ if (FlagsFactory.getFlags().getTopicsKillSwitch()
+ && FlagsFactory.getFlags().getFledgeSelectAdsKillSwitch()) {
+ LogUtil.e(
+ "Both Topics and Select Ads are disabled, skipping and cancelling"
+ + " MaintenanceJobService");
return skipAndCancelBackgroundJob(params);
}
- ListenableFuture<Void> appReconciliationFuture =
- Futures.submit(
- () -> TopicsWorker.getInstance(this).reconcileApplicationUpdate(this),
- AdServicesExecutors.getBackgroundExecutor());
+ ListenableFuture<Void> appReconciliationFuture;
+ if (FlagsFactory.getFlags().getTopicsKillSwitch()) {
+ LogUtil.d("Topics API is disabled, skipping Topics Job");
+ appReconciliationFuture = Futures.immediateFuture(null);
+ } else {
+ appReconciliationFuture =
+ Futures.submit(
+ () -> TopicsWorker.getInstance(this).reconcileApplicationUpdate(this),
+ AdServicesExecutors.getBackgroundExecutor());
+ }
+
+ ListenableFuture<Void> fledgeMaintenanceTasksFuture;
+ if (FlagsFactory.getFlags().getFledgeSelectAdsKillSwitch()) {
+ LogUtil.d("SelectAds API is disabled, skipping SelectAds Job");
+ fledgeMaintenanceTasksFuture = Futures.immediateFuture(null);
+ } else {
+ fledgeMaintenanceTasksFuture =
+ Futures.submit(
+ this::doAdSelectionDataMaintenanceTasks,
+ AdServicesExecutors.getBackgroundExecutor());
+ }
+
+ ListenableFuture<List<Void>> futuresList =
+ Futures.allAsList(fledgeMaintenanceTasksFuture, appReconciliationFuture);
Futures.addCallback(
- appReconciliationFuture,
- new FutureCallback<Void>() {
+ futuresList,
+ new FutureCallback<List<Void>>() {
@Override
- public void onSuccess(Void result) {
- LogUtil.d("App Update Reconciliation is done!");
+ public void onSuccess(List<Void> result) {
+ LogUtil.d("PP API jobs are done!");
jobFinished(params, /* wantsReschedule = */ false);
}
@@ -79,7 +116,8 @@ public final class MaintenanceJobService extends JobService {
return false;
}
- private static void schedule(
+ @VisibleForTesting
+ static void schedule(
Context context,
@NonNull JobScheduler jobScheduler,
long maintenanceJobPeriodMs,
@@ -89,6 +127,7 @@ public final class MaintenanceJobService extends JobService {
MAINTENANCE_JOB_ID,
new ComponentName(context, MaintenanceJobService.class))
.setRequiresCharging(true)
+ .setPersisted(true)
.setPeriodic(maintenanceJobPeriodMs, maintenanceJobFlexMs)
.build();
@@ -105,8 +144,11 @@ public final class MaintenanceJobService extends JobService {
* @return a {@code boolean} to indicate if the service job is actually scheduled.
*/
public static boolean scheduleIfNeeded(Context context, boolean forceSchedule) {
- if (FlagsFactory.getFlags().getTopicsKillSwitch()) {
- LogUtil.e("Topics API is disabled, skip scheduling the MaintenanceJobService");
+ if (FlagsFactory.getFlags().getTopicsKillSwitch()
+ && FlagsFactory.getFlags().getFledgeSelectAdsKillSwitch()) {
+ LogUtil.e(
+ "Both Topics and Select Ads are disabled, skipping scheduling"
+ + " MaintenanceJobService");
return false;
}
@@ -148,4 +190,17 @@ public final class MaintenanceJobService extends JobService {
// Returning false means that this job has completed its work.
return false;
}
+
+ private FledgeMaintenanceTasksWorker getFledgeMaintenanceTasksWorker() {
+ if (!Objects.isNull(mFledgeMaintenanceTasksWorker)) {
+ return mFledgeMaintenanceTasksWorker;
+ }
+ mFledgeMaintenanceTasksWorker = FledgeMaintenanceTasksWorker.create(this);
+ return mFledgeMaintenanceTasksWorker;
+ }
+
+ private void doAdSelectionDataMaintenanceTasks() {
+ LogUtil.v("Performing Ad Selection maintenance tasks");
+ getFledgeMaintenanceTasksWorker().clearExpiredAdSelectionData();
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/PhFlags.java b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
index 384d730f9..3d838acbd 100644
--- a/adservices/service-core/java/com/android/adservices/service/PhFlags.java
+++ b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
@@ -28,6 +28,8 @@ import androidx.annotation.Nullable;
import com.android.adservices.LogUtil;
import com.android.internal.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+
import java.io.PrintWriter;
/** Flags Implementation that delegates to DeviceConfig. */
@@ -48,6 +50,8 @@ public final class PhFlags implements Flags {
static final String KEY_TOPICS_NUMBER_OF_TOP_TOPICS = "topics_number_of_top_topics";
static final String KEY_TOPICS_NUMBER_OF_RANDOM_TOPICS = "topics_number_of_random_topics";
static final String KEY_TOPICS_NUMBER_OF_LOOK_BACK_EPOCHS = "topics_number_of_lookback_epochs";
+ static final String KEY_NUMBER_OF_EPOCHS_TO_KEEP_IN_HISTORY =
+ "topics_number_of_epochs_to_keep_in_history";
// Topics classifier keys
static final String KEY_CLASSIFIER_TYPE = "classifier_type";
@@ -55,6 +59,8 @@ public final class PhFlags implements Flags {
static final String KEY_CLASSIFIER_THRESHOLD = "classifier_threshold";
static final String KEY_CLASSIFIER_DESCRIPTION_MAX_WORDS = "classifier_description_max_words";
static final String KEY_CLASSIFIER_DESCRIPTION_MAX_LENGTH = "classifier_description_max_length";
+ static final String KEY_CLASSIFIER_FORCE_USE_BUNDLED_FILES =
+ "classifier_force_use_bundled_files";
// Measurement keys
static final String KEY_MEASUREMENT_EVENT_MAIN_REPORTING_JOB_PERIOD_MS =
@@ -149,10 +155,23 @@ public final class PhFlags implements Flags {
"fledge_ad_selection_scoring_timeout_ms";
static final String KEY_FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS =
"fledge_ad_selection_overall_timeout_ms";
+ static final String KEY_FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S =
+ "fledge_ad_selection_expiration_window_s";
static final String KEY_FLEDGE_REPORT_IMPRESSION_OVERALL_TIMEOUT_MS =
"fledge_report_impression_overall_timeout_ms";
- static final String KEY_NUMBER_OF_EPOCHS_TO_KEEP_IN_HISTORY =
- "topics_number_of_epochs_to_keep_in_history";
+ static final String KEY_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS =
+ "fledge_ad_selection_bidding_timeout_per_buyer_ms";
+
+ // FLEDGE Off device ad selection keys
+ static final String KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS =
+ "fledge_ad_selection_off_device_overall_timeout_ms";
+ // Whether to call trusted servers for off device ad selection.
+ static final String KEY_FLEDE_AD_SELECTION_OFF_DEVICE_ENABLED =
+ "fledge_ad_selection_off_device_enabled";
+ // Whether to compress the request object when calling trusted servers for off device ad
+ // selection.
+ static final String KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED =
+ "fledge_ad_selection_off_device_request_compression_enabled";
// Fledge invoking app status keys
static final String KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_RUN_AD_SELECTION =
@@ -210,6 +229,8 @@ public final class PhFlags implements Flags {
"measurement_job_attribution_kill_switch";
static final String KEY_MEASUREMENT_JOB_DELETE_EXPIRED_KILL_SWITCH =
"measurement_job_delete_expired_kill_switch";
+ static final String KEY_MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH =
+ "measurement_job_delete_uninstalled_kill_switch";
static final String KEY_MEASUREMENT_JOB_EVENT_FALLBACK_REPORTING_KILL_SWITCH =
"measurement_job_event_fallback_reporting_kill_switch";
static final String KEY_MEASUREMENT_JOB_EVENT_REPORTING_KILL_SWITCH =
@@ -218,6 +239,8 @@ public final class PhFlags implements Flags {
"measurement_receiver_install_attribution_kill_switch";
static final String KEY_MEASUREMENT_RECEIVER_DELETE_PACKAGES_KILL_SWITCH =
"measurement_receiver_delete_packages_kill_switch";
+ static final String KEY_MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH =
+ "measurement_job_registration_job_queue_kill_switch";
static final String KEY_TOPICS_KILL_SWITCH = "topics_kill_switch";
static final String KEY_MDD_BACKGROUND_TASK_KILL_SWITCH = "mdd_background_task_kill_switch";
static final String KEY_MDD_LOGGER_KILL_SWITCH = "mdd_logger_kill_switch";
@@ -261,9 +284,28 @@ public final class PhFlags implements Flags {
static final String KEY_MAX_RESPONSE_BASED_REGISTRATION_SIZE_BYTES =
"max_response_based_registration_size_bytes";
+ // UI keys
+ static final String KEY_UI_DIALOGS_FEATURE_ENABLED = "ui_dialogs_feature_enabled";
+
// Maximum possible percentage for percentage variables
static final int MAX_PERCENTAGE = 100;
+ // Whether to call trusted servers for off device ad selection.
+ static final String KEY_OFF_DEVICE_AD_SELECTION_ENABLED = "enable_off_device_ad_selection";
+
+ // Interval in which to run Registration Job Queue Service.
+ static final String KEY_REGISTRATION_JOB_QUEUE_INTERVAL_MS =
+ "key_registration_job_queue_interval_ms";
+
+ // Feature Flags
+ static final String KEY_ENABLE_TOPIC_CONTRIBUTORS_CHECK = "enable_topic_contributors_check";
+
+ // Database Schema Version Flags
+ static final String KEY_ENABLE_DATABASE_SCHEMA_VERSION_3 = "enable_database_schema_version_3";
+
+ // Enrollment flags.
+ static final String KEY_ENROLLMENT_BLOCKLIST_IDS = "enrollment_blocklist_ids";
+
private static final PhFlags sSingleton = new PhFlags();
/** Returns the singleton instance of the PhFlags. */
@@ -273,6 +315,15 @@ public final class PhFlags implements Flags {
}
@Override
+ public long getRegistrationJobQueueIntervalMs() {
+ // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_REGISTRATION_JOB_QUEUE_INTERVAL_MS,
+ /* defaultValue */ ASYNC_REGISTRATION_JOB_QUEUE_INTERVAL_MS);
+ }
+
+ @Override
public long getTopicsEpochJobPeriodMs() {
// The priority of applying the flag values: SystemProperties, PH (DeviceConfig), then
// hard-coded value.
@@ -421,6 +472,15 @@ public final class PhFlags implements Flags {
}
@Override
+ public boolean getClassifierForceUseBundledFiles() {
+ // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_CLASSIFIER_FORCE_USE_BUNDLED_FILES,
+ /* defaultValue */ CLASSIFIER_FORCE_USE_BUNDLED_FILES);
+ }
+
+ @Override
public long getMaintenanceJobPeriodMs() {
// The priority of applying the flag values: SystemProperties, PH (DeviceConfig) and then
// hard-coded value.
@@ -786,6 +846,14 @@ public final class PhFlags implements Flags {
}
@Override
+ public long getAdSelectionBiddingTimeoutPerBuyerMs() {
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS,
+ /* defaultValue */ FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS);
+ }
+
+ @Override
public long getAdSelectionScoringTimeoutMs() {
return DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ADSERVICES,
@@ -802,6 +870,14 @@ public final class PhFlags implements Flags {
}
@Override
+ public long getAdSelectionOffDeviceOverallTimeoutMs() {
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS,
+ /* defaultValue */ FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS);
+ }
+
+ @Override
public long getReportImpressionOverallTimeoutMs() {
return DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ADSERVICES,
@@ -1039,6 +1115,22 @@ public final class PhFlags implements Flags {
}
@Override
+ public boolean getMeasurementJobDeleteUninstalledKillSwitch() {
+ // We check the Global Killswitch first then Measurement Killswitch.
+ // As a result, it overrides all other killswitches.
+ // The priority of applying the flag values: SystemProperties, PH (DeviceConfig), then
+ // hard-coded value.
+ return getGlobalKillSwitch()
+ || getMeasurementKillSwitch()
+ || SystemProperties.getBoolean(
+ getSystemPropertyName(KEY_MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH),
+ /* defaultValue */ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH,
+ /* defaultValue */ MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH));
+ }
+
+ @Override
public boolean getMeasurementJobEventFallbackReportingKillSwitch() {
// We check the Global Killswitch first then Measurement Killswitch.
// As a result, it overrides all other killswitches.
@@ -1071,6 +1163,22 @@ public final class PhFlags implements Flags {
}
@Override
+ public boolean getRegistrationJobQueueKillSwitch() {
+ // We check the Global Killswitch first then Measurement Killswitch.
+ // As a result, it overrides all other killswitches.
+ // The priority of applying the flag values: SystemProperties, PH (DeviceConfig), then
+ // hard-coded value.
+ final String flagName = KEY_MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH;
+ final boolean defaultValue = MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH;
+ return getGlobalKillSwitch()
+ || getMeasurementKillSwitch()
+ || SystemProperties.getBoolean(
+ getSystemPropertyName(flagName),
+ /* defaultValue */ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES, flagName, defaultValue));
+ }
+
+ @Override
public boolean getMeasurementReceiverInstallAttributionKillSwitch() {
// We check the Global Killswitch first then Measurement Killswitch.
// As a result, it overrides all other killswitches.
@@ -1106,31 +1214,29 @@ public final class PhFlags implements Flags {
// ADID Killswitches
@Override
public boolean getAdIdKillSwitch() {
- // We check the Global Killswitch first. As a result, it overrides all other killswitches.
+ // Ignore Global Killswitch for adid.
// The priority of applying the flag values: SystemProperties, PH (DeviceConfig), then
// hard-coded value.
- return getGlobalKillSwitch()
- || SystemProperties.getBoolean(
- getSystemPropertyName(KEY_ADID_KILL_SWITCH),
- /* defaultValue */ DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_ADSERVICES,
- /* flagName */ KEY_ADID_KILL_SWITCH,
- /* defaultValue */ ADID_KILL_SWITCH));
+ return SystemProperties.getBoolean(
+ getSystemPropertyName(KEY_ADID_KILL_SWITCH),
+ /* defaultValue */ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_ADID_KILL_SWITCH,
+ /* defaultValue */ ADID_KILL_SWITCH));
}
// APPSETID Killswitch.
@Override
public boolean getAppSetIdKillSwitch() {
- // We check the Global Killswitch first. As a result, it overrides all other killswitches.
+ // Ignore Global Killswitch for appsetid.
// The priority of applying the flag values: SystemProperties, PH (DeviceConfig), then
// hard-coded value.
- return getGlobalKillSwitch()
- || SystemProperties.getBoolean(
- getSystemPropertyName(KEY_APPSETID_KILL_SWITCH),
- /* defaultValue */ DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_ADSERVICES,
- /* flagName */ KEY_APPSETID_KILL_SWITCH,
- /* defaultValue */ APPSETID_KILL_SWITCH));
+ return SystemProperties.getBoolean(
+ getSystemPropertyName(KEY_APPSETID_KILL_SWITCH),
+ /* defaultValue */ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_APPSETID_KILL_SWITCH,
+ /* defaultValue */ APPSETID_KILL_SWITCH));
}
// TOPICS Killswitches
@@ -1283,6 +1389,22 @@ public final class PhFlags implements Flags {
}
@Override
+ public boolean getAdSelectionOffDeviceEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_FLEDE_AD_SELECTION_OFF_DEVICE_ENABLED,
+ FLEDGE_AD_SELECTION_OFF_DEVICE_ENABLED);
+ }
+
+ @Override
+ public boolean getAdSelectionOffDeviceRequestCompressionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED,
+ FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED);
+ }
+
+ @Override
public boolean isDisableTopicsEnrollmentCheck() {
return SystemProperties.getBoolean(
getSystemPropertyName(KEY_DISABLE_TOPICS_ENROLLMENT_CHECK),
@@ -1454,11 +1576,6 @@ public final class PhFlags implements Flags {
/* defaultValue */ WEB_CONTEXT_CLIENT_ALLOW_LIST);
}
- @VisibleForTesting
- static String getSystemPropertyName(String key) {
- return SYSTEM_PROPERTY_PREFIX + key;
- }
-
@Override
public boolean getConsentNotificationDebugMode() {
return DeviceConfig.getBoolean(
@@ -1482,6 +1599,46 @@ public final class PhFlags implements Flags {
/* defaultValue */ MAX_RESPONSE_BASED_REGISTRATION_SIZE_BYTES);
}
+ @VisibleForTesting
+ static String getSystemPropertyName(String key) {
+ return SYSTEM_PROPERTY_PREFIX + key;
+ }
+
+ @Override
+ public boolean getUIDialogsFeatureEnabled() {
+ // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
+ return SystemProperties.getBoolean(
+ getSystemPropertyName(KEY_UI_DIALOGS_FEATURE_ENABLED),
+ /* defaultValue */ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_UI_DIALOGS_FEATURE_ENABLED,
+ /* defaultValue */ UI_DIALOGS_FEATURE_ENABLED));
+ }
+
+ @Override
+ public long getAdSelectionExpirationWindowS() {
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S,
+ /* defaultValue */ FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S);
+ }
+
+ @Override
+ public boolean getEnableTopicContributorsCheck() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_ENABLE_TOPIC_CONTRIBUTORS_CHECK,
+ /* defaultValue */ ENABLE_TOPIC_CONTRIBUTORS_CHECK);
+ }
+
+ @Override
+ public boolean getEnableDatabaseSchemaVersion3() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_ENABLE_DATABASE_SCHEMA_VERSION_3,
+ /* defaultValue */ ENABLE_DATABASE_SCHEMA_VERSION_3);
+ }
+
@Override
public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
writer.println("==== AdServices PH Flags Dump Enrollment ====");
@@ -1569,6 +1726,7 @@ public final class PhFlags implements Flags {
+ " = "
+ getTopicsNumberOfLookBackEpochs());
+ writer.println("==== AdServices PH Flags Dump Topics Classifier related flags ====");
writer.println(
"\t"
+ KEY_CLASSIFIER_NUMBER_OF_TOP_LABELS
@@ -1576,6 +1734,21 @@ public final class PhFlags implements Flags {
+ getClassifierNumberOfTopLabels());
writer.println("\t" + KEY_CLASSIFIER_TYPE + " = " + getClassifierType());
writer.println("\t" + KEY_CLASSIFIER_THRESHOLD + " = " + getClassifierThreshold());
+ writer.println(
+ "\t"
+ + KEY_CLASSIFIER_DESCRIPTION_MAX_LENGTH
+ + " = "
+ + getClassifierDescriptionMaxLength());
+ writer.println(
+ "\t"
+ + KEY_CLASSIFIER_DESCRIPTION_MAX_WORDS
+ + " = "
+ + getClassifierDescriptionMaxWords());
+ writer.println(
+ "\t"
+ + KEY_CLASSIFIER_FORCE_USE_BUNDLED_FILES
+ + " = "
+ + getClassifierForceUseBundledFiles());
writer.println(
"\t"
@@ -1806,6 +1979,11 @@ public final class PhFlags implements Flags {
+ getAdSelectionBiddingTimeoutPerCaMs());
writer.println(
"\t"
+ + KEY_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS
+ + " = "
+ + getAdSelectionBiddingTimeoutPerBuyerMs());
+ writer.println(
+ "\t"
+ KEY_FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS
+ " = "
+ getAdSelectionScoringTimeoutMs());
@@ -1816,45 +1994,64 @@ public final class PhFlags implements Flags {
+ getAdSelectionOverallTimeoutMs());
writer.println(
"\t"
+ + KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS
+ + " = "
+ + getAdSelectionOffDeviceOverallTimeoutMs());
+ writer.println(
+ "\t"
+ KEY_FLEDGE_REPORT_IMPRESSION_OVERALL_TIMEOUT_MS
+ " = "
+ getReportImpressionOverallTimeoutMs());
-
writer.println(
"\t"
+ KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_OVERRIDE
+ " = "
+ getEnforceForegroundStatusForFledgeOverrides());
-
writer.println(
"\t"
+ KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_REPORT_IMPRESSION
+ " = "
+ getEnforceForegroundStatusForFledgeReportImpression());
-
writer.println(
"\t"
+ KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_RUN_AD_SELECTION
+ " = "
+ getEnforceForegroundStatusForFledgeRunAdSelection());
-
writer.println(
"\t"
+ KEY_ENFORCE_FOREGROUND_STATUS_FLEDGE_CUSTOM_AUDIENCE
+ " = "
+ getEnforceForegroundStatusForFledgeCustomAudience());
-
writer.println(
"\t"
+ KEY_FOREGROUND_STATUS_LEVEL
+ " = "
+ getForegroundStatuslLevelForValidation());
+ writer.println(
+ "\t"
+ + KEY_FLEDE_AD_SELECTION_OFF_DEVICE_ENABLED
+ + " = "
+ + getAdSelectionOffDeviceEnabled());
+ writer.println(
+ "\t"
+ + KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED
+ + " = "
+ + getAdSelectionOffDeviceRequestCompressionEnabled());
writer.println(
"\t" + KEY_ENFORCE_ISOLATE_MAX_HEAP_SIZE + " = " + getEnforceIsolateMaxHeapSize());
writer.println(
"\t" + KEY_ISOLATE_MAX_HEAP_SIZE_BYTES + " = " + getIsolateMaxHeapSizeBytes());
+ writer.println(
+ "\t"
+ + KEY_FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S
+ + " = "
+ + getAdSelectionExpirationWindowS());
+
+ writer.println("==== AdServices PH Flags Dump UI Related Flags ====");
+ writer.println(
+ "\t" + KEY_UI_DIALOGS_FEATURE_ENABLED + " = " + getUIDialogsFeatureEnabled());
writer.println("==== AdServices PH Flags Dump STATUS ====");
writer.println("\t" + KEY_ADSERVICES_ENABLED + " = " + getAdServicesEnabled());
@@ -1864,4 +2061,22 @@ public final class PhFlags implements Flags {
+ " = "
+ getForegroundStatuslLevelForValidation());
}
+
+ @Override
+ public boolean isEnrollmentBlocklisted(String enrollmentId) {
+ return getEnrollmentBlocklist().contains(enrollmentId);
+ }
+
+ @VisibleForTesting
+ @Override
+ public ImmutableList<String> getEnrollmentBlocklist() {
+ String blocklistFlag =
+ DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_ADSERVICES, KEY_ENROLLMENT_BLOCKLIST_IDS, "");
+ if (TextUtils.isEmpty(blocklistFlag)) {
+ return ImmutableList.of();
+ }
+ String[] blocklistList = blocklistFlag.split(",");
+ return ImmutableList.copyOf(blocklistList);
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/adid/AdIdServiceImpl.java b/adservices/service-core/java/com/android/adservices/service/adid/AdIdServiceImpl.java
index 0152eb87d..3782c4aa4 100644
--- a/adservices/service-core/java/com/android/adservices/service/adid/AdIdServiceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/adid/AdIdServiceImpl.java
@@ -19,6 +19,7 @@ import static android.adservices.common.AdServicesStatusUtils.STATUS_BACKGROUND_
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_PERMISSION_NOT_REQUESTED;
+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;
@@ -45,6 +46,7 @@ import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException;
import com.android.adservices.service.common.PermissionHelper;
import com.android.adservices.service.common.SdkRuntimeUtil;
+import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesStatsLog;
import com.android.adservices.service.stats.ApiCallStats;
@@ -64,6 +66,7 @@ public class AdIdServiceImpl extends IAdIdService.Stub {
private final AdServicesLogger mAdServicesLogger;
private final Clock mClock;
private final Flags mFlags;
+ private final Throttler mThrottler;
private final AppImportanceFilter mAppImportanceFilter;
public AdIdServiceImpl(
@@ -72,12 +75,14 @@ public class AdIdServiceImpl extends IAdIdService.Stub {
AdServicesLogger adServicesLogger,
Clock clock,
Flags flags,
+ Throttler throttler,
AppImportanceFilter appImportanceFilter) {
mContext = context;
mAdIdWorker = adidWorker;
mAdServicesLogger = adServicesLogger;
mClock = clock;
mFlags = flags;
+ mThrottler = throttler;
mAppImportanceFilter = appImportanceFilter;
}
@@ -87,6 +92,8 @@ public class AdIdServiceImpl extends IAdIdService.Stub {
@NonNull CallerMetadata callerMetadata,
@NonNull IGetAdIdCallback callback) {
+ if (isThrottled(adIdParam, callback)) return;
+
final long startServiceTime = mClock.elapsedRealtime();
final String packageName = adIdParam.getAppPackageName();
final String sdkPackageName = adIdParam.getSdkPackageName();
@@ -138,6 +145,26 @@ public class AdIdServiceImpl extends IAdIdService.Stub {
});
}
+ // Throttle the AdId API.
+ // Return true if we should throttle (don't allow the API call).
+ private boolean isThrottled(GetAdIdParam adIdParam, IGetAdIdCallback callback) {
+ boolean throttled =
+ !mThrottler.tryAcquire(
+ Throttler.ApiKey.ADID_API_APP_PACKAGE_NAME, adIdParam.getAppPackageName());
+
+ if (throttled) {
+ LogUtil.e("Rate Limit Reached for ADID_API");
+ try {
+ callback.onError(STATUS_RATE_LIMIT_REACHED);
+ } catch (RemoteException e) {
+ LogUtil.e(e, "Fail to call the callback on Rate Limit Reached.");
+ } finally {
+ return true;
+ }
+ }
+ return false;
+ }
+
// Enforce whether caller is from foreground.
private void enforceForeground(int callingUid) {
// If caller calls Topics API from Sandbox, regard it as foreground.
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/AdBidGenerator.java b/adservices/service-core/java/com/android/adservices/service/adselection/AdBidGenerator.java
index 9271cc0b7..65d67fe3f 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/AdBidGenerator.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/AdBidGenerator.java
@@ -16,7 +16,6 @@
package com.android.adservices.service.adselection;
-import android.adservices.adselection.AdSelectionConfig;
import android.adservices.common.AdSelectionSignals;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,7 +36,6 @@ interface AdBidGenerator {
* @param contextualSignals Contextual information about the App where the Ad is being shown, Ad
* slot and size, geographic location information, the seller invoking the ad selection and
* so on.
- * @param adSelectionConfig used as the primary key in remote overrides
* @return a future contains either a {@link AdBiddingOutcome} containing the candidate ad with
* the best bid for this custom audience or null if no valid ads are available for scoring.
*/
@@ -46,6 +44,5 @@ interface AdBidGenerator {
@NonNull DBCustomAudience customAudience,
@NonNull AdSelectionSignals adSelectionSignals,
@NonNull AdSelectionSignals buyerSignals,
- @NonNull AdSelectionSignals contextualSignals,
- @NonNull AdSelectionConfig adSelectionConfig);
+ @NonNull AdSelectionSignals contextualSignals);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/AdBidGeneratorImpl.java b/adservices/service-core/java/com/android/adservices/service/adselection/AdBidGeneratorImpl.java
index 0cfb4a31b..4e950085f 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/AdBidGeneratorImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/AdBidGeneratorImpl.java
@@ -16,7 +16,6 @@
package com.android.adservices.service.adselection;
-import android.adservices.adselection.AdSelectionConfig;
import android.adservices.adselection.AdWithBid;
import android.adservices.common.AdData;
import android.adservices.common.AdSelectionSignals;
@@ -60,13 +59,11 @@ import java.util.stream.Collectors;
*/
public class AdBidGeneratorImpl implements AdBidGenerator {
- @VisibleForTesting static final String QUERY_PARAM_KEYS = "keys";
-
@VisibleForTesting
- static final String MISSING_TRUSTED_BIDDING_SIGNALS = "Error fetching trusted bidding signals";
+ static final String QUERY_PARAM_KEYS = "keys";
@VisibleForTesting
- static final String MISSING_BIDDING_LOGIC = "Error fetching bidding js logic";
+ static final String MISSING_TRUSTED_BIDDING_SIGNALS = "Error fetching trusted bidding signals";
@VisibleForTesting
static final String BIDDING_TIMED_OUT = "Bidding exceeded allowed time limit";
@@ -78,16 +75,19 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
@NonNull private final Context mContext;
@NonNull private final ListeningExecutorService mLightweightExecutorService;
@NonNull private final ListeningExecutorService mBackgroundExecutorService;
+ @NonNull private final ScheduledThreadPoolExecutor mScheduledExecutor;
@NonNull private final AdSelectionScriptEngine mAdSelectionScriptEngine;
@NonNull private final AdServicesHttpsClient mAdServicesHttpsClient;
@NonNull private final CustomAudienceDevOverridesHelper mCustomAudienceDevOverridesHelper;
@NonNull private final Flags mFlags;
+ @NonNull private final JsFetcher mJsFetcher;
public AdBidGeneratorImpl(
@NonNull Context context,
@NonNull AdServicesHttpsClient adServicesHttpsClient,
@NonNull ListeningExecutorService lightweightExecutorService,
@NonNull ListeningExecutorService backgroundExecutorService,
+ @NonNull ScheduledThreadPoolExecutor scheduledExecutor,
@NonNull DevContext devContext,
@NonNull CustomAudienceDao customAudienceDao,
@NonNull Flags flags) {
@@ -95,6 +95,7 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
Objects.requireNonNull(adServicesHttpsClient);
Objects.requireNonNull(lightweightExecutorService);
Objects.requireNonNull(backgroundExecutorService);
+ Objects.requireNonNull(scheduledExecutor);
Objects.requireNonNull(devContext);
Objects.requireNonNull(customAudienceDao);
Objects.requireNonNull(flags);
@@ -102,6 +103,7 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
mContext = context;
mLightweightExecutorService = lightweightExecutorService;
mBackgroundExecutorService = backgroundExecutorService;
+ mScheduledExecutor = scheduledExecutor;
mAdServicesHttpsClient = adServicesHttpsClient;
mCustomAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(devContext, customAudienceDao);
@@ -111,6 +113,12 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
mContext,
() -> mFlags.getEnforceIsolateMaxHeapSize(),
() -> mFlags.getIsolateMaxHeapSizeBytes());
+ mJsFetcher =
+ new JsFetcher(
+ backgroundExecutorService,
+ lightweightExecutorService,
+ mCustomAudienceDevOverridesHelper,
+ adServicesHttpsClient);
}
@VisibleForTesting
@@ -118,27 +126,33 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
@NonNull Context context,
@NonNull ListeningExecutorService lightWeightExecutorService,
@NonNull ListeningExecutorService backgroundExecutorService,
+ @NonNull ScheduledThreadPoolExecutor scheduledExecutor,
@NonNull AdSelectionScriptEngine adSelectionScriptEngine,
@NonNull AdServicesHttpsClient adServicesHttpsClient,
@NonNull CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper,
@NonNull Flags flags,
- @NonNull IsolateSettings isolateSettings) {
+ @NonNull IsolateSettings isolateSettings,
+ @NonNull JsFetcher jsFetcher) {
Objects.requireNonNull(context);
Objects.requireNonNull(lightWeightExecutorService);
Objects.requireNonNull(backgroundExecutorService);
+ Objects.requireNonNull(scheduledExecutor);
Objects.requireNonNull(adSelectionScriptEngine);
Objects.requireNonNull(adServicesHttpsClient);
Objects.requireNonNull(customAudienceDevOverridesHelper);
Objects.requireNonNull(flags);
Objects.requireNonNull(isolateSettings);
+ Objects.requireNonNull(jsFetcher);
mContext = context;
mLightweightExecutorService = lightWeightExecutorService;
mBackgroundExecutorService = backgroundExecutorService;
+ mScheduledExecutor = scheduledExecutor;
mAdSelectionScriptEngine = adSelectionScriptEngine;
mAdServicesHttpsClient = adServicesHttpsClient;
mCustomAudienceDevOverridesHelper = customAudienceDevOverridesHelper;
mFlags = flags;
+ mJsFetcher = jsFetcher;
}
@Override
@@ -147,13 +161,11 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
@NonNull DBCustomAudience customAudience,
@NonNull AdSelectionSignals adSelectionSignals,
@NonNull AdSelectionSignals buyerSignals,
- @NonNull AdSelectionSignals contextualSignals,
- @NonNull AdSelectionConfig adSelectionConfig) {
+ @NonNull AdSelectionSignals contextualSignals) {
Objects.requireNonNull(customAudience);
Objects.requireNonNull(adSelectionSignals);
Objects.requireNonNull(buyerSignals);
Objects.requireNonNull(contextualSignals);
- Objects.requireNonNull(adSelectionConfig);
LogUtil.v("Running Ad Bidding for CA : %s", customAudience.getName());
if (customAudience.getAds().isEmpty()) {
@@ -161,14 +173,13 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
return FluentFuture.from(Futures.immediateFuture(null));
}
- AdSelectionSignals userSignals = buildUserSignals(customAudience);
CustomAudienceSignals customAudienceSignals =
CustomAudienceSignals.buildFromCustomAudience(customAudience);
// TODO(b/221862406): implement ads filtering logic.
FluentFuture<String> buyerDecisionLogic =
- getBuyerDecisionLogic(
+ mJsFetcher.getBuyerDecisionLogic(
customAudience.getBiddingLogicUri(),
customAudience.getOwner(),
customAudience.getBuyer(),
@@ -183,7 +194,6 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
buyerSignals,
contextualSignals,
customAudienceSignals,
- userSignals,
adSelectionSignals);
},
mLightweightExecutorService);
@@ -216,9 +226,7 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
.withTimeout(
mFlags.getAdSelectionBiddingTimeoutPerCaMs(),
TimeUnit.MILLISECONDS,
- // TODO(b/237103033): Comply with thread usage policy for AdServices;
- // use a global scheduled executor
- new ScheduledThreadPoolExecutor(1))
+ mScheduledExecutor)
.catching(
JSONException.class, this::handleBiddingError, mLightweightExecutorService)
.catching(
@@ -293,59 +301,10 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
mLightweightExecutorService);
}
- private FluentFuture<String> getBuyerDecisionLogic(
- @NonNull final Uri decisionLogicUri,
- @NonNull String owner,
- @NonNull AdTechIdentifier buyer,
- @NonNull String name) {
- FluentFuture<String> jsOverrideFuture =
- FluentFuture.from(
- mBackgroundExecutorService.submit(
- () ->
- mCustomAudienceDevOverridesHelper.getBiddingLogicOverride(
- owner, buyer, name)));
- return jsOverrideFuture
- .transformAsync(
- jsOverride -> {
- if (jsOverride == null) {
- LogUtil.v(
- "Fetching buyer decision logic from server: %s",
- decisionLogicUri.toString());
- return mAdServicesHttpsClient.fetchPayload(decisionLogicUri);
- } else {
- LogUtil.d(
- "Developer options enabled and an override JS is provided "
- + "for the current Custom Audience. "
- + "Skipping call to server.");
- return Futures.immediateFuture(jsOverride);
- }
- },
- mLightweightExecutorService)
- .catching(
- Exception.class,
- e -> {
- LogUtil.w(
- e, "Exception encountered when fetching buyer decision logic");
- throw new IllegalStateException(MISSING_BIDDING_LOGIC);
- },
- mLightweightExecutorService);
- }
-
/**
- * @return user information with respect to the custom audience will be available to
- * generateBid(). This could include language, demographic information, information about
- * custom audience such as time in list, number of impressions, last N winning impression
- * timestamp etc.
+ * @return the {@link AdWithBid} with the best bid per CustomAudience.
*/
@NonNull
- public AdSelectionSignals buildUserSignals(@Nullable DBCustomAudience customAudience) {
- // TODO: implement how to build user_signals with respect to customAudience.
- LogUtil.v("Building Custom Audience User Signals %s", customAudience.getName());
- return AdSelectionSignals.EMPTY;
- }
-
- /** @return the {@link AdWithBid} with the best bid per CustomAudience. */
- @NonNull
@VisibleForTesting
FluentFuture<Pair<AdWithBid, String>> runBidding(
@NonNull DBCustomAudience customAudience,
@@ -353,7 +312,6 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
@NonNull AdSelectionSignals buyerSignals,
@NonNull AdSelectionSignals contextualSignals,
@NonNull CustomAudienceSignals customAudienceSignals,
- @NonNull AdSelectionSignals userSignals,
@NonNull AdSelectionSignals adSelectionSignals) {
FluentFuture<AdSelectionSignals> trustedBiddingSignals =
getTrustedBiddingSignals(
@@ -380,7 +338,6 @@ public class AdBidGeneratorImpl implements AdBidGenerator {
buyerSignals,
biddingSignals,
contextualSignals,
- userSignals,
customAudienceSignals);
},
mLightweightExecutorService)
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionRunner.java b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionRunner.java
index 2f69f2f3d..7851ef398 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionRunner.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionRunner.java
@@ -23,18 +23,14 @@ import android.adservices.adselection.AdSelectionCallback;
import android.adservices.adselection.AdSelectionConfig;
import android.adservices.adselection.AdSelectionInput;
import android.adservices.adselection.AdSelectionResponse;
-import android.adservices.common.AdSelectionSignals;
import android.adservices.common.AdServicesStatusUtils;
import android.adservices.common.FledgeErrorResponse;
-import android.adservices.exceptions.AdServicesException;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.LimitExceededException;
import android.os.RemoteException;
-import android.util.Pair;
import com.android.adservices.LogUtil;
import com.android.adservices.data.adselection.AdSelectionEntryDao;
@@ -43,18 +39,18 @@ import com.android.adservices.data.adselection.DBBuyerDecisionLogic;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.DBCustomAudience;
import com.android.adservices.service.Flags;
-import com.android.adservices.service.common.AdServicesHttpsClient;
import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException;
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.consent.ConsentManager;
-import com.android.adservices.service.devapi.DevContext;
+import com.android.adservices.service.js.JSSandboxIsNotAvailableException;
+import com.android.adservices.service.js.JSScriptEngine;
import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.ApiServiceLatencyCalculator;
import com.android.internal.annotations.VisibleForTesting;
-import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FluentFuture;
@@ -66,20 +62,13 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.UncheckedTimeoutException;
import java.time.Clock;
-import java.time.Instant;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
/**
* Orchestrator that runs the Ads Auction/Bidding and Scoring logic The class expects the caller to
@@ -88,7 +77,7 @@ import java.util.stream.Collectors;
*
* <p>Class takes in an executor on which it runs the AdSelection logic
*/
-public final class AdSelectionRunner {
+public abstract class AdSelectionRunner {
@VisibleForTesting static final String AD_SELECTION_ERROR_PATTERN = "%s: %s";
@@ -112,86 +101,90 @@ public final class AdSelectionRunner {
@VisibleForTesting
static final String AD_SELECTION_THROTTLED = "Ad selection exceeded allowed rate limit";
+ @VisibleForTesting
+ static final String JS_SANDBOX_IS_NOT_AVAILABLE =
+ String.format(
+ AD_SELECTION_ERROR_PATTERN,
+ ERROR_AD_SELECTION_FAILURE,
+ "JS Sandbox is not available");
+
public static final long DAY_IN_SECONDS = 60 * 60 * 24;
- @NonNull private final Context mContext;
- @NonNull private final CustomAudienceDao mCustomAudienceDao;
- @NonNull private final AdSelectionEntryDao mAdSelectionEntryDao;
- @NonNull private final AdServicesHttpsClient mAdServicesHttpsClient;
- @NonNull private final ListeningExecutorService mLightweightExecutorService;
- @NonNull private final ListeningExecutorService mBackgroundExecutorService;
- @NonNull private final AdsScoreGenerator mAdsScoreGenerator;
- @NonNull private final AdBidGenerator mAdBidGenerator;
- @NonNull private final AdSelectionIdGenerator mAdSelectionIdGenerator;
- @NonNull private final Clock mClock;
- @NonNull private final ConsentManager mConsentManager;
- @NonNull private final AdServicesLogger mAdServicesLogger;
- @NonNull private final Flags mFlags;
- @NonNull private final AppImportanceFilter mAppImportanceFilter;
- private final int mCallerUid;
- @NonNull private final Supplier<Throttler> mThrottlerSupplier;
- @NonNull private final FledgeAuthorizationFilter mFledgeAuthorizationFilter;
- @NonNull private final FledgeAllowListsFilter mFledgeAllowListsFilter;
+ @NonNull protected final Context mContext;
+ @NonNull protected final CustomAudienceDao mCustomAudienceDao;
+ @NonNull protected final AdSelectionEntryDao mAdSelectionEntryDao;
+ @NonNull protected final ListeningExecutorService mLightweightExecutorService;
+ @NonNull protected final ListeningExecutorService mBackgroundExecutorService;
+ @NonNull protected final ScheduledThreadPoolExecutor mScheduledExecutor;
+ @NonNull protected final AdSelectionIdGenerator mAdSelectionIdGenerator;
+ @NonNull protected final Clock mClock;
+ @NonNull protected final ConsentManager mConsentManager;
+ @NonNull protected final AdServicesLogger mAdServicesLogger;
+ @NonNull protected final Flags mFlags;
+ @NonNull protected final AppImportanceFilter mAppImportanceFilter;
+ @NonNull protected final Supplier<Throttler> mThrottlerSupplier;
+ @NonNull protected final FledgeAuthorizationFilter mFledgeAuthorizationFilter;
+ @NonNull protected final FledgeAllowListsFilter mFledgeAllowListsFilter;
+ @NonNull protected final ApiServiceLatencyCalculator mApiServiceLatencyCalculator;
+ protected final int mCallerUid;
+ /**
+ * @param context service context
+ * @param customAudienceDao DAO to access custom audience storage
+ * @param adSelectionEntryDao DAO to access ad selection storage
+ * @param lightweightExecutorService executor for running short tasks
+ * @param backgroundExecutorService executor for longer running tasks (ex. network calls)
+ * @param scheduledExecutor executor for tasks to be run with a delay or timed executions
+ * @param consentManager instance of {@link ConsentManager} for verifying user consent
+ * @param adServicesLogger logger for logging calls to PPAPI
+ * @param appImportanceFilter filter to assert calling app is running in the foreground
+ * @param flags for accessing feature flags
+ * @param throttlerSupplier supplier for throttling calls to PPAPI
+ * @param callerUid calling app UID
+ * @param fledgeAuthorizationFilter filter for authorizing the caller on certain behavior
+ * @param fledgeAllowListsFilter filter for verifying the caller can call PPAPI
+ */
public AdSelectionRunner(
@NonNull final Context context,
@NonNull final CustomAudienceDao customAudienceDao,
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
- @NonNull final AdServicesHttpsClient adServicesHttpsClient,
@NonNull final ExecutorService lightweightExecutorService,
@NonNull final ExecutorService backgroundExecutorService,
+ @NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
@NonNull final ConsentManager consentManager,
@NonNull final AdServicesLogger adServicesLogger,
- @NonNull final DevContext devContext,
@NonNull AppImportanceFilter appImportanceFilter,
@NonNull final Flags flags,
@NonNull final Supplier<Throttler> throttlerSupplier,
int callerUid,
@NonNull final FledgeAuthorizationFilter fledgeAuthorizationFilter,
- @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter) {
+ @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter,
+ @NonNull final ApiServiceLatencyCalculator apiServiceLatencyCalculator) {
Objects.requireNonNull(context);
Objects.requireNonNull(customAudienceDao);
Objects.requireNonNull(adSelectionEntryDao);
- Objects.requireNonNull(adServicesHttpsClient);
Objects.requireNonNull(lightweightExecutorService);
Objects.requireNonNull(backgroundExecutorService);
Objects.requireNonNull(consentManager);
Objects.requireNonNull(adServicesLogger);
- Objects.requireNonNull(devContext);
Objects.requireNonNull(appImportanceFilter);
Objects.requireNonNull(flags);
Objects.requireNonNull(throttlerSupplier);
Objects.requireNonNull(fledgeAuthorizationFilter);
Objects.requireNonNull(fledgeAllowListsFilter);
+ Preconditions.checkArgument(
+ JSScriptEngine.AvailabilityChecker.isJSSandboxAvailable(),
+ JS_SANDBOX_IS_NOT_AVAILABLE);
+ Objects.requireNonNull(apiServiceLatencyCalculator);
+
mContext = context;
mCustomAudienceDao = customAudienceDao;
mAdSelectionEntryDao = adSelectionEntryDao;
- mAdServicesHttpsClient = adServicesHttpsClient;
mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService);
mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService);
+ mScheduledExecutor = scheduledExecutor;
mConsentManager = consentManager;
mAdServicesLogger = adServicesLogger;
- mAdsScoreGenerator =
- new AdsScoreGeneratorImpl(
- new AdSelectionScriptEngine(
- mContext,
- () -> flags.getEnforceIsolateMaxHeapSize(),
- () -> flags.getIsolateMaxHeapSizeBytes()),
- mLightweightExecutorService,
- mBackgroundExecutorService,
- mAdServicesHttpsClient,
- devContext,
- mAdSelectionEntryDao,
- flags);
- mAdBidGenerator =
- new AdBidGeneratorImpl(
- context,
- mAdServicesHttpsClient,
- mLightweightExecutorService,
- mBackgroundExecutorService,
- devContext,
- mCustomAudienceDao,
- flags);
mAdSelectionIdGenerator = new AdSelectionIdGenerator();
mClock = Clock.systemUTC();
mFlags = flags;
@@ -200,6 +193,7 @@ public final class AdSelectionRunner {
mCallerUid = callerUid;
mFledgeAuthorizationFilter = fledgeAuthorizationFilter;
mFledgeAllowListsFilter = fledgeAllowListsFilter;
+ mApiServiceLatencyCalculator = apiServiceLatencyCalculator;
}
@VisibleForTesting
@@ -207,12 +201,10 @@ public final class AdSelectionRunner {
@NonNull final Context context,
@NonNull final CustomAudienceDao customAudienceDao,
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
- @NonNull final AdServicesHttpsClient adServicesHttpsClient,
@NonNull final ExecutorService lightweightExecutorService,
@NonNull final ExecutorService backgroundExecutorService,
+ @NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
@NonNull final ConsentManager consentManager,
- @NonNull final AdsScoreGenerator adsScoreGenerator,
- @NonNull final AdBidGenerator adBidGenerator,
@NonNull final AdSelectionIdGenerator adSelectionIdGenerator,
@NonNull Clock clock,
@NonNull final AdServicesLogger adServicesLogger,
@@ -221,32 +213,30 @@ public final class AdSelectionRunner {
@NonNull final Supplier<Throttler> throttlerSupplier,
int callerUid,
@NonNull final FledgeAuthorizationFilter fledgeAuthorizationFilter,
- @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter) {
+ @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter,
+ @NonNull final ApiServiceLatencyCalculator apiServiceLatencyCalculator) {
Objects.requireNonNull(context);
Objects.requireNonNull(customAudienceDao);
Objects.requireNonNull(adSelectionEntryDao);
- Objects.requireNonNull(adServicesHttpsClient);
Objects.requireNonNull(lightweightExecutorService);
Objects.requireNonNull(backgroundExecutorService);
+ Objects.requireNonNull(scheduledExecutor);
Objects.requireNonNull(consentManager);
- Objects.requireNonNull(adsScoreGenerator);
- Objects.requireNonNull(adBidGenerator);
Objects.requireNonNull(adSelectionIdGenerator);
Objects.requireNonNull(clock);
Objects.requireNonNull(adServicesLogger);
Objects.requireNonNull(appImportanceFilter);
Objects.requireNonNull(flags);
Objects.requireNonNull(fledgeAuthorizationFilter);
+ Objects.requireNonNull(apiServiceLatencyCalculator);
mContext = context;
mCustomAudienceDao = customAudienceDao;
mAdSelectionEntryDao = adSelectionEntryDao;
- mAdServicesHttpsClient = adServicesHttpsClient;
mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService);
mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService);
+ mScheduledExecutor = scheduledExecutor;
mConsentManager = consentManager;
- mAdsScoreGenerator = adsScoreGenerator;
- mAdBidGenerator = adBidGenerator;
mAdSelectionIdGenerator = adSelectionIdGenerator;
mClock = clock;
mAdServicesLogger = adServicesLogger;
@@ -256,6 +246,7 @@ public final class AdSelectionRunner {
mCallerUid = callerUid;
mFledgeAuthorizationFilter = fledgeAuthorizationFilter;
mFledgeAllowListsFilter = fledgeAllowListsFilter;
+ mApiServiceLatencyCalculator = apiServiceLatencyCalculator;
}
/**
@@ -293,8 +284,6 @@ public final class AdSelectionRunner {
@Override
public void onSuccess(DBAdSelection result) {
notifySuccessToCaller(result, callback);
- // TODO(242280808): Schedule a clear for stale data instead of this hack
- clearExpiredAdSelectionData();
}
@Override
@@ -306,12 +295,11 @@ public final class AdSelectionRunner {
} else {
notifyFailureToCaller(callback, t);
}
- // TODO(242280808): Schedule a clear for stale data instead of this hack
- clearExpiredAdSelectionData();
}
},
mLightweightExecutorService);
} catch (Throwable t) {
+ LogUtil.v("run ad selection fails fast with exception %s.", t.toString());
notifyFailureToCaller(callback, t);
}
}
@@ -330,11 +318,15 @@ public final class AdSelectionRunner {
LogUtil.e(e, "Encountered exception during notifying AdSelection callback");
resultCode = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR;
} finally {
+ int overallLatencyMs = mApiServiceLatencyCalculator.getApiServiceOverallLatencyMs();
LogUtil.v(
- "Ad Selection with Id:%d completed, attempted notifying success",
- result.getAdSelectionId());
+ "Ad Selection with Id:%d completed with overall latency %d in ms, "
+ + "attempted notifying success",
+ result.getAdSelectionId(), overallLatencyMs);
+ // TODO(b//253522566): When including logging data from bidding & auction server side
+ // should be able to differentiate the data from the on-device telemetry.
mAdServicesLogger.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode);
+ AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode, overallLatencyMs);
}
}
@@ -350,8 +342,15 @@ public final class AdSelectionRunner {
LogUtil.e(e, "Encountered exception during notifying AdSelection callback");
resultCode = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR;
} finally {
+ int overallLatencyMs = mApiServiceLatencyCalculator.getApiServiceOverallLatencyMs();
+ LogUtil.v(
+ "Ad Selection with Id:%d completed with overall latency %d in ms, "
+ + "attempted notifying success for a silent failure",
+ mAdSelectionIdGenerator.generateId(), overallLatencyMs);
+ // TODO(b//253522566): When including logging data from bidding & auction server side
+ // should be able to differentiate the data from the on-device telemetry.
mAdServicesLogger.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode);
+ AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode, overallLatencyMs);
}
}
@@ -372,6 +371,8 @@ public final class AdSelectionRunner {
resultCode = AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
} else if (t instanceof LimitExceededException) {
resultCode = AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
+ } else if (t instanceof JSSandboxIsNotAvailableException) {
+ resultCode = AdServicesStatusUtils.STATUS_JS_SANDBOX_UNAVAILABLE;
} else {
resultCode = AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
}
@@ -391,8 +392,12 @@ public final class AdSelectionRunner {
LogUtil.e(e, "Encountered exception during notifying AdSelection callback");
resultCode = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR;
} finally {
+ int overallLatencyMs = mApiServiceLatencyCalculator.getApiServiceOverallLatencyMs();
+ LogUtil.v("Ad Selection failed with overall latency %d in ms", overallLatencyMs);
+ // TODO(b//253522566): When including logging data from bidding & auction server side
+ // should be able to differentiate the data from the on-device telemetry.
mAdServicesLogger.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode);
+ AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode, overallLatencyMs);
}
}
@@ -410,60 +415,34 @@ public final class AdSelectionRunner {
ListenableFuture<List<DBCustomAudience>> buyerCustomAudience =
getBuyersCustomAudience(adSelectionConfig);
+ ListenableFuture<AdSelectionOrchestrationResult> dbAdSelection =
+ orchestrateAdSelection(adSelectionConfig, callerPackageName, buyerCustomAudience);
- AsyncFunction<List<DBCustomAudience>, List<AdBiddingOutcome>> bidAds =
- buyerCAs -> {
- return runAdBidding(buyerCAs, adSelectionConfig);
- };
-
- ListenableFuture<List<AdBiddingOutcome>> biddingOutcome =
- Futures.transformAsync(buyerCustomAudience, bidAds, mLightweightExecutorService);
-
- AsyncFunction<List<AdBiddingOutcome>, List<AdScoringOutcome>> mapBidsToScores =
- bids -> {
- return runAdScoring(bids, adSelectionConfig);
- };
-
- ListenableFuture<List<AdScoringOutcome>> scoredAds =
- Futures.transformAsync(
- biddingOutcome, mapBidsToScores, mLightweightExecutorService);
-
- Function<List<AdScoringOutcome>, AdScoringOutcome> reduceScoresToWinner =
- scores -> {
- return getWinningOutcome(scores);
- };
-
- ListenableFuture<AdScoringOutcome> winningOutcome =
- Futures.transform(scoredAds, reduceScoresToWinner, mLightweightExecutorService);
-
- Function<AdScoringOutcome, Pair<DBAdSelection.Builder, String>> mapWinnerToDBResult =
- scoringWinner -> {
- return createAdSelectionResult(scoringWinner);
- };
-
- ListenableFuture<Pair<DBAdSelection.Builder, String>> dbAdSelectionBuilder =
- Futures.transform(winningOutcome, mapWinnerToDBResult, mLightweightExecutorService);
-
- AsyncFunction<Pair<DBAdSelection.Builder, String>, DBAdSelection> saveResultToPersistence =
+ AsyncFunction<AdSelectionOrchestrationResult, DBAdSelection> saveResultToPersistence =
adSelectionAndJs -> {
return persistAdSelection(
- adSelectionAndJs.first, adSelectionAndJs.second, callerPackageName);
+ adSelectionAndJs.mDbAdSelectionBuilder,
+ adSelectionAndJs.mBuyerDecisionLogicJs,
+ callerPackageName);
};
- return FluentFuture.from(dbAdSelectionBuilder)
+ return FluentFuture.from(dbAdSelection)
.transformAsync(saveResultToPersistence, mLightweightExecutorService)
.withTimeout(
mFlags.getAdSelectionOverallTimeoutMs(),
TimeUnit.MILLISECONDS,
- // TODO(b/237103033): Comply with thread usage policy for AdServices;
- // use a global scheduled executor
- new ScheduledThreadPoolExecutor(1))
+ mScheduledExecutor)
.catching(
TimeoutException.class,
this::handleTimeoutError,
mLightweightExecutorService);
}
+ abstract ListenableFuture<AdSelectionOrchestrationResult> orchestrateAdSelection(
+ @NonNull AdSelectionConfig adSelectionConfig,
+ @NonNull String callerPackageName,
+ @NonNull ListenableFuture<List<DBCustomAudience>> buyerCustomAudience);
+
@Nullable
private DBAdSelection handleTimeoutError(TimeoutException e) {
LogUtil.e(e, "Ad Selection exceeded time limit");
@@ -491,136 +470,6 @@ public final class AdSelectionRunner {
});
}
- private ListenableFuture<List<AdBiddingOutcome>> runAdBidding(
- @NonNull final List<DBCustomAudience> customAudiences,
- @NonNull final AdSelectionConfig adSelectionConfig)
- throws InterruptedException, ExecutionException {
- if (customAudiences.isEmpty()) {
- LogUtil.w("Cannot invoke bidding on empty list of CAs");
- return Futures.immediateFailedFuture(new Throwable("No CAs found for selection"));
- }
-
- // TODO(b/237004875) : Use common thread pool for parallel execution if possible
- ForkJoinPool customThreadPool = new ForkJoinPool(getParallelBiddingCount());
- final AtomicReference<List<ListenableFuture<AdBiddingOutcome>>> bidWinningAds =
- new AtomicReference<>();
-
- try {
- LogUtil.d("Triggering bidding for all %d custom audiences", customAudiences.size());
- customThreadPool
- .submit(
- () -> {
- LogUtil.v("Invoking bidding for #%d CAs", customAudiences.size());
- bidWinningAds.set(
- customAudiences.parallelStream()
- .map(
- customAudience -> {
- return runAdBiddingPerCA(
- customAudience,
- adSelectionConfig);
- })
- .collect(Collectors.toList()));
- })
- .get();
- } catch (InterruptedException e) {
- final String exceptionReason = "Bidding Interrupted Exception";
- LogUtil.e(e, exceptionReason);
- throw new InterruptedException(exceptionReason);
- } catch (ExecutionException e) {
- final String exceptionReason = "Bidding Execution Exception";
- LogUtil.e(e, exceptionReason);
- throw new ExecutionException(e.getCause());
- } finally {
- customThreadPool.shutdownNow();
- }
- return Futures.successfulAsList(bidWinningAds.get());
- }
-
- private int getParallelBiddingCount() {
- int parallelBiddingCountConfigValue = mFlags.getAdSelectionConcurrentBiddingCount();
- int numberOfAvailableProcessors = Runtime.getRuntime().availableProcessors();
- return Math.min(parallelBiddingCountConfigValue, numberOfAvailableProcessors);
- }
-
- private ListenableFuture<AdBiddingOutcome> runAdBiddingPerCA(
- @NonNull final DBCustomAudience customAudience,
- @NonNull final AdSelectionConfig adSelectionConfig) {
- LogUtil.v(String.format("Invoking bidding for CA: %s", customAudience.getName()));
-
- // TODO(b/233239475) : Validate Buyer signals in Ad Selection Config
- AdSelectionSignals buyerSignal =
- Optional.ofNullable(
- adSelectionConfig
- .getPerBuyerSignals()
- .get(customAudience.getBuyer()))
- .orElse(AdSelectionSignals.EMPTY);
- return mAdBidGenerator.runAdBiddingPerCA(
- customAudience,
- adSelectionConfig.getAdSelectionSignals(),
- buyerSignal,
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
- // TODO(b/230569187): get the contextualSignal securely = "invoking app name"
- }
-
- @SuppressLint("DefaultLocale")
- private ListenableFuture<List<AdScoringOutcome>> runAdScoring(
- @NonNull final List<AdBiddingOutcome> adBiddingOutcomes,
- @NonNull final AdSelectionConfig adSelectionConfig)
- throws AdServicesException {
- LogUtil.v("Got %d bidding outcomes", adBiddingOutcomes.size());
- List<AdBiddingOutcome> validBiddingOutcomes =
- adBiddingOutcomes.stream().filter(Objects::nonNull).collect(Collectors.toList());
-
- if (validBiddingOutcomes.isEmpty()) {
- LogUtil.w("Received empty list of Bidding outcomes");
- throw new IllegalStateException(ERROR_NO_VALID_BIDS_FOR_SCORING);
- }
- return mAdsScoreGenerator.runAdScoring(validBiddingOutcomes, adSelectionConfig);
- }
-
- private AdScoringOutcome getWinningOutcome(
- @NonNull List<AdScoringOutcome> overallAdScoringOutcome) {
- LogUtil.v("Scoring completed, generating winning outcome");
- return overallAdScoringOutcome.stream()
- .filter(a -> a.getAdWithScore().getScore() > 0)
- .max(
- (a, b) ->
- Double.compare(
- a.getAdWithScore().getScore(),
- b.getAdWithScore().getScore()))
- .orElseThrow(() -> new IllegalStateException(ERROR_NO_WINNING_AD_FOUND));
- }
-
- /**
- * This method populates an Ad Selection result ready to be persisted in DB, with all the fields
- * except adSelectionId and creation time, which should be created as close as possible to
- * persistence logic
- *
- * @param scoringWinner Winning Ad for overall Ad Selection
- * @return A {@link Pair} with a Builder for {@link DBAdSelection} populated with necessary data
- * and a string containing the JS with the decision logic from this buyer.
- */
- @VisibleForTesting
- Pair<DBAdSelection.Builder, String> createAdSelectionResult(
- @NonNull AdScoringOutcome scoringWinner) {
- DBAdSelection.Builder dbAdSelectionBuilder = new DBAdSelection.Builder();
- LogUtil.v("Creating Ad Selection result from scoring winner");
- dbAdSelectionBuilder
- .setWinningAdBid(scoringWinner.getAdWithScore().getAdWithBid().getBid())
- .setCustomAudienceSignals(
- scoringWinner.getCustomAudienceBiddingInfo().getCustomAudienceSignals())
- .setWinningAdRenderUri(
- scoringWinner.getAdWithScore().getAdWithBid().getAdData().getRenderUri())
- .setBiddingLogicUri(
- scoringWinner.getCustomAudienceBiddingInfo().getBiddingLogicUri())
- .setContextualSignals("{}");
- // TODO(b/230569187): get the contextualSignal securely = "invoking app name"
- return Pair.create(
- dbAdSelectionBuilder,
- scoringWinner.getCustomAudienceBiddingInfo().getBuyerDecisionLogicJs());
- }
-
private ListenableFuture<DBAdSelection> persistAdSelection(
@NonNull DBAdSelection.Builder dbAdSelectionBuilder,
@NonNull String buyerDecisionLogicJS,
@@ -654,7 +503,7 @@ public final class AdSelectionRunner {
* user consent
*/
private Void assertCallerHasUserConsent() throws ConsentManager.RevokedConsentException {
- if (!mConsentManager.getConsent(mContext.getPackageManager()).isGiven()) {
+ if (!mConsentManager.getConsent().isGiven()) {
throw new ConsentManager.RevokedConsentException();
}
return null;
@@ -793,8 +642,14 @@ public final class AdSelectionRunner {
return null;
}
- private void clearExpiredAdSelectionData() {
- Instant expirationTime = mClock.instant().minusSeconds(DAY_IN_SECONDS);
- mAdSelectionEntryDao.removeExpiredAdSelection(expirationTime);
+ static class AdSelectionOrchestrationResult {
+ DBAdSelection.Builder mDbAdSelectionBuilder;
+ String mBuyerDecisionLogicJs;
+
+ AdSelectionOrchestrationResult(
+ DBAdSelection.Builder dbAdSelectionBuilder, String buyerDecisionLogicJs) {
+ this.mDbAdSelectionBuilder = dbAdSelectionBuilder;
+ this.mBuyerDecisionLogicJs = buyerDecisionLogicJs;
+ }
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionScriptEngine.java b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionScriptEngine.java
index 9644c540c..6e8f38f6d 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionScriptEngine.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionScriptEngine.java
@@ -73,8 +73,10 @@ public class AdSelectionScriptEngine {
public static final String PER_BUYER_SIGNALS_ARG_NAME = "__rb_per_buyer_signals";
public static final String TRUSTED_BIDDING_SIGNALS_ARG_NAME = "__rb_trusted_bidding_signals";
public static final String CONTEXTUAL_SIGNALS_ARG_NAME = "__rb_contextual_signals";
- public static final String USER_SIGNALS_ARG_NAME = "__rb_user_signals";
- public static final String CUSTOM_AUDIENCE_SIGNALS_ARG_NAME = "__rb_custom_audience_signals";
+ public static final String CUSTOM_AUDIENCE_BIDDING_SIGNALS_ARG_NAME =
+ "__rb_custom_audience_bidding_signals";
+ public static final String CUSTOM_AUDIENCE_SCORING_SIGNALS_ARG_NAME =
+ "__rb_custom_audience_scoring_signals";
public static final String AUCTION_CONFIG_ARG_NAME = "__rb_auction_config";
public static final String SELLER_SIGNALS_ARG_NAME = "__rb_seller_signals";
public static final String TRUSTED_SCORING_SIGNALS_ARG_NAME = "__rb_trusted_scoring_signals";
@@ -147,7 +149,6 @@ public class AdSelectionScriptEngine {
@NonNull AdSelectionSignals perBuyerSignals,
@NonNull AdSelectionSignals trustedBiddingSignals,
@NonNull AdSelectionSignals contextualSignals,
- @NonNull AdSelectionSignals userSignals,
@NonNull CustomAudienceSignals customAudienceSignals)
throws JSONException {
Objects.requireNonNull(generateBidJS);
@@ -156,7 +157,6 @@ public class AdSelectionScriptEngine {
Objects.requireNonNull(perBuyerSignals);
Objects.requireNonNull(trustedBiddingSignals);
Objects.requireNonNull(contextualSignals);
- Objects.requireNonNull(userSignals);
Objects.requireNonNull(customAudienceSignals);
ImmutableList<JSScriptArgument> signals =
@@ -168,10 +168,10 @@ public class AdSelectionScriptEngine {
TRUSTED_BIDDING_SIGNALS_ARG_NAME,
trustedBiddingSignals.toString()))
.add(jsonArg(CONTEXTUAL_SIGNALS_ARG_NAME, contextualSignals.toString()))
- .add(jsonArg(USER_SIGNALS_ARG_NAME, userSignals.toString()))
.add(
CustomAudienceBiddingSignalsArgument.asScriptArgument(
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME, customAudienceSignals))
+ CUSTOM_AUDIENCE_BIDDING_SIGNALS_ARG_NAME,
+ customAudienceSignals))
.build();
ImmutableList.Builder<JSScriptArgument> adDataArguments = new ImmutableList.Builder<>();
@@ -221,7 +221,7 @@ public class AdSelectionScriptEngine {
.add(jsonArg(CONTEXTUAL_SIGNALS_ARG_NAME, contextualSignals.toString()))
.add(
CustomAudienceScoringSignalsArgument.asScriptArgument(
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME,
+ CUSTOM_AUDIENCE_SCORING_SIGNALS_ARG_NAME,
customAudienceSignalsList))
.build();
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionServiceImpl.java b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionServiceImpl.java
index 85070de40..dadd927f8 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionServiceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionServiceImpl.java
@@ -30,6 +30,7 @@ import android.adservices.adselection.ReportImpressionCallback;
import android.adservices.adselection.ReportImpressionInput;
import android.adservices.common.AdSelectionSignals;
import android.adservices.common.AdServicesStatusUtils;
+import android.adservices.common.CallerMetadata;
import android.annotation.NonNull;
import android.content.Context;
@@ -56,10 +57,13 @@ import com.android.adservices.service.js.JSScriptEngine;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.stats.AdServicesStatsLog;
+import com.android.adservices.service.stats.ApiServiceLatencyCalculator;
+import com.android.adservices.service.stats.Clock;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* Implementation of {@link AdSelectionService}.
@@ -73,6 +77,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
@NonNull private final AdServicesHttpsClient mAdServicesHttpsClient;
@NonNull private final ExecutorService mLightweightExecutor;
@NonNull private final ExecutorService mBackgroundExecutor;
+ @NonNull private final ScheduledThreadPoolExecutor mScheduledExecutor;
@NonNull private final Context mContext;
@NonNull private final ConsentManager mConsentManager;
@NonNull private final DevContextFilter mDevContextFilter;
@@ -96,6 +101,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
@NonNull AppImportanceFilter appImportanceFilter,
@NonNull ExecutorService lightweightExecutorService,
@NonNull ExecutorService backgroundExecutorService,
+ @NonNull ScheduledThreadPoolExecutor scheduledExecutor,
@NonNull Context context,
ConsentManager consentManager,
@NonNull AdServicesLogger adServicesLogger,
@@ -111,6 +117,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
Objects.requireNonNull(appImportanceFilter);
Objects.requireNonNull(lightweightExecutorService);
Objects.requireNonNull(backgroundExecutorService);
+ Objects.requireNonNull(scheduledExecutor);
Objects.requireNonNull(consentManager);
Objects.requireNonNull(adServicesLogger);
Objects.requireNonNull(flags);
@@ -124,6 +131,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
mAppImportanceFilter = appImportanceFilter;
mLightweightExecutor = lightweightExecutorService;
mBackgroundExecutor = backgroundExecutorService;
+ mScheduledExecutor = scheduledExecutor;
mContext = context;
mConsentManager = consentManager;
mAdServicesLogger = adServicesLogger;
@@ -151,6 +159,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
() -> FlagsFactory.getFlags().getForegroundStatuslLevelForValidation()),
AdServicesExecutors.getLightWeightExecutor(),
AdServicesExecutors.getBackgroundExecutor(),
+ AdServicesExecutors.getScheduler(),
context,
ConsentManager.getInstance(context),
AdServicesLoggerImpl.getInstance(),
@@ -164,43 +173,107 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
// TODO(b/233116758): Validate all the fields inside the adSelectionConfig.
@Override
public void runAdSelection(
- @NonNull AdSelectionInput inputParams, @NonNull AdSelectionCallback callback) {
+ @NonNull AdSelectionInput inputParams,
+ @NonNull CallerMetadata callerMetadata,
+ @NonNull AdSelectionCallback callback) {
+ final ApiServiceLatencyCalculator apiServiceLatencyCalculator =
+ new ApiServiceLatencyCalculator(callerMetadata, Clock.SYSTEM_CLOCK);
int apiName = AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS;
// Caller permissions must be checked in the binder thread, before anything else
mFledgeAuthorizationFilter.assertAppDeclaredPermission(mContext, apiName);
-
try {
Objects.requireNonNull(inputParams);
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
+ int overallLatencyMs = apiServiceLatencyCalculator.getApiServiceOverallLatencyMs();
+ LogUtil.v(
+ "The runAdSelection() arguments should not be null, failed with overall"
+ + "latency %d in ms.",
+ overallLatencyMs);
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, overallLatencyMs);
// Rethrow because we want to fail fast
throw exception;
}
DevContext devContext = mDevContextFilter.createDevContext();
+ int callerUid = getCallingUid(apiName);
+ mLightweightExecutor.execute(
+ () -> {
+ // TODO(b/249298855): Evolve off device ad selection logic.
+ if (mFlags.getAdSelectionOffDeviceEnabled()) {
+ runOffDeviceAdSelection(
+ devContext,
+ callerUid,
+ inputParams,
+ callback,
+ apiServiceLatencyCalculator);
+ } else {
+ runOnDeviceAdSelection(
+ devContext,
+ callerUid,
+ inputParams,
+ callback,
+ apiServiceLatencyCalculator);
+ }
+ });
+ }
- AdSelectionRunner adSelectionRunner =
- new AdSelectionRunner(
+ private void runOnDeviceAdSelection(
+ DevContext devContext,
+ int callerUid,
+ @NonNull AdSelectionInput inputParams,
+ @NonNull AdSelectionCallback callback,
+ @NonNull ApiServiceLatencyCalculator apiServiceLatencyCalculator) {
+ OnDeviceAdSelectionRunner runner =
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutor,
mBackgroundExecutor,
+ mScheduledExecutor,
mConsentManager,
mAdServicesLogger,
devContext,
mAppImportanceFilter,
mFlags,
() -> Throttler.getInstance(mFlags.getSdkRequestPermitsPerSecond()),
- getCallingUid(apiName),
+ callerUid,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ apiServiceLatencyCalculator);
+ runner.runAdSelection(inputParams, callback);
+ }
- adSelectionRunner.runAdSelection(inputParams, callback);
+ private void runOffDeviceAdSelection(
+ DevContext devContext,
+ int callerUid,
+ @NonNull AdSelectionInput inputParams,
+ @NonNull AdSelectionCallback callback,
+ @NonNull ApiServiceLatencyCalculator apiServiceLatencyCalculator) {
+ TrustedServerAdSelectionRunner runner =
+ new TrustedServerAdSelectionRunner(
+ mContext,
+ mCustomAudienceDao,
+ mAdSelectionEntryDao,
+ mAdServicesHttpsClient,
+ mLightweightExecutor,
+ mBackgroundExecutor,
+ mScheduledExecutor,
+ mConsentManager,
+ mAdServicesLogger,
+ devContext,
+ mAppImportanceFilter,
+ mFlags,
+ () -> Throttler.getInstance(mFlags.getSdkRequestPermitsPerSecond()),
+ callerUid,
+ mFledgeAuthorizationFilter,
+ mFledgeAllowListsFilter,
+ apiServiceLatencyCalculator);
+ runner.runAdSelection(inputParams, callback);
}
@Override
@@ -217,7 +290,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow because we want to fail fast
throw exception;
}
@@ -229,6 +302,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
mContext,
mLightweightExecutor,
mBackgroundExecutor,
+ mScheduledExecutor,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mConsentManager,
@@ -260,7 +334,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow because we want to fail fast
throw exception;
}
@@ -269,7 +343,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
if (!devContext.getDevOptionsEnabled()) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
+ apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR, 0);
throw new SecurityException(API_NOT_AUTHORIZED_MSG);
}
@@ -294,7 +368,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
return mCallingAppUidSupplier.getCallingAppUid();
} catch (IllegalStateException illegalStateException) {
mAdServicesLogger.logFledgeApiCallStats(
- apiNameLoggingId, AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
+ apiNameLoggingId, AdServicesStatusUtils.STATUS_INTERNAL_ERROR, 0);
throw illegalStateException;
}
}
@@ -315,7 +389,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow because we want to fail fast
throw exception;
}
@@ -324,7 +398,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
if (!devContext.getDevOptionsEnabled()) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
+ apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR, 0);
throw new SecurityException(API_NOT_AUTHORIZED_MSG);
}
@@ -358,7 +432,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow because we want to fail fast
throw exception;
}
@@ -367,7 +441,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
if (!devContext.getDevOptionsEnabled()) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
+ apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR, 0);
throw new SecurityException(API_NOT_AUTHORIZED_MSG);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/AdsScoreGeneratorImpl.java b/adservices/service-core/java/com/android/adservices/service/adselection/AdsScoreGeneratorImpl.java
index 7a8101f2b..33fdf528e 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/AdsScoreGeneratorImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/AdsScoreGeneratorImpl.java
@@ -69,6 +69,7 @@ public class AdsScoreGeneratorImpl implements AdsScoreGenerator {
@NonNull private final AdSelectionScriptEngine mAdSelectionScriptEngine;
@NonNull private final ListeningExecutorService mLightweightExecutorService;
@NonNull private final ListeningExecutorService mBackgroundExecutorService;
+ @NonNull private final ScheduledThreadPoolExecutor mScheduledExecutor;
@NonNull private final AdServicesHttpsClient mAdServicesHttpsClient;
@NonNull private final AdSelectionDevOverridesHelper mAdSelectionDevOverridesHelper;
@NonNull private final Flags mFlags;
@@ -77,6 +78,7 @@ public class AdsScoreGeneratorImpl implements AdsScoreGenerator {
@NonNull AdSelectionScriptEngine adSelectionScriptEngine,
@NonNull ListeningExecutorService lightweightExecutor,
@NonNull ListeningExecutorService backgroundExecutor,
+ @NonNull ScheduledThreadPoolExecutor scheduledExecutor,
@NonNull AdServicesHttpsClient adServicesHttpsClient,
@NonNull DevContext devContext,
@NonNull AdSelectionEntryDao adSelectionEntryDao,
@@ -84,6 +86,7 @@ public class AdsScoreGeneratorImpl implements AdsScoreGenerator {
Objects.requireNonNull(adSelectionScriptEngine);
Objects.requireNonNull(lightweightExecutor);
Objects.requireNonNull(backgroundExecutor);
+ Objects.requireNonNull(scheduledExecutor);
Objects.requireNonNull(adServicesHttpsClient);
Objects.requireNonNull(devContext);
Objects.requireNonNull(adSelectionEntryDao);
@@ -93,6 +96,7 @@ public class AdsScoreGeneratorImpl implements AdsScoreGenerator {
mAdServicesHttpsClient = adServicesHttpsClient;
mLightweightExecutorService = lightweightExecutor;
mBackgroundExecutorService = backgroundExecutor;
+ mScheduledExecutor = scheduledExecutor;
mAdSelectionDevOverridesHelper =
new AdSelectionDevOverridesHelper(devContext, adSelectionEntryDao);
mFlags = flags;
@@ -133,9 +137,7 @@ public class AdsScoreGeneratorImpl implements AdsScoreGenerator {
.withTimeout(
mFlags.getAdSelectionScoringTimeoutMs(),
TimeUnit.MILLISECONDS,
- // TODO(b/237103033): Comply with thread usage policy for AdServices;
- // use a global scheduled executor
- new ScheduledThreadPoolExecutor(1))
+ mScheduledExecutor)
.catching(
TimeoutException.class,
this::handleTimeoutError,
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/CustomAudienceReportingSignalsArgument.java b/adservices/service-core/java/com/android/adservices/service/adselection/CustomAudienceReportingSignalsArgument.java
new file mode 100644
index 000000000..e2a97527e
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/CustomAudienceReportingSignalsArgument.java
@@ -0,0 +1,49 @@
+/*
+ * 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.adselection;
+
+import static com.android.adservices.service.js.JSScriptArgument.recordArg;
+import static com.android.adservices.service.js.JSScriptArgument.stringArg;
+
+import com.android.adservices.data.adselection.CustomAudienceSignals;
+import com.android.adservices.service.js.JSScriptArgument;
+
+import org.json.JSONException;
+
+/**
+ * A utility class to convert instances of {@link CustomAudienceSignals} into {@link
+ * JSScriptArgument}. It strips out extraneous information from {@link CustomAudienceSignals} and
+ * only passes the data relevant for reporting.
+ */
+public class CustomAudienceReportingSignalsArgument {
+
+ // TODO: (b/228094391): Put these common constants in a separate class
+ public static final String NAME_FIELD_NAME = "name";
+
+ // No instance of this class is supposed to be created
+ private CustomAudienceReportingSignalsArgument() {}
+
+ /**
+ * @return A {@link JSScriptArgument} with the given {@code name} to represent this instance of
+ * {@link CustomAudienceReportingSignalsArgument}
+ * @throws JSONException if any of the signals in this class is not valid JSON.
+ */
+ public static JSScriptArgument asScriptArgument(
+ String name, CustomAudienceSignals customAudienceSignals) throws JSONException {
+ return recordArg(name, stringArg(NAME_FIELD_NAME, customAudienceSignals.getName()));
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/ImpressionReporter.java b/adservices/service-core/java/com/android/adservices/service/adselection/ImpressionReporter.java
index 2e6bc6fcc..25801096c 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/ImpressionReporter.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/ImpressionReporter.java
@@ -39,11 +39,13 @@ import com.android.adservices.data.adselection.CustomAudienceSignals;
import com.android.adservices.data.adselection.DBAdSelectionEntry;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AdServicesHttpsClient;
+import com.android.adservices.service.common.AdTechUriValidator;
import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException;
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.ValidatorUtil;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.devapi.AdSelectionDevOverridesHelper;
import com.android.adservices.service.devapi.DevContext;
@@ -74,6 +76,8 @@ public class ImpressionReporter {
public static final String CALLER_PACKAGE_NAME_MISMATCH =
"Caller package name does not match name used in ad selection";
+ private static final String REPORTING_URI_FIELD_NAME = "reporting URI";
+
@VisibleForTesting
static final String REPORT_IMPRESSION_THROTTLED = "Report impression exceeded rate limit";
@@ -82,6 +86,7 @@ public class ImpressionReporter {
@NonNull private final AdServicesHttpsClient mAdServicesHttpsClient;
@NonNull private final ListeningExecutorService mLightweightExecutorService;
@NonNull private final ListeningExecutorService mBackgroundExecutorService;
+ @NonNull private final ScheduledThreadPoolExecutor mScheduledExecutor;
@NonNull private final ReportImpressionScriptEngine mJsEngine;
@NonNull private final ConsentManager mConsentManager;
@NonNull private final AdSelectionDevOverridesHelper mAdSelectionDevOverridesHelper;
@@ -97,6 +102,7 @@ public class ImpressionReporter {
@NonNull Context context,
@NonNull ExecutorService lightweightExecutor,
@NonNull ExecutorService backgroundExecutor,
+ @NonNull ScheduledThreadPoolExecutor scheduledExecutor,
@NonNull AdSelectionEntryDao adSelectionEntryDao,
@NonNull AdServicesHttpsClient adServicesHttpsClient,
@NonNull ConsentManager consentManager,
@@ -111,6 +117,7 @@ public class ImpressionReporter {
Objects.requireNonNull(context);
Objects.requireNonNull(lightweightExecutor);
Objects.requireNonNull(backgroundExecutor);
+ Objects.requireNonNull(scheduledExecutor);
Objects.requireNonNull(adSelectionEntryDao);
Objects.requireNonNull(adServicesHttpsClient);
Objects.requireNonNull(consentManager);
@@ -125,6 +132,7 @@ public class ImpressionReporter {
mContext = context;
mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutor);
mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutor);
+ mScheduledExecutor = scheduledExecutor;
mAdSelectionEntryDao = adSelectionEntryDao;
mAdServicesHttpsClient = adServicesHttpsClient;
mJsEngine =
@@ -161,7 +169,7 @@ public class ImpressionReporter {
throw e.rethrowFromSystemServer();
} finally {
mAdServicesLogger.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, resultCode);
+ AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, resultCode, 0);
}
}
@@ -177,7 +185,7 @@ public class ImpressionReporter {
// TODO(b/233681870): Investigate implementation of actual failures in
// logs/metrics
mAdServicesLogger.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, resultCode);
+ AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, resultCode, 0);
}
}
@@ -239,15 +247,24 @@ public class ImpressionReporter {
requestParams.getCallerPackageName()),
mLightweightExecutorService)
.transform(
- reportingUris -> notifySuccessToCaller(callback, reportingUris),
+ reportingUrisAndContext ->
+ notifySuccessToCaller(
+ callback,
+ reportingUrisAndContext.first,
+ reportingUrisAndContext.second),
mLightweightExecutorService)
.withTimeout(
mFlags.getReportImpressionOverallTimeoutMs(),
TimeUnit.MILLISECONDS,
// TODO(b/237103033): Comply with thread usage policy for AdServices;
// use a global scheduled executor
- new ScheduledThreadPoolExecutor(1))
- .transformAsync(this::doReport, mLightweightExecutorService)
+ mScheduledExecutor)
+ .transformAsync(
+ reportingUrisAndContext ->
+ doReport(
+ reportingUrisAndContext.first,
+ reportingUrisAndContext.second),
+ mLightweightExecutorService)
.addCallback(
new FutureCallback<List<Void>>() {
@Override
@@ -270,10 +287,12 @@ public class ImpressionReporter {
mLightweightExecutorService);
}
- private ReportingUris notifySuccessToCaller(
- @NonNull ReportImpressionCallback callback, @NonNull ReportingUris reportingUris) {
+ private Pair<ReportingUris, ReportingContext> notifySuccessToCaller(
+ @NonNull ReportImpressionCallback callback,
+ @NonNull ReportingUris reportingUris,
+ @NonNull ReportingContext ctx) {
invokeSuccess(callback, AdServicesStatusUtils.STATUS_SUCCESS);
- return reportingUris;
+ return Pair.create(reportingUris, ctx);
}
private void notifyFailureToCaller(
@@ -297,22 +316,58 @@ public class ImpressionReporter {
}
@NonNull
- private ListenableFuture<List<Void>> doReport(ReportingUris reportingUris) {
+ private ListenableFuture<List<Void>> doReport(
+ ReportingUris reportingUris, ReportingContext ctx) {
LogUtil.v("Reporting URIs");
- ListenableFuture<Void> sellerFuture =
- mAdServicesHttpsClient.reportUri(reportingUris.sellerReportingUri);
+
+ ListenableFuture<Void> sellerFuture;
+
+ // Validate seller uri before reporting
+ AdTechUriValidator sellerValidator =
+ new AdTechUriValidator(
+ ValidatorUtil.AD_TECH_ROLE_SELLER,
+ ctx.mAdSelectionConfig.getSeller().toString(),
+ this.getClass().getSimpleName(),
+ REPORTING_URI_FIELD_NAME);
+ try {
+ sellerValidator.validate(reportingUris.sellerReportingUri);
+ // Perform reporting if no exception was thrown
+ sellerFuture = mAdServicesHttpsClient.reportUri(reportingUris.sellerReportingUri);
+ } catch (IllegalArgumentException e) {
+ LogUtil.v("Seller reporting URI validation failed!");
+ sellerFuture = Futures.immediateFuture(null);
+ }
+
ListenableFuture<Void> buyerFuture;
+ // Validate buyer uri if it exists
if (!Objects.isNull(reportingUris.buyerReportingUri)) {
- buyerFuture = mAdServicesHttpsClient.reportUri(reportingUris.buyerReportingUri);
+ CustomAudienceSignals customAudienceSignals =
+ Objects.requireNonNull(ctx.mDBAdSelectionEntry.getCustomAudienceSignals());
+
+ AdTechUriValidator buyerValidator =
+ new AdTechUriValidator(
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ customAudienceSignals.getBuyer().toString(),
+ this.getClass().getSimpleName(),
+ REPORTING_URI_FIELD_NAME);
+ try {
+ buyerValidator.validate(reportingUris.buyerReportingUri);
+ // Perform reporting if no exception was thrown
+ buyerFuture = mAdServicesHttpsClient.reportUri(reportingUris.buyerReportingUri);
+ } catch (IllegalArgumentException e) {
+ LogUtil.v("Buyer reporting URI validation failed!");
+ buyerFuture = Futures.immediateFuture(null);
+ }
} else {
+ // In case of contextual ad
buyerFuture = Futures.immediateFuture(null);
}
return Futures.allAsList(sellerFuture, buyerFuture);
}
- private FluentFuture<ReportingUris> computeReportingUris(
+ private FluentFuture<Pair<ReportingUris, ReportingContext>> computeReportingUris(
long adSelectionId, AdSelectionConfig adSelectionConfig, String callerPackageName) {
return fetchAdSelectionEntry(adSelectionId, callerPackageName)
.transformAsync(
@@ -332,8 +387,7 @@ public class ImpressionReporter {
sellerResultAndCtx ->
invokeBuyerScript(
sellerResultAndCtx.first, sellerResultAndCtx.second),
- mLightweightExecutorService)
- .transform(urisAndContext -> urisAndContext.first, mLightweightExecutorService);
+ mLightweightExecutorService);
}
private FluentFuture<DBAdSelectionEntry> fetchAdSelectionEntry(
@@ -457,7 +511,7 @@ public class ImpressionReporter {
* user consent
*/
private Void assertCallerHasUserConsent() throws ConsentManager.RevokedConsentException {
- if (!mConsentManager.getConsent(mContext.getPackageManager()).isGiven()) {
+ if (!mConsentManager.getConsent().isGiven()) {
throw new ConsentManager.RevokedConsentException();
}
return null;
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/JsFetcher.java b/adservices/service-core/java/com/android/adservices/service/adselection/JsFetcher.java
new file mode 100644
index 000000000..4efd7f778
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/JsFetcher.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.adselection;
+
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.NonNull;
+import android.net.Uri;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.service.common.AdServicesHttpsClient;
+import com.android.adservices.service.devapi.CustomAudienceDevOverridesHelper;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+/** Class to fetch JavaScript code both on and off device. */
+public class JsFetcher {
+ @VisibleForTesting
+ static final String MISSING_BIDDING_LOGIC = "Error fetching bidding js logic";
+
+ private final ListeningExecutorService mBackgroundExecutorService;
+ private final ListeningExecutorService mLightweightExecutorService;
+ private final CustomAudienceDevOverridesHelper mCustomAudienceDevOverridesHelper;
+ private final AdServicesHttpsClient mAdServicesHttpsClient;
+
+ public JsFetcher(
+ @NonNull ListeningExecutorService backgroundExecutorService,
+ @NonNull ListeningExecutorService lightweightExecutorService,
+ @NonNull CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper,
+ @NonNull AdServicesHttpsClient adServicesHttpsClient) {
+ mBackgroundExecutorService = backgroundExecutorService;
+ mCustomAudienceDevOverridesHelper = customAudienceDevOverridesHelper;
+ mAdServicesHttpsClient = adServicesHttpsClient;
+ mLightweightExecutorService = lightweightExecutorService;
+ }
+
+ /**
+ * Fetch the buyer decision logic. Check locally to see if an override is present, otherwise
+ * fetch from server.
+ *
+ * @return buyer decision logic
+ */
+ public FluentFuture<String> getBuyerDecisionLogic(
+ @NonNull final Uri decisionLogicUri,
+ @NonNull String owner,
+ @NonNull AdTechIdentifier buyer,
+ @NonNull String name) {
+ FluentFuture<String> jsOverrideFuture =
+ FluentFuture.from(
+ mBackgroundExecutorService.submit(
+ () ->
+ mCustomAudienceDevOverridesHelper.getBiddingLogicOverride(
+ owner, buyer, name)));
+ return jsOverrideFuture
+ .transformAsync(
+ jsOverride -> {
+ if (jsOverride == null) {
+ LogUtil.v(
+ "Fetching buyer decision logic from server: %s",
+ decisionLogicUri.toString());
+ return mAdServicesHttpsClient.fetchPayload(decisionLogicUri);
+ } else {
+ LogUtil.d(
+ "Developer options enabled and an override JS is provided "
+ + "for the current Custom Audience. "
+ + "Skipping call to server.");
+ return Futures.immediateFuture(jsOverride);
+ }
+ },
+ mLightweightExecutorService)
+ .catching(
+ Exception.class,
+ e -> {
+ LogUtil.w(
+ e, "Exception encountered when fetching buyer decision logic");
+ throw new IllegalStateException(MISSING_BIDDING_LOGIC);
+ },
+ mLightweightExecutorService);
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/OnDeviceAdSelectionRunner.java b/adservices/service-core/java/com/android/adservices/service/adselection/OnDeviceAdSelectionRunner.java
new file mode 100644
index 000000000..ba4fd5c57
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/OnDeviceAdSelectionRunner.java
@@ -0,0 +1,331 @@
+/*
+ * 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.adselection;
+
+import android.adservices.adselection.AdSelectionConfig;
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.exceptions.AdServicesException;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.Pair;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.adselection.AdSelectionEntryDao;
+import com.android.adservices.data.adselection.DBAdSelection;
+import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.common.AdServicesHttpsClient;
+import com.android.adservices.service.common.AppImportanceFilter;
+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.consent.ConsentManager;
+import com.android.adservices.service.devapi.DevContext;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.ApiServiceLatencyCalculator;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/** Orchestrate on-device ad selection. */
+public class OnDeviceAdSelectionRunner extends AdSelectionRunner {
+ @NonNull protected final AdsScoreGenerator mAdsScoreGenerator;
+ @NonNull protected final AdServicesHttpsClient mAdServicesHttpsClient;
+ @NonNull protected final AdBidGenerator mAdBidGenerator;
+
+ public OnDeviceAdSelectionRunner(
+ @NonNull final Context context,
+ @NonNull final CustomAudienceDao customAudienceDao,
+ @NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final AdServicesHttpsClient adServicesHttpsClient,
+ @NonNull final ExecutorService lightweightExecutorService,
+ @NonNull final ExecutorService backgroundExecutorService,
+ @NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
+ @NonNull final ConsentManager consentManager,
+ @NonNull final AdServicesLogger adServicesLogger,
+ @NonNull final DevContext devContext,
+ @NonNull AppImportanceFilter appImportanceFilter,
+ @NonNull final Flags flags,
+ @NonNull final Supplier<Throttler> throttlerSupplier,
+ int callerUid,
+ @NonNull final FledgeAuthorizationFilter fledgeAuthorizationFilter,
+ @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter,
+ @NonNull final ApiServiceLatencyCalculator apiServiceLatencyCalculator) {
+ super(
+ context,
+ customAudienceDao,
+ adSelectionEntryDao,
+ lightweightExecutorService,
+ backgroundExecutorService,
+ scheduledExecutor,
+ consentManager,
+ adServicesLogger,
+ appImportanceFilter,
+ flags,
+ throttlerSupplier,
+ callerUid,
+ fledgeAuthorizationFilter,
+ fledgeAllowListsFilter,
+ apiServiceLatencyCalculator);
+
+ Objects.requireNonNull(adServicesHttpsClient);
+
+ mAdServicesHttpsClient = adServicesHttpsClient;
+ mAdBidGenerator =
+ new AdBidGeneratorImpl(
+ context,
+ mAdServicesHttpsClient,
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ devContext,
+ mCustomAudienceDao,
+ flags);
+ mAdsScoreGenerator =
+ new AdsScoreGeneratorImpl(
+ new AdSelectionScriptEngine(
+ mContext,
+ () -> flags.getEnforceIsolateMaxHeapSize(),
+ () -> flags.getIsolateMaxHeapSizeBytes()),
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ mAdServicesHttpsClient,
+ devContext,
+ mAdSelectionEntryDao,
+ flags);
+ }
+
+ @VisibleForTesting
+ OnDeviceAdSelectionRunner(
+ @NonNull final Context context,
+ @NonNull final CustomAudienceDao customAudienceDao,
+ @NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final AdServicesHttpsClient adServicesHttpsClient,
+ @NonNull final ExecutorService lightweightExecutorService,
+ @NonNull final ExecutorService backgroundExecutorService,
+ @NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
+ @NonNull final ConsentManager consentManager,
+ @NonNull final AdsScoreGenerator adsScoreGenerator,
+ @NonNull final AdBidGenerator adBidGenerator,
+ @NonNull final AdSelectionIdGenerator adSelectionIdGenerator,
+ @NonNull Clock clock,
+ @NonNull final AdServicesLogger adServicesLogger,
+ @NonNull AppImportanceFilter appImportanceFilter,
+ @NonNull final Flags flags,
+ @NonNull final Supplier<Throttler> throttlerSupplier,
+ int callerUid,
+ @NonNull final FledgeAuthorizationFilter fledgeAuthorizationFilter,
+ @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter,
+ @NonNull final ApiServiceLatencyCalculator apiServiceLatencyCalculator) {
+ super(
+ context,
+ customAudienceDao,
+ adSelectionEntryDao,
+ lightweightExecutorService,
+ backgroundExecutorService,
+ scheduledExecutor,
+ consentManager,
+ adSelectionIdGenerator,
+ clock,
+ adServicesLogger,
+ appImportanceFilter,
+ flags,
+ throttlerSupplier,
+ callerUid,
+ fledgeAuthorizationFilter,
+ fledgeAllowListsFilter,
+ apiServiceLatencyCalculator);
+
+ Objects.requireNonNull(adsScoreGenerator);
+ Objects.requireNonNull(adServicesHttpsClient);
+ Objects.requireNonNull(adBidGenerator);
+
+ mAdsScoreGenerator = adsScoreGenerator;
+ mAdServicesHttpsClient = adServicesHttpsClient;
+ mAdBidGenerator = adBidGenerator;
+ }
+
+ /**
+ * Orchestrate on device ad selection.
+ *
+ * @param adSelectionConfig Set of data from Sellers and Buyers needed for Ad Auction and
+ * Selection
+ */
+ public ListenableFuture<AdSelectionOrchestrationResult> orchestrateAdSelection(
+ @NonNull final AdSelectionConfig adSelectionConfig,
+ @NonNull final String callerPackageName,
+ ListenableFuture<List<DBCustomAudience>> buyerCustomAudience) {
+ AsyncFunction<List<DBCustomAudience>, List<AdBiddingOutcome>> bidAds =
+ buyerCAs -> {
+ return runAdBidding(buyerCAs, adSelectionConfig);
+ };
+
+ ListenableFuture<List<AdBiddingOutcome>> biddingOutcome =
+ Futures.transformAsync(buyerCustomAudience, bidAds, mLightweightExecutorService);
+
+ AsyncFunction<List<AdBiddingOutcome>, List<AdScoringOutcome>> mapBidsToScores =
+ bids -> {
+ return runAdScoring(bids, adSelectionConfig);
+ };
+
+ ListenableFuture<List<AdScoringOutcome>> scoredAds =
+ Futures.transformAsync(
+ biddingOutcome, mapBidsToScores, mLightweightExecutorService);
+
+ Function<List<AdScoringOutcome>, AdScoringOutcome> reduceScoresToWinner =
+ scores -> {
+ return getWinningOutcome(scores);
+ };
+
+ ListenableFuture<AdScoringOutcome> winningOutcome =
+ Futures.transform(scoredAds, reduceScoresToWinner, mLightweightExecutorService);
+
+ Function<AdScoringOutcome, AdSelectionOrchestrationResult> mapWinnerToDBResult =
+ scoringWinner -> {
+ return createAdSelectionResult(scoringWinner);
+ };
+
+ ListenableFuture<AdSelectionOrchestrationResult> dbAdSelectionBuilder =
+ Futures.transform(winningOutcome, mapWinnerToDBResult, mLightweightExecutorService);
+
+ return dbAdSelectionBuilder;
+ }
+
+ private ListenableFuture<List<AdBiddingOutcome>> runAdBidding(
+ @NonNull final List<DBCustomAudience> customAudiences,
+ @NonNull final AdSelectionConfig adSelectionConfig) {
+ if (customAudiences.isEmpty()) {
+ LogUtil.w("Cannot invoke bidding on empty list of CAs");
+ return Futures.immediateFailedFuture(new Throwable("No CAs found for selection"));
+ }
+
+ Map<AdTechIdentifier, List<DBCustomAudience>> buyerToCustomAudienceMap =
+ mapBuyerToCustomAudience(customAudiences);
+ PerBuyerBiddingRunner buyerBidRunner =
+ new PerBuyerBiddingRunner(
+ mAdBidGenerator, mScheduledExecutor, mBackgroundExecutorService);
+
+ LogUtil.v("Invoking bidding for #%d buyers", buyerToCustomAudienceMap.size());
+ return Futures.successfulAsList(
+ buyerToCustomAudienceMap.entrySet().parallelStream()
+ .map(
+ entry -> {
+ return buyerBidRunner.runBidding(
+ entry.getKey(),
+ entry.getValue(),
+ mFlags.getAdSelectionBiddingTimeoutPerBuyerMs(),
+ adSelectionConfig);
+ })
+ .flatMap(List::stream)
+ .collect(Collectors.toList()));
+ }
+
+ @SuppressLint("DefaultLocale")
+ private ListenableFuture<List<AdScoringOutcome>> runAdScoring(
+ @NonNull final List<AdBiddingOutcome> adBiddingOutcomes,
+ @NonNull final AdSelectionConfig adSelectionConfig)
+ throws AdServicesException {
+ LogUtil.v("Got %d total bidding outcomes", adBiddingOutcomes.size());
+ List<AdBiddingOutcome> validBiddingOutcomes =
+ adBiddingOutcomes.stream().filter(Objects::nonNull).collect(Collectors.toList());
+ LogUtil.v("Got %d valid bidding outcomes", validBiddingOutcomes.size());
+
+ if (validBiddingOutcomes.isEmpty()) {
+ LogUtil.w("Received empty list of successful Bidding outcomes");
+ throw new IllegalStateException(ERROR_NO_VALID_BIDS_FOR_SCORING);
+ }
+ return mAdsScoreGenerator.runAdScoring(validBiddingOutcomes, adSelectionConfig);
+ }
+
+ private AdScoringOutcome getWinningOutcome(
+ @NonNull List<AdScoringOutcome> overallAdScoringOutcome) {
+ LogUtil.v("Scoring completed, generating winning outcome");
+ return overallAdScoringOutcome.stream()
+ .filter(a -> a.getAdWithScore().getScore() > 0)
+ .max(
+ (a, b) ->
+ Double.compare(
+ a.getAdWithScore().getScore(),
+ b.getAdWithScore().getScore()))
+ .orElseThrow(() -> new IllegalStateException(ERROR_NO_WINNING_AD_FOUND));
+ }
+
+ /**
+ * This method populates an Ad Selection result ready to be persisted in DB, with all the fields
+ * except adSelectionId and creation time, which should be created as close as possible to
+ * persistence logic
+ *
+ * @param scoringWinner Winning Ad for overall Ad Selection
+ * @return A {@link Pair} with a Builder for {@link DBAdSelection} populated with necessary data
+ * and a string containing the JS with the decision logic from this buyer.
+ */
+ @VisibleForTesting
+ AdSelectionOrchestrationResult createAdSelectionResult(
+ @NonNull AdScoringOutcome scoringWinner) {
+ DBAdSelection.Builder dbAdSelectionBuilder = new DBAdSelection.Builder();
+ LogUtil.v("Creating Ad Selection result from scoring winner");
+ dbAdSelectionBuilder
+ .setWinningAdBid(scoringWinner.getAdWithScore().getAdWithBid().getBid())
+ .setCustomAudienceSignals(
+ scoringWinner.getCustomAudienceBiddingInfo().getCustomAudienceSignals())
+ .setWinningAdRenderUri(
+ scoringWinner.getAdWithScore().getAdWithBid().getAdData().getRenderUri())
+ .setBiddingLogicUri(
+ scoringWinner.getCustomAudienceBiddingInfo().getBiddingLogicUri())
+ .setContextualSignals("{}");
+ // TODO(b/230569187): get the contextualSignal securely = "invoking app name"
+ return new AdSelectionOrchestrationResult(
+ dbAdSelectionBuilder,
+ scoringWinner.getCustomAudienceBiddingInfo().getBuyerDecisionLogicJs());
+ }
+
+ private Map<AdTechIdentifier, List<DBCustomAudience>> mapBuyerToCustomAudience(
+ final List<DBCustomAudience> customAudienceList) {
+ Map<AdTechIdentifier, List<DBCustomAudience>> buyerToCustomAudienceMap = new HashMap<>();
+
+ for (DBCustomAudience customAudience : customAudienceList) {
+ buyerToCustomAudienceMap
+ .computeIfAbsent(customAudience.getBuyer(), k -> new ArrayList<>())
+ .add(customAudience);
+ }
+ LogUtil.v("Created mapping for #%d buyers", buyerToCustomAudienceMap.size());
+ return buyerToCustomAudienceMap;
+ }
+
+ private int getParallelBiddingCount() {
+ int parallelBiddingCountConfigValue = mFlags.getAdSelectionConcurrentBiddingCount();
+ int numberOfAvailableProcessors = Runtime.getRuntime().availableProcessors();
+ return Math.min(parallelBiddingCountConfigValue, numberOfAvailableProcessors);
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/PerBuyerBiddingRunner.java b/adservices/service-core/java/com/android/adservices/service/adselection/PerBuyerBiddingRunner.java
new file mode 100644
index 000000000..606a64817
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/PerBuyerBiddingRunner.java
@@ -0,0 +1,139 @@
+/*
+ * 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.adselection;
+
+import android.adservices.adselection.AdSelectionConfig;
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.annotation.NonNull;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.customaudience.DBCustomAudience;
+
+import com.google.common.util.concurrent.ExecutionSequencer;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * Runs bidding for a buyer and its associated Custom Audience. The bidding for every buyer is time
+ * capped, where the incomplete CAs are dropped from bidding when timed out while preserving the
+ * ones that were already completed
+ */
+public class PerBuyerBiddingRunner {
+ @NonNull private AdBidGenerator mAdBidGenerator;
+ @NonNull private ScheduledThreadPoolExecutor mScheduledExecutor;
+ @NonNull private ListeningExecutorService mBackgroundExecutorService;
+
+ public PerBuyerBiddingRunner(
+ @NonNull AdBidGenerator adBidGenerator,
+ @NonNull ScheduledThreadPoolExecutor scheduledExecutor,
+ @NonNull ListeningExecutorService backgroundExecutorService) {
+ mAdBidGenerator = adBidGenerator;
+ mScheduledExecutor = scheduledExecutor;
+ mBackgroundExecutorService = backgroundExecutorService;
+ }
+ /**
+ * This method executes bidding sequentially on the list of CustomAudience for a buyer. By
+ * leveraging the sequential executor, the bidding for subsequent Custom Audience is not even
+ * started until the previous bidding completes. This leads to significant saving of resources
+ * as without sequence, all the CAs begin bidding async and start downloading JS and consuming
+ * other resources. This ensures that at any point, only one bidding would be in progress.
+ *
+ * @param buyerTimeoutMs timeout value, post which incomplete CA bids are cancelled
+ * @param adSelectionConfig for the current Ad Selection
+ * @return list of futures with bidding outcomes
+ */
+ public List<ListenableFuture<AdBiddingOutcome>> runBidding(
+ final AdTechIdentifier buyer,
+ final List<DBCustomAudience> customAudienceList,
+ final long buyerTimeoutMs,
+ final AdSelectionConfig adSelectionConfig) {
+ LogUtil.v(
+ "Running bid for #%d Custom Audiences for buyer: %s",
+ customAudienceList.size(), buyer);
+
+ /*
+ * We require a unique sequencer per buyer, as using a global sequencer enforces sequence
+ * across buyers, where a buyer can starve other buyers' CAs from bidding.
+ */
+ ExecutionSequencer sequencer = ExecutionSequencer.create();
+ List<ListenableFuture<AdBiddingOutcome>> buyerBiddingOutcomes =
+ customAudienceList.stream()
+ .map(
+ (customAudience) ->
+ sequencer.submitAsync(
+ () ->
+ runBiddingPerCA(
+ customAudience, adSelectionConfig),
+ mBackgroundExecutorService))
+ .collect(Collectors.toList());
+
+ eventuallyTimeoutIncompleteTasks(buyerTimeoutMs, buyerBiddingOutcomes);
+ return buyerBiddingOutcomes;
+ }
+
+ private ListenableFuture<AdBiddingOutcome> runBiddingPerCA(
+ @NonNull final DBCustomAudience customAudience,
+ @NonNull final AdSelectionConfig adSelectionConfig) {
+ LogUtil.v(String.format("Invoking bidding for CA: %s", customAudience.getName()));
+
+ // TODO(b/233239475) : Validate Buyer signals in Ad Selection Config
+ AdSelectionSignals buyerSignal =
+ Optional.ofNullable(
+ adSelectionConfig
+ .getPerBuyerSignals()
+ .get(customAudience.getBuyer()))
+ .orElse(AdSelectionSignals.EMPTY);
+ return mAdBidGenerator.runAdBiddingPerCA(
+ customAudience,
+ adSelectionConfig.getAdSelectionSignals(),
+ buyerSignal,
+ AdSelectionSignals.EMPTY);
+ }
+
+ /**
+ * Instead of timing out entire list of future, we only cancel the ones which are not done. This
+ * helps preserve tasks that are already completed while freeing up resources from the tasks
+ * which maybe in progress or are yet to be scheduled by cancelling them.
+ *
+ * @param timeoutMs delay after which these tasks should be cancelled
+ * @param runningTasks potentially ongoing tasks, that need to be timed-out
+ */
+ private <T> void eventuallyTimeoutIncompleteTasks(
+ final long timeoutMs, List<ListenableFuture<T>> runningTasks) {
+ Runnable cancelOngoingTasks =
+ () -> {
+ int incompleteTaskCount = 0;
+ for (ListenableFuture<T> runningTask : runningTasks) {
+ // TODO(b/254176437): use Closing futures to free up resources
+ if (runningTask.cancel(true)) {
+ incompleteTaskCount++;
+ }
+ }
+ LogUtil.v(
+ "Total tasks: #%d, cancelled incomplete tasks: #%d",
+ runningTasks.size(), incompleteTaskCount);
+ };
+ mScheduledExecutor.schedule(cancelOngoingTasks, timeoutMs, TimeUnit.MILLISECONDS);
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/ReportImpressionScriptEngine.java b/adservices/service-core/java/com/android/adservices/service/adselection/ReportImpressionScriptEngine.java
index 057ab7494..60d50bd68 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/ReportImpressionScriptEngine.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/ReportImpressionScriptEngine.java
@@ -67,7 +67,8 @@ public class ReportImpressionScriptEngine {
public static final String PER_BUYER_SIGNALS_ARG_NAME = "per_buyer_signals";
public static final String SIGNALS_FOR_BUYER_ARG_NAME = "signals_for_buyer";
public static final String CONTEXTUAL_SIGNALS_ARG_NAME = "contextual_signals";
- public static final String CUSTOM_AUDIENCE_SIGNALS_ARG_NAME = "custom_audience_signals";
+ public static final String CUSTOM_AUDIENCE_REPORTING_SIGNALS_ARG_NAME =
+ "custom_audience_reporting_signals";
public static final String AD_SELECTION_CONFIG_ARG_NAME = "ad_selection_config";
public static final String BID_ARG_NAME = "bid";
public static final String RENDER_URI_ARG_NAME = "render_uri";
@@ -171,8 +172,9 @@ public class ReportImpressionScriptEngine {
.add(jsonArg(SIGNALS_FOR_BUYER_ARG_NAME, signalsForBuyer.toString()))
.add(jsonArg(CONTEXTUAL_SIGNALS_ARG_NAME, contextualSignals.toString()))
.add(
- CustomAudienceBiddingSignalsArgument.asScriptArgument(
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME, customAudienceSignals))
+ CustomAudienceReportingSignalsArgument.asScriptArgument(
+ CUSTOM_AUDIENCE_REPORTING_SIGNALS_ARG_NAME,
+ customAudienceSignals))
.build();
return transform(
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/TrustedServerAdSelectionRunner.java b/adservices/service-core/java/com/android/adservices/service/adselection/TrustedServerAdSelectionRunner.java
new file mode 100644
index 000000000..d67a80e10
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/TrustedServerAdSelectionRunner.java
@@ -0,0 +1,405 @@
+/*
+ * 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.adselection;
+
+import android.adservices.adselection.AdSelectionConfig;
+import android.adservices.common.AdSelectionSignals;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Pair;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.adselection.AdSelectionEntryDao;
+import com.android.adservices.data.adselection.CustomAudienceSignals;
+import com.android.adservices.data.adselection.DBAdSelection;
+import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.common.AdServicesHttpsClient;
+import com.android.adservices.service.common.AppImportanceFilter;
+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.consent.ConsentManager;
+import com.android.adservices.service.devapi.CustomAudienceDevOverridesHelper;
+import com.android.adservices.service.devapi.DevContext;
+import com.android.adservices.service.proto.SellerFrontEndGrpc;
+import com.android.adservices.service.proto.SellerFrontendService.BuyerInput;
+import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdRequest;
+import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdRequest.SelectWinningAdRawRequest.ClientType;
+import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdResponse;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.ApiServiceLatencyCalculator;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.UncheckedTimeoutException;
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.time.Clock;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import io.grpc.Codec;
+import io.grpc.ManagedChannel;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+
+/**
+ * Offload execution to Bidding & Auction services. Sends an umbrella request to the Seller Frontend
+ * Service.
+ */
+public class TrustedServerAdSelectionRunner extends AdSelectionRunner {
+ public static final String GZIP = new Codec.Gzip().getMessageEncoding(); // "gzip"
+
+ @NonNull private final JsFetcher mJsFetcher;
+
+ public TrustedServerAdSelectionRunner(
+ @NonNull final Context context,
+ @NonNull final CustomAudienceDao customAudienceDao,
+ @NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final AdServicesHttpsClient adServicesHttpsClient,
+ @NonNull final ExecutorService lightweightExecutorService,
+ @NonNull final ExecutorService backgroundExecutorService,
+ @NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
+ @NonNull final ConsentManager consentManager,
+ @NonNull final AdServicesLogger adServicesLogger,
+ @NonNull final DevContext devContext,
+ @NonNull AppImportanceFilter appImportanceFilter,
+ @NonNull final Flags flags,
+ @NonNull final Supplier<Throttler> throttlerSupplier,
+ int callerUid,
+ @NonNull final FledgeAuthorizationFilter fledgeAuthorizationFilter,
+ @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter,
+ @NonNull final ApiServiceLatencyCalculator apiServiceLatencyCalculator) {
+ super(
+ context,
+ customAudienceDao,
+ adSelectionEntryDao,
+ lightweightExecutorService,
+ backgroundExecutorService,
+ scheduledExecutor,
+ consentManager,
+ adServicesLogger,
+ appImportanceFilter,
+ flags,
+ throttlerSupplier,
+ callerUid,
+ fledgeAuthorizationFilter,
+ fledgeAllowListsFilter,
+ apiServiceLatencyCalculator);
+
+ CustomAudienceDevOverridesHelper mCustomAudienceDevOverridesHelper =
+ new CustomAudienceDevOverridesHelper(devContext, customAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ mCustomAudienceDevOverridesHelper,
+ adServicesHttpsClient);
+ }
+
+ @VisibleForTesting
+ TrustedServerAdSelectionRunner(
+ @NonNull final Context context,
+ @NonNull final CustomAudienceDao customAudienceDao,
+ @NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final ExecutorService lightweightExecutorService,
+ @NonNull final ExecutorService backgroundExecutorService,
+ @NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
+ @NonNull final ConsentManager consentManager,
+ @NonNull final AdSelectionIdGenerator adSelectionIdGenerator,
+ @NonNull Clock clock,
+ @NonNull final AdServicesLogger adServicesLogger,
+ @NonNull AppImportanceFilter appImportanceFilter,
+ @NonNull final Flags flags,
+ @NonNull final Supplier<Throttler> throttlerSupplier,
+ int callerUid,
+ @NonNull final FledgeAuthorizationFilter fledgeAuthorizationFilter,
+ @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter,
+ @NonNull final JsFetcher jsFetcher,
+ @NonNull final ApiServiceLatencyCalculator apiServiceLatencyCalculator) {
+ super(
+ context,
+ customAudienceDao,
+ adSelectionEntryDao,
+ lightweightExecutorService,
+ backgroundExecutorService,
+ scheduledExecutor,
+ consentManager,
+ adSelectionIdGenerator,
+ clock,
+ adServicesLogger,
+ appImportanceFilter,
+ flags,
+ throttlerSupplier,
+ callerUid,
+ fledgeAuthorizationFilter,
+ fledgeAllowListsFilter,
+ apiServiceLatencyCalculator);
+
+ this.mJsFetcher = jsFetcher;
+ }
+
+ /** Prepares request and calls Seller Front-end Service to orchestrate ad selection. */
+ public ListenableFuture<AdSelectionOrchestrationResult> orchestrateAdSelection(
+ @NonNull final AdSelectionConfig adSelectionConfig,
+ @NonNull final String callerPackageName,
+ @NonNull ListenableFuture<List<DBCustomAudience>> buyersCustomAudiences) {
+
+ Function<List<DBCustomAudience>, Map<String, BuyerInput>> createBuyerInputs =
+ buyerCAs -> {
+ return createBuyerInputs(buyerCAs, adSelectionConfig);
+ };
+
+ Function<Map<String, BuyerInput>, SelectWinningAdRequest> createSelectWinningAdRequest =
+ encryptedInputPerBuyer -> {
+ return createSelectWinningAdRequest(adSelectionConfig, encryptedInputPerBuyer);
+ };
+
+ AsyncFunction<SelectWinningAdRequest, SelectWinningAdResponse> callSelectWinningAd =
+ req -> {
+ return callSelectWinningAd(req);
+ };
+
+ // Return the DBCustomAudience to fetch the buyerLogicJs in the next future.
+ Function<SelectWinningAdResponse, Pair<DBAdSelection.Builder, DBCustomAudience>>
+ getCustomAudienceAndDBAdSelection =
+ selectWinningAdResponse -> {
+ return getCustomAudienceAndDBAdSelection(
+ selectWinningAdResponse,
+ callerPackageName,
+ buyersCustomAudiences);
+ };
+
+ // TODO(b/254066067): Confirm if buyer logic for reporting can be fetched after rendering.
+ AsyncFunction<
+ Pair<DBAdSelection.Builder, DBCustomAudience>,
+ Pair<DBAdSelection.Builder, FluentFuture<String>>>
+ fetchBuyerLogicJs =
+ dbAdSelectionAndCAPair -> {
+ return fetchBuyerLogicJs(dbAdSelectionAndCAPair);
+ };
+
+ Function<Pair<DBAdSelection.Builder, FluentFuture<String>>, AdSelectionOrchestrationResult>
+ createAdSelectionResult =
+ dbAdSelectionAndBuyerLogicJsPair -> {
+ return createAdSelectionResult(dbAdSelectionAndBuyerLogicJsPair);
+ };
+
+ return FluentFuture.from(buyersCustomAudiences)
+ .transform(createBuyerInputs, mLightweightExecutorService)
+ .transform(createSelectWinningAdRequest, mLightweightExecutorService)
+ .transformAsync(callSelectWinningAd, mBackgroundExecutorService)
+ .transform(getCustomAudienceAndDBAdSelection, mLightweightExecutorService)
+ .transformAsync(fetchBuyerLogicJs, mBackgroundExecutorService)
+ .transform(createAdSelectionResult, mLightweightExecutorService)
+ .withTimeout(
+ mFlags.getAdSelectionOffDeviceOverallTimeoutMs(),
+ TimeUnit.MILLISECONDS,
+ mScheduledExecutor)
+ .catching(
+ TimeoutException.class,
+ this::handleTimeoutError,
+ mLightweightExecutorService);
+ }
+
+ private Map<String, BuyerInput> createBuyerInputs(
+ List<DBCustomAudience> buyerCAs, AdSelectionConfig adSelectionConfig) {
+ Map<String, BuyerInput> buyerInputs = new HashMap<>();
+ for (DBCustomAudience customAudience : buyerCAs) {
+ BuyerInput.CustomAudience.Builder customAudienceBuilder =
+ BuyerInput.CustomAudience.newBuilder()
+ .setName(customAudience.getName())
+ .addAllBiddingSignalsKeys(getBiddingSignalKeys(customAudience));
+
+ AdSelectionSignals perBuyerSignals =
+ adSelectionConfig.getPerBuyerSignals().get(customAudience.getBuyer());
+ BuyerInput input =
+ BuyerInput.newBuilder()
+ .addCustomAudiences(customAudienceBuilder)
+ .setBuyerSignals(convertSignalsToStruct(perBuyerSignals))
+ .build();
+ // TODO(b/254325545): Update the key to the domain of the BFE service, not buyer name.
+ buyerInputs.put(customAudience.getBuyer().toString(), input);
+ }
+
+ return buyerInputs;
+ }
+
+ private List<String> getBiddingSignalKeys(DBCustomAudience customAudience) {
+ List<String> biddingSignalKeys = customAudience.getTrustedBiddingData().getKeys();
+ // If the bidding signal keys is just the CA name, we don't need to pass it to the server.
+ if (biddingSignalKeys.size() == 1
+ && customAudience.getName().equals(biddingSignalKeys.get(0))) {
+ return ImmutableList.of();
+ }
+
+ // Remove the CA name from the bidding signal keys list to save space.
+ biddingSignalKeys.remove(customAudience.getName());
+ return biddingSignalKeys;
+ }
+
+ private SelectWinningAdRequest createSelectWinningAdRequest(
+ AdSelectionConfig adSelectionConfig, Map<String, BuyerInput> rawInputPerBuyer) {
+ SelectWinningAdRequest.SelectWinningAdRawRequest.AuctionConfig.Builder auctionConfig =
+ SelectWinningAdRequest.SelectWinningAdRawRequest.AuctionConfig.newBuilder()
+ .setSellerSignals(
+ convertSignalsToStruct((adSelectionConfig.getSellerSignals())))
+ // TODO(b/254068070): Check if this is contextually derived auction_signals.
+ .setAuctionSignals(
+ convertSignalsToStruct(adSelectionConfig.getAdSelectionSignals()));
+
+ SelectWinningAdRequest.SelectWinningAdRawRequest.Builder rawRequestBuilder =
+ SelectWinningAdRequest.SelectWinningAdRawRequest.newBuilder()
+ .setAdSelectionRequestId(mAdSelectionIdGenerator.generateId())
+ .putAllRawBuyerInput(rawInputPerBuyer)
+ .setAuctionConfig(auctionConfig)
+ // FLEDGE is currently only supported on GMS core devices.
+ .setClientType(ClientType.ANDROID);
+
+ return SelectWinningAdRequest.newBuilder().setRawRequest(rawRequestBuilder).build();
+ }
+
+ private ListenableFuture<SelectWinningAdResponse> callSelectWinningAd(
+ SelectWinningAdRequest req) {
+ // TODO(b/249575366): Pass in address + port when the fields are added.
+ ManagedChannel channel = OkHttpChannelBuilder.forAddress("localhost", 8080).build();
+ SellerFrontEndGrpc.SellerFrontEndFutureStub stub =
+ SellerFrontEndGrpc.newFutureStub(channel);
+
+ if (mFlags.getAdSelectionOffDeviceRequestCompressionEnabled()) {
+ stub = stub.withCompression(GZIP);
+ }
+
+ return stub.selectWinningAd(req);
+ }
+
+ private Pair<DBAdSelection.Builder, DBCustomAudience> getCustomAudienceAndDBAdSelection(
+ SelectWinningAdResponse selectWinningAdResponse,
+ String callerPackageName,
+ ListenableFuture<List<DBCustomAudience>> buyerCustomAudiences) {
+ SelectWinningAdResponse.SelectWinningAdRawResponse rawResponse =
+ selectWinningAdResponse.getRawResponse();
+ Uri winningAdRenderUri = Uri.parse(rawResponse.getAdRenderUrl());
+
+ // Find custom audience of the winning ad.
+ DBCustomAudience customAudience;
+ try {
+ // buyerCustomAudiences's future is already complete by the time this method is called.
+ List<DBCustomAudience> customAudiences = buyerCustomAudiences.get();
+ List<DBCustomAudience> filteredCustomAudiences =
+ customAudiences.stream()
+ .filter(
+ audience ->
+ audience.getName()
+ .equals(rawResponse.getCustomAudienceName()))
+ .collect(Collectors.toList());
+ customAudience = Iterables.getOnlyElement(filteredCustomAudiences);
+ } catch (InterruptedException | ExecutionException e) {
+ // Will never be thrown since the future has already completed for the code to be here.
+ throw new RuntimeException("Could not read buyerCustomAudiences list from device");
+ } catch (NoSuchElementException e) {
+ throw new IllegalStateException(
+ "Could not find corresponding custom audience returned from Bidding & Auction"
+ + " services");
+ }
+
+ CustomAudienceSignals customAudienceSignals =
+ CustomAudienceSignals.buildFromCustomAudience(customAudience);
+ DBAdSelection.Builder builder =
+ new DBAdSelection.Builder()
+ .setWinningAdBid(rawResponse.getBidPrice())
+ .setWinningAdRenderUri(winningAdRenderUri)
+ .setCustomAudienceSignals(customAudienceSignals)
+ .setBiddingLogicUri(customAudience.getBiddingLogicUri())
+ .setContextualSignals("{}")
+ .setCallerPackageName(callerPackageName);
+
+ return new Pair(builder, customAudience);
+ }
+
+ private ListenableFuture<Pair<DBAdSelection.Builder, FluentFuture<String>>> fetchBuyerLogicJs(
+ Pair<DBAdSelection.Builder, DBCustomAudience> dbAdSelectionAndCAPair) {
+ return mBackgroundExecutorService.submit(
+ () -> {
+ DBCustomAudience customAudience = dbAdSelectionAndCAPair.second;
+ FluentFuture<String> buyerDecisionLogic =
+ mJsFetcher.getBuyerDecisionLogic(
+ customAudience.getBiddingLogicUri(),
+ customAudience.getOwner(),
+ customAudience.getBuyer(),
+ customAudience.getName());
+ return new Pair(dbAdSelectionAndCAPair.first, buyerDecisionLogic);
+ });
+ }
+
+ private AdSelectionOrchestrationResult createAdSelectionResult(
+ Pair<DBAdSelection.Builder, FluentFuture<String>> dbAdSelectionAndBuyerLogicJsPair) {
+ try {
+ String buyerJsLogic = dbAdSelectionAndBuyerLogicJsPair.second.get();
+ return new AdSelectionOrchestrationResult(
+ dbAdSelectionAndBuyerLogicJsPair.first, buyerJsLogic);
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException("Could not fetch buyerJsLogic", e);
+ }
+ }
+
+ private Struct convertSignalsToStruct(AdSelectionSignals adSelectionSignals) {
+ Struct.Builder signals = Struct.newBuilder();
+ try {
+ JSONObject json = new JSONObject(adSelectionSignals.toString());
+ for (String keyStr : json.keySet()) {
+ Object obj = json.get(keyStr);
+ if (obj instanceof String) {
+ signals.putFields(
+ keyStr, Value.newBuilder().setStringValue((String) obj).build());
+ }
+ }
+ } catch (JSONException e) {
+ String error = "Invalid JSON found during SelectWinningAdRequest construction";
+ throw new IllegalArgumentException(error, e);
+ }
+
+ return signals.build();
+ }
+
+ @Nullable
+ private AdSelectionOrchestrationResult handleTimeoutError(TimeoutException e) {
+ LogUtil.e(e, "Ad Selection exceeded time limit");
+ throw new UncheckedTimeoutException(AD_SELECTION_TIMED_OUT);
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsetid/AppSetIdServiceImpl.java b/adservices/service-core/java/com/android/adservices/service/appsetid/AppSetIdServiceImpl.java
index cf368ae08..8221309f8 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsetid/AppSetIdServiceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsetid/AppSetIdServiceImpl.java
@@ -18,6 +18,7 @@ package com.android.adservices.service.appsetid;
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;
@@ -43,6 +44,7 @@ import com.android.adservices.service.common.AllowLists;
import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException;
import com.android.adservices.service.common.SdkRuntimeUtil;
+import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesStatsLog;
import com.android.adservices.service.stats.ApiCallStats;
@@ -62,6 +64,7 @@ public class AppSetIdServiceImpl extends IAppSetIdService.Stub {
private final AdServicesLogger mAdServicesLogger;
private final Clock mClock;
private final Flags mFlags;
+ private final Throttler mThrottler;
private final AppImportanceFilter mAppImportanceFilter;
public AppSetIdServiceImpl(
@@ -70,12 +73,14 @@ public class AppSetIdServiceImpl extends IAppSetIdService.Stub {
AdServicesLogger adServicesLogger,
Clock clock,
Flags flags,
+ Throttler throttler,
AppImportanceFilter appImportanceFilter) {
mContext = context;
mAppSetIdWorker = appsetidWorker;
mAdServicesLogger = adServicesLogger;
mClock = clock;
mFlags = flags;
+ mThrottler = throttler;
mAppImportanceFilter = appImportanceFilter;
}
@@ -85,6 +90,8 @@ public class AppSetIdServiceImpl extends IAppSetIdService.Stub {
@NonNull CallerMetadata callerMetadata,
@NonNull IGetAppSetIdCallback callback) {
+ if (isThrottled(appSetIdParam, callback)) return;
+
final long startServiceTime = mClock.elapsedRealtime();
final String packageName = appSetIdParam.getAppPackageName();
final String sdkPackageName = appSetIdParam.getSdkPackageName();
@@ -127,6 +134,26 @@ public class AppSetIdServiceImpl extends IAppSetIdService.Stub {
});
}
+ // Throttle the AppSetId API.
+ // Return true if we should throttle (don't allow the API call).
+ private boolean isThrottled(GetAppSetIdParam appSetIdParam, IGetAppSetIdCallback callback) {
+ boolean throttled =
+ !mThrottler.tryAcquire(
+ Throttler.ApiKey.APPSETID_API_APP_PACKAGE_NAME,
+ appSetIdParam.getAppPackageName());
+
+ if (throttled) {
+ LogUtil.e("Rate Limit Reached for APPSETID_API");
+ try {
+ callback.onError(STATUS_RATE_LIMIT_REACHED);
+ } catch (RemoteException e) {
+ LogUtil.e(e, "Fail to call the callback on Rate Limit Reached.");
+ }
+ return true;
+ }
+ return false;
+ }
+
// Enforce whether caller is from foreground.
private void enforceForeground(int callingUid) {
// If caller calls AppSetId API from Sandbox, regard it as foreground.
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AdServicesCommonServiceImpl.java b/adservices/service-core/java/com/android/adservices/service/common/AdServicesCommonServiceImpl.java
index cc729be1a..803f0a05f 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/AdServicesCommonServiceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/AdServicesCommonServiceImpl.java
@@ -125,9 +125,8 @@ public class AdServicesCommonServiceImpl extends
+ mFlags.getAdServicesEnabled());
if (mFlags.getAdServicesEnabled() && adServicesEntryPointEnabled) {
ConsentNotificationJobService.schedule(mContext, adIdEnabled);
- if (ConsentManager.getInstance(mContext)
- .getConsent(mContext.getPackageManager())
- .isGiven()) {
+ if (ConsentManager.getInstance(mContext).getConsent().isGiven()) {
+ PackageChangedReceiver.enableReceiver(mContext);
BackgroundJobsManager.scheduleAllBackgroundJobs(mContext);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AllowLists.java b/adservices/service-core/java/com/android/adservices/service/common/AllowLists.java
index d7e4a14d9..f62528e1b 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/AllowLists.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/AllowLists.java
@@ -31,6 +31,10 @@ import com.android.internal.annotations.VisibleForTesting;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
/** Utility class to handle AllowList for Apps and SDKs. */
public class AllowLists {
@@ -39,6 +43,25 @@ public class AllowLists {
private static final String SPLITTER = ",";
private static final String HASH_ALGORITHM = "SHA-256";
+ /** Returns whether all entities are allowlisted or not based on the given {@code allowList}. */
+ public static boolean doesAllowListAllowAll(@NonNull String allowList) {
+ Objects.requireNonNull(allowList);
+ return ALLOW_ALL.equals(allowList);
+ }
+
+ /** Splits the given {@code allowList} into the list of entities allowed. */
+ public static List<String> splitAllowList(@NonNull String allowList) {
+ Objects.requireNonNull(allowList);
+
+ if (allowList.trim().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return Arrays.stream(allowList.split(SPLITTER))
+ .map(String::trim)
+ .collect(Collectors.toList());
+ }
+
/**
* A utility to check if an app package exists in the provided allow-list. The allow-list to
* search is split by {@link #SPLITTER} without any white spaces. E.g. of a valid allow list -
@@ -54,7 +77,9 @@ public class AllowLists {
// TODO(b/237686242): Cache the AllowList so that we don't need to read from Flags and split
// on every API call.
- return Arrays.asList(allowList.split(SPLITTER)).contains(appPackageName);
+ return Arrays.stream(allowList.split(SPLITTER))
+ .map(String::trim)
+ .anyMatch(packageName -> packageName.equals(appPackageName));
}
/**
@@ -88,7 +113,9 @@ public class AllowLists {
// TODO(b/237686242): Cache the AllowList so that we don't need to read from Flags and split
// on every API call.
- return Arrays.asList(signatureAllowList.split(SPLITTER)).contains(hexSignature);
+ return Arrays.stream(signatureAllowList.split(SPLITTER))
+ .map(String::trim)
+ .anyMatch(signature -> signature.equals(hexSignature));
}
/**
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AppImportanceFilter.java b/adservices/service-core/java/com/android/adservices/service/common/AppImportanceFilter.java
index 025d92050..c737f2697 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/AppImportanceFilter.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/AppImportanceFilter.java
@@ -131,13 +131,8 @@ public class AppImportanceFilter {
@NonNull String appPackageName, int apiNameIdForLogging, @Nullable String sdkName)
throws WrongCallingApplicationStateException {
int importance = mActivityManager.getPackageImportance(appPackageName);
- LogUtil.v(
- "Package "
- + appPackageName
- + " has importance "
- + importance
- + " comparing with threshold of "
- + mImportanceThresholdSupplier.get());
+ LogUtil.v("Package %s has importance %d comparing with threshold of %d.",
+ appPackageName, importance, mImportanceThresholdSupplier.get());
if (importance > mImportanceThresholdSupplier.get()) {
LogUtil.v(
"Application importance failed for app %s with importance %d greater"
@@ -168,13 +163,8 @@ public class AppImportanceFilter {
int appUid, int apiNameLoggingId, @Nullable String sdkName)
throws WrongCallingApplicationStateException {
int importance = mActivityManager.getUidImportance(appUid);
- LogUtil.v(
- "Process "
- + appUid
- + "has importance "
- + importance
- + " comparing with threshold of "
- + mImportanceThresholdSupplier.get());
+ LogUtil.v("Process %d has importance %d comparing with threshold of %d.",
+ appUid, importance, mImportanceThresholdSupplier.get());
if (importance > mImportanceThresholdSupplier.get()) {
LogUtil.v(
"Application importance failed for app with UID %d "
diff --git a/adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java b/adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java
index 6679596d3..04e59071b 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java
@@ -24,7 +24,9 @@ import com.android.adservices.download.MddJobService;
import com.android.adservices.service.AdServicesConfig;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.MaintenanceJobService;
+import com.android.adservices.service.measurement.AsyncRegistrationQueueJobService;
import com.android.adservices.service.measurement.DeleteExpiredJobService;
+import com.android.adservices.service.measurement.DeleteUninstalledJobService;
import com.android.adservices.service.measurement.attribution.AttributionJobService;
import com.android.adservices.service.measurement.reporting.AggregateFallbackReportingJobService;
import com.android.adservices.service.measurement.reporting.AggregateReportingJobService;
@@ -42,9 +44,14 @@ public class BackgroundJobsManager {
* @param context application context.
*/
public static void scheduleAllBackgroundJobs(@NonNull Context context) {
+ // We will schedule MaintenanceJobService as long as either kill switch is off
+ if (!FlagsFactory.getFlags().getTopicsKillSwitch()
+ || !FlagsFactory.getFlags().getFledgeSelectAdsKillSwitch()) {
+ MaintenanceJobService.scheduleIfNeeded(context, false);
+ }
+
if (!FlagsFactory.getFlags().getTopicsKillSwitch()) {
EpochJobService.scheduleIfNeeded(context, false);
- MaintenanceJobService.scheduleIfNeeded(context, false);
}
if (!FlagsFactory.getFlags().getMddBackgroundTaskKillSwitch()) {
@@ -58,6 +65,8 @@ public class BackgroundJobsManager {
EventReportingJobService.scheduleIfNeeded(context, false);
EventFallbackReportingJobService.scheduleIfNeeded(context, false);
DeleteExpiredJobService.scheduleIfNeeded(context, false);
+ DeleteUninstalledJobService.scheduleIfNeeded(context, false);
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(context, false);
}
}
@@ -74,10 +83,12 @@ public class BackgroundJobsManager {
jobScheduler.cancel(AdServicesConfig.MEASUREMENT_EVENT_MAIN_REPORTING_JOB_ID);
jobScheduler.cancel(AdServicesConfig.MEASUREMENT_DELETE_EXPIRED_JOB_ID);
+ jobScheduler.cancel(AdServicesConfig.MEASUREMENT_DELETE_UNINSTALLED_JOB_ID);
jobScheduler.cancel(AdServicesConfig.MEASUREMENT_ATTRIBUTION_JOB_ID);
jobScheduler.cancel(AdServicesConfig.MEASUREMENT_EVENT_FALLBACK_REPORTING_JOB_ID);
jobScheduler.cancel(AdServicesConfig.MEASUREMENT_AGGREGATE_MAIN_REPORTING_JOB_ID);
jobScheduler.cancel(AdServicesConfig.MEASUREMENT_AGGREGATE_FALLBACK_REPORTING_JOB_ID);
+ jobScheduler.cancel(AdServicesConfig.ASYNC_REGISTRATION_QUEUE_JOB_ID);
jobScheduler.cancel(AdServicesConfig.FLEDGE_BACKGROUND_FETCH_JOB_ID);
diff --git a/adservices/service-core/java/com/android/adservices/service/common/ConsentNotificationJobService.java b/adservices/service-core/java/com/android/adservices/service/common/ConsentNotificationJobService.java
index 47b2ea73c..f5e51e042 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/ConsentNotificationJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/ConsentNotificationJobService.java
@@ -160,8 +160,7 @@ public class ConsentNotificationJobService extends JobService {
() -> {
try {
if (!FlagsFactory.getFlags().getConsentNotificationDebugMode()
- && mConsentManager.wasNotificationDisplayed(
- getPackageManager())) {
+ && mConsentManager.wasNotificationDisplayed()) {
LogUtil.i("already notified, return back");
return;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/FledgeAllowListsFilter.java b/adservices/service-core/java/com/android/adservices/service/common/FledgeAllowListsFilter.java
index 1553c0e31..c7461dd72 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/FledgeAllowListsFilter.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/FledgeAllowListsFilter.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.common;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
+
import android.adservices.common.AdServicesStatusUtils;
import android.annotation.NonNull;
@@ -55,8 +57,7 @@ public class FledgeAllowListsFilter {
LogUtil.v(
"App package name \"%s\" not authorized to call API %d",
appPackageName, apiNameLoggingId);
- mAdServicesLogger.logFledgeApiCallStats(
- apiNameLoggingId, AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED);
+ mAdServicesLogger.logFledgeApiCallStats(apiNameLoggingId, STATUS_CALLER_NOT_ALLOWED, 0);
throw new AppNotAllowedException();
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/FledgeAuthorizationFilter.java b/adservices/service-core/java/com/android/adservices/service/common/FledgeAuthorizationFilter.java
index 2f3bc92ff..dfa3ffd2f 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/FledgeAuthorizationFilter.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/FledgeAuthorizationFilter.java
@@ -16,6 +16,10 @@
package com.android.adservices.service.common;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_PERMISSION_NOT_REQUESTED;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
+
import android.adservices.common.AdServicesStatusUtils;
import android.adservices.common.AdTechIdentifier;
import android.annotation.NonNull;
@@ -24,6 +28,7 @@ import android.content.pm.PackageManager;
import com.android.adservices.LogUtil;
import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.service.PhFlags;
import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.internal.annotations.VisibleForTesting;
@@ -83,8 +88,7 @@ public class FledgeAuthorizationFilter {
}
LogUtil.v("No match found, failing calling package name match in API %d", apiNameLoggingId);
- mAdServicesLogger.logFledgeApiCallStats(
- apiNameLoggingId, AdServicesStatusUtils.STATUS_UNAUTHORIZED);
+ mAdServicesLogger.logFledgeApiCallStats(apiNameLoggingId, STATUS_UNAUTHORIZED, 0);
throw new CallerMismatchException();
}
@@ -101,7 +105,7 @@ public class FledgeAuthorizationFilter {
if (!PermissionHelper.hasCustomAudiencesPermission(context)) {
LogUtil.v("Permission not declared by caller in API %d", apiNameLoggingId);
mAdServicesLogger.logFledgeApiCallStats(
- apiNameLoggingId, AdServicesStatusUtils.STATUS_PERMISSION_NOT_REQUESTED);
+ apiNameLoggingId, STATUS_PERMISSION_NOT_REQUESTED, 0);
throw new SecurityException(
AdServicesStatusUtils
.SECURITY_EXCEPTION_PERMISSION_NOT_REQUESTED_ERROR_MESSAGE);
@@ -135,8 +139,7 @@ public class FledgeAuthorizationFilter {
LogUtil.v(
"Enrollment data match not found for ad tech \"%s\" while calling API %d",
adTechIdentifier.toString(), apiNameLoggingId);
- mAdServicesLogger.logFledgeApiCallStats(
- apiNameLoggingId, AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED);
+ mAdServicesLogger.logFledgeApiCallStats(apiNameLoggingId, STATUS_CALLER_NOT_ALLOWED, 0);
throw new AdTechNotAllowedException();
}
@@ -146,8 +149,17 @@ public class FledgeAuthorizationFilter {
"App package name \"%s\" with ad tech identifier \"%s\" not authorized to call"
+ " API %d",
appPackageName, adTechIdentifier.toString(), apiNameLoggingId);
- mAdServicesLogger.logFledgeApiCallStats(
- apiNameLoggingId, AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED);
+ mAdServicesLogger.logFledgeApiCallStats(apiNameLoggingId, STATUS_CALLER_NOT_ALLOWED, 0);
+ throw new AdTechNotAllowedException();
+ }
+
+ // Check if enrollment is in blocklist.
+ if (PhFlags.getInstance().isEnrollmentBlocklisted(enrollmentData.getEnrollmentId())) {
+ LogUtil.v(
+ "App package name \"%s\" with ad tech identifier \"%s\" not authorized to call"
+ + " API %d",
+ appPackageName, adTechIdentifier.toString(), apiNameLoggingId);
+ mAdServicesLogger.logFledgeApiCallStats(apiNameLoggingId, STATUS_CALLER_NOT_ALLOWED, 0);
throw new AdTechNotAllowedException();
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java b/adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java
new file mode 100644
index 000000000..6cfb60820
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java
@@ -0,0 +1,64 @@
+/*
+ * 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.content.Context;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.adselection.AdSelectionDatabase;
+import com.android.adservices.data.adselection.AdSelectionEntryDao;
+import com.android.adservices.service.FlagsFactory;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.time.Clock;
+import java.time.Instant;
+
+/** Utility class to perform Fledge maintenance tasks */
+public class FledgeMaintenanceTasksWorker {
+ @NonNull private AdSelectionEntryDao mAdSelectionEntryDao;
+
+ @VisibleForTesting
+ public FledgeMaintenanceTasksWorker(AdSelectionEntryDao adSelectionEntryDao) {
+ mAdSelectionEntryDao = adSelectionEntryDao;
+ }
+
+ private FledgeMaintenanceTasksWorker(Context context) {
+ mAdSelectionEntryDao = AdSelectionDatabase.getInstance(context).adSelectionEntryDao();
+ }
+
+ /** Creates a new instance of {@link FledgeMaintenanceTasksWorker}. */
+ public static FledgeMaintenanceTasksWorker create(@NonNull Context context) {
+ return new FledgeMaintenanceTasksWorker(context);
+ }
+
+ /**
+ * Clears all entries in the {@code ad_selection} table that are older than {@code
+ * expirationTime}. Then, clears all expired entries in the {@code buyer_decision_logic}.
+ */
+ public void clearExpiredAdSelectionData() {
+ Instant expirationTime =
+ Clock.systemUTC()
+ .instant()
+ .minusSeconds(FlagsFactory.getFlags().getAdSelectionExpirationWindowS());
+ LogUtil.v("Clearing expired Ad Selection data");
+ mAdSelectionEntryDao.removeExpiredAdSelection(expirationTime);
+
+ LogUtil.v("Clearing expired Buyer Decision Logic data ");
+ mAdSelectionEntryDao.removeExpiredBuyerDecisionLogic();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/PackageChangedReceiver.java b/adservices/service-core/java/com/android/adservices/service/common/PackageChangedReceiver.java
index 1a81df44f..2a8b0b8b1 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/PackageChangedReceiver.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/PackageChangedReceiver.java
@@ -16,10 +16,14 @@
package com.android.adservices.service.common;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import com.android.adservices.LogUtil;
@@ -61,6 +65,21 @@ public class PackageChangedReceiver extends BroadcastReceiver {
private static final Executor sBackgroundExecutor = AdServicesExecutors.getBackgroundExecutor();
+ /** Enable the PackageChangedReceiver */
+ public static boolean enableReceiver(@NonNull Context context) {
+ try {
+ context.getPackageManager()
+ .setComponentEnabledSetting(
+ new ComponentName(context, PackageChangedReceiver.class),
+ COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ } catch (IllegalArgumentException e) {
+ LogUtil.e("enableService failed for %s", context.getPackageName());
+ return false;
+ }
+ return true;
+ }
+
@Override
public void onReceive(Context context, Intent intent) {
LogUtil.d("PackageChangedReceiver received a broadcast: " + intent.getAction());
@@ -133,9 +152,10 @@ public class PackageChangedReceiver extends BroadcastReceiver {
return;
}
- LogUtil.d("Deleting topics data for package: " + packageUri.toString());
+ LogUtil.d(
+ "Handling App Uninstallation in Topics API for package: " + packageUri.toString());
sBackgroundExecutor.execute(
- () -> TopicsWorker.getInstance(context).deletePackageData(packageUri));
+ () -> TopicsWorker.getInstance(context).handleAppUninstallation(packageUri));
}
@VisibleForTesting
diff --git a/adservices/service-core/java/com/android/adservices/service/common/PermissionHelper.java b/adservices/service-core/java/com/android/adservices/service/common/PermissionHelper.java
index 8e2e43989..0bd6a9ff7 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/PermissionHelper.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/PermissionHelper.java
@@ -55,13 +55,18 @@ public final class PermissionHelper {
/** @return {@code true} if the caller has the permission to invoke AdID APIs. */
public static boolean hasAdIdPermission(
@NonNull Context context, boolean useSandboxCheck, @NonNull String sdkName) {
+
+ boolean callerPerm =
+ context.checkCallingOrSelfPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
+ == PackageManager.PERMISSION_GRANTED;
// Note: Checking permission declared by Sdk running in Sandbox is only for accounting
// purposes and should not be used as a security measure.
if (useSandboxCheck) {
- // TODO(b/240718367): Add check for SDK permission.
+ return callerPerm
+ && checkSdkPermission(
+ context, sdkName, AdServicesPermissions.ACCESS_ADSERVICES_AD_ID);
}
- return (context.checkCallingOrSelfPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
- == PackageManager.PERMISSION_GRANTED);
+ return callerPerm;
}
/** @return {@code true} if the caller has the permission to invoke Attribution APIs. */
diff --git a/adservices/service-core/java/com/android/adservices/service/common/Throttler.java b/adservices/service-core/java/com/android/adservices/service/common/Throttler.java
index 2ff08b26c..96cb3dded 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/Throttler.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/Throttler.java
@@ -62,7 +62,13 @@ public class Throttler {
FLEDGE_API_LEAVE_CUSTOM_AUDIENCE,
// Key to throttle Report impressions API
- FLEDGE_API_REPORT_IMPRESSIONS
+ FLEDGE_API_REPORT_IMPRESSIONS,
+
+ // Key to throttle AdId API, based on app package name.
+ ADID_API_APP_PACKAGE_NAME,
+
+ // Key to throttle AppSetId API, based on app package name.
+ APPSETID_API_APP_PACKAGE_NAME,
}
private static volatile Throttler sSingleton;
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java b/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
index b6c6258cf..c932e6c7a 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
@@ -19,20 +19,19 @@ package com.android.adservices.service.consent;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__OPT_IN_SELECTED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__OPT_OUT_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
import android.annotation.NonNull;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.content.pm.PackageManager;
-
-
import com.android.adservices.LogUtil;
import com.android.adservices.data.common.BooleanFileDatastore;
import com.android.adservices.data.consent.AppConsentDao;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.CustomAudienceDatabase;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.topics.Topic;
import com.android.adservices.data.topics.TopicsTables;
import com.android.adservices.service.Flags;
@@ -46,17 +45,17 @@ import com.android.adservices.service.topics.TopicsWorker;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
-
/**
* Manager to handle user's consent.
*
- * <p> For Beta the consent is given for all {@link AdServicesApiType} or for none. </p>
+ * <p>For Beta the consent is given for all {@link AdServicesApiType} or for none.
*/
public class ConsentManager {
private static final String ERROR_MESSAGE_DATASTORE_EXCEPTION_WHILE_GET_CONTENT =
@@ -76,9 +75,10 @@ public class ConsentManager {
private final TopicsWorker mTopicsWorker;
private final BooleanFileDatastore mDatastore;
private final AppConsentDao mAppConsentDao;
+ private final EnrollmentDao mEnrollmentDao;
private final MeasurementImpl mMeasurementImpl;
private final AdServicesLoggerImpl mAdServicesLoggerImpl;
- private int mDeviceLoggingRegion;
+ private final int mDeviceLoggingRegion;
private final CustomAudienceDao mCustomAudienceDao;
private final ExecutorService mExecutor;
@@ -86,6 +86,7 @@ public class ConsentManager {
@NonNull Context context,
@NonNull TopicsWorker topicsWorker,
@NonNull AppConsentDao appConsentDao,
+ @NonNull EnrollmentDao enrollmentDao,
@NonNull MeasurementImpl measurementImpl,
@NonNull AdServicesLoggerImpl adServicesLoggerImpl,
@NonNull CustomAudienceDao customAudienceDao,
@@ -100,11 +101,13 @@ public class ConsentManager {
mTopicsWorker = topicsWorker;
mDatastore = new BooleanFileDatastore(context, STORAGE_XML_IDENTIFIER, STORAGE_VERSION);
mAppConsentDao = appConsentDao;
+ mEnrollmentDao = enrollmentDao;
mMeasurementImpl = measurementImpl;
mAdServicesLoggerImpl = adServicesLoggerImpl;
mCustomAudienceDao = customAudienceDao;
mExecutor = Executors.newSingleThreadExecutor();
mFlags = flags;
+ mDeviceLoggingRegion = initializeLoggingValues(context);
}
/**
@@ -125,6 +128,7 @@ public class ConsentManager {
context,
TopicsWorker.getInstance(context),
AppConsentDao.getInstance(context),
+ EnrollmentDao.getInstance(context),
MeasurementImpl.getInstance(context),
AdServicesLoggerImpl.getInstance(),
CustomAudienceDatabase.getInstance(context).customAudienceDao(),
@@ -139,15 +143,18 @@ public class ConsentManager {
* Enables all PP API services. It gives consent to Topics, Fledge and Measurements services.
*/
public void enable(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
mAdServicesLoggerImpl.logUIStats(
new UIStats.Builder()
.setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
.setRegion(mDeviceLoggingRegion)
.setAction(AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__OPT_IN_SELECTED)
.build());
+
// Enable all the APIs
try {
- init(context.getPackageManager());
+ init();
BackgroundJobsManager.scheduleAllBackgroundJobs(context);
@@ -173,12 +180,13 @@ public class ConsentManager {
// Disable all the APIs
try {
- init(context.getPackageManager());
+ init();
// reset all data
resetTopicsAndBlockedTopics();
resetAppsAndBlockedApps();
resetMeasurement();
+ resetEnrollment();
BackgroundJobsManager.unscheduleAllBackgroundJobs(
context.getSystemService(JobScheduler.class));
@@ -191,12 +199,12 @@ public class ConsentManager {
}
/** Retrieves the consent for all PP API services. */
- public AdServicesApiConsent getConsent(@NonNull PackageManager packageManager) {
+ public AdServicesApiConsent getConsent() {
if (mFlags.getConsentManagerDebugMode()) {
return AdServicesApiConsent.GIVEN;
}
try {
- init(packageManager);
+ init();
return AdServicesApiConsent.getConsent(mDatastore.get(CONSENT_KEY));
} catch (NullPointerException | IllegalArgumentException | IOException e) {
LogUtil.e(e, ERROR_MESSAGE_DATASTORE_EXCEPTION_WHILE_GET_CONTENT);
@@ -250,12 +258,14 @@ public class ConsentManager {
/** Wipes out all the data gathered by Topics API but blocked topics. */
public void resetTopics() {
- mTopicsWorker.clearAllTopicsData(List.of(TopicsTables.BlockedTopicsContract.TABLE));
+ ArrayList<String> tablesToBlock = new ArrayList<>();
+ tablesToBlock.add(TopicsTables.BlockedTopicsContract.TABLE);
+ mTopicsWorker.clearAllTopicsData(tablesToBlock);
}
/** Wipes out all the data gathered by Topics API. */
public void resetTopicsAndBlockedTopics() {
- mTopicsWorker.clearAllTopicsData(List.of());
+ mTopicsWorker.clearAllTopicsData(new ArrayList<>());
}
/**
@@ -345,8 +355,6 @@ public class ConsentManager {
* <p>This method also checks whether a user has opted out of the FLEDGE Privacy Sandbox
* initiative.
*
- * @param packageManager the {@link PackageManager} used to check initial consent for the
- * Privacy Sandbox
* @param packageName String package name that uniquely identifies an installed application to
* check
* @return {@code true} if either the FLEDGE Privacy Sandbox initiative has been opted out or if
@@ -354,11 +362,10 @@ public class ConsentManager {
* @throws IllegalArgumentException if the package name is invalid or not found as an installed
* application
*/
- public boolean isFledgeConsentRevokedForApp(
- @NonNull PackageManager packageManager, @NonNull String packageName)
+ public boolean isFledgeConsentRevokedForApp(@NonNull String packageName)
throws IllegalArgumentException {
// TODO(b/238464639): Implement API-specific consent for FLEDGE
- if (!getConsent(packageManager).isGiven()) {
+ if (!getConsent().isGiven()) {
return true;
}
@@ -379,8 +386,6 @@ public class ConsentManager {
*
* <p>This is only meant to be called by the FLEDGE APIs.
*
- * @param packageManager the {@link PackageManager} used to check initial consent for the
- * Privacy Sandbox
* @param packageName String package name that uniquely identifies an installed application that
* has used a FLEDGE API
* @return {@code true} if user consent has been revoked for the application or API, {@code
@@ -388,11 +393,10 @@ public class ConsentManager {
* @throws IllegalArgumentException if the package name is invalid or not found as an installed
* application
*/
- public boolean isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- @NonNull PackageManager packageManager, @NonNull String packageName)
+ public boolean isFledgeConsentRevokedForAppAfterSettingFledgeUse(@NonNull String packageName)
throws IllegalArgumentException {
// TODO(b/238464639): Implement API-specific consent for FLEDGE
- if (!getConsent(packageManager).isGiven()) {
+ if (!getConsent().isGiven()) {
return true;
}
@@ -409,13 +413,18 @@ public class ConsentManager {
mMeasurementImpl.deleteAllMeasurementData(List.of());
}
+ /** Wipes out all the Enrollment data */
+ private void resetEnrollment() {
+ mEnrollmentDao.deleteAll();
+ }
+
/**
* Saves information to the storage that notification was displayed for the first time to the
* user.
*/
- public void recordNotificationDisplayed(@NonNull PackageManager packageManager) {
+ public void recordNotificationDisplayed() {
try {
- init(packageManager);
+ init();
// TODO(b/229725886): add metrics / logging
mDatastore.put(NOTIFICATION_DISPLAYED_ONCE, true);
} catch (IOException e) {
@@ -428,9 +437,9 @@ public class ConsentManager {
*
* @return true if Consent Notification was displayed, otherwise false.
*/
- public Boolean wasNotificationDisplayed(@NonNull PackageManager packageManager) {
+ public Boolean wasNotificationDisplayed() {
try {
- init(packageManager);
+ init();
return mDatastore.get(NOTIFICATION_DISPLAYED_ONCE);
} catch (IOException e) {
LogUtil.e(e, "Record notification failed due to IOException thrown by Datastore.");
@@ -438,14 +447,12 @@ public class ConsentManager {
}
}
- private void setConsent(AdServicesApiConsent state)
- throws IOException {
+ private void setConsent(AdServicesApiConsent state) throws IOException {
mDatastore.put(CONSENT_KEY, state.isGiven());
}
- void init(PackageManager packageManager) throws IOException {
+ void init() throws IOException {
initializeStorage();
- initializeLoggingValues(packageManager);
if (mDatastore.get(CONSENT_ALREADY_INITIALIZED_KEY) == null
|| mDatastore.get(CONSENT_KEY) == null) {
mDatastore.put(NOTIFICATION_DISPLAYED_ONCE, false);
@@ -464,14 +471,12 @@ public class ConsentManager {
}
}
- private void initializeLoggingValues(PackageManager packageManager) {
- // TODO: fix it after background job CLs are submitted
- // if (DeviceRegionProvider.isEuDevice(context)) {
- // mDeviceLoggingRegion = AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU;
- // } else {
- // mDeviceLoggingRegion = AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
- // }
- mDeviceLoggingRegion = AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
+ private int initializeLoggingValues(Context context) {
+ if (DeviceRegionProvider.isEuDevice(context)) {
+ return AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU;
+ } else {
+ return AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
+ }
}
/**
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchJobService.java b/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchJobService.java
index 6452f5d07..ab0078989 100644
--- a/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchJobService.java
@@ -52,9 +52,7 @@ public class BackgroundFetchJobService extends JobService {
}
if (FlagsFactory.getFlags().getFledgeCustomAudienceServiceKillSwitch()
- || !ConsentManager.getInstance(this)
- .getConsent(this.getPackageManager())
- .isGiven()) {
+ || !ConsentManager.getInstance(this).getConsent().isGiven()) {
LogUtil.d("FLEDGE Custom Audience API is disabled ; skipping and cancelling job");
return skipAndCancelBackgroundJob(params);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchRunner.java b/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchRunner.java
index 86f1926fe..6e3623869 100644
--- a/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchRunner.java
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchRunner.java
@@ -18,12 +18,15 @@ package com.android.adservices.service.customaudience;
import android.adservices.common.AdTechIdentifier;
import android.annotation.NonNull;
+import android.content.pm.PackageManager;
import android.net.Uri;
import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.customaudience.CustomAudienceStats;
import com.android.adservices.data.customaudience.DBCustomAudienceBackgroundFetchData;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AdServicesHttpsClient;
@@ -38,6 +41,8 @@ import java.util.concurrent.ExecutionException;
/** Runner executing actual background fetch tasks. */
public class BackgroundFetchRunner {
private final CustomAudienceDao mCustomAudienceDao;
+ private final PackageManager mPackageManager;
+ private final EnrollmentDao mEnrollmentDao;
private final Flags mFlags;
private final AdServicesHttpsClient mHttpsClient;
@@ -53,10 +58,17 @@ public class BackgroundFetchRunner {
}
public BackgroundFetchRunner(
- @NonNull CustomAudienceDao customAudienceDao, @NonNull Flags flags) {
+ @NonNull CustomAudienceDao customAudienceDao,
+ @NonNull PackageManager packageManager,
+ @NonNull EnrollmentDao enrollmentDao,
+ @NonNull Flags flags) {
Objects.requireNonNull(customAudienceDao);
+ Objects.requireNonNull(packageManager);
+ Objects.requireNonNull(enrollmentDao);
Objects.requireNonNull(flags);
mCustomAudienceDao = customAudienceDao;
+ mPackageManager = packageManager;
+ mEnrollmentDao = enrollmentDao;
mFlags = flags;
mHttpsClient =
new AdServicesHttpsClient(
@@ -74,12 +86,43 @@ public class BackgroundFetchRunner {
public void deleteExpiredCustomAudiences(@NonNull Instant jobStartTime) {
Objects.requireNonNull(jobStartTime);
- LogUtil.d("Starting custom audience garbage collection");
+ LogUtil.d("Starting expired custom audience garbage collection");
int numCustomAudiencesDeleted =
mCustomAudienceDao.deleteAllExpiredCustomAudienceData(jobStartTime);
LogUtil.d("Deleted %d expired custom audiences", numCustomAudiencesDeleted);
}
+ /**
+ * Deletes custom audiences whose owner applications which are not installed or in the app
+ * allowlist.
+ *
+ * <p>Also clears corresponding update information from the background fetch table.
+ */
+ public void deleteDisallowedOwnerCustomAudiences() {
+ LogUtil.d("Starting custom audience disallowed owner garbage collection");
+ CustomAudienceStats deletedCAStats =
+ mCustomAudienceDao.deleteAllDisallowedOwnerCustomAudienceData(
+ mPackageManager, mFlags);
+ LogUtil.d(
+ "Deleted %d custom audiences belonging to %d disallowed owner apps",
+ deletedCAStats.getTotalCustomAudienceCount(), deletedCAStats.getTotalOwnerCount());
+ }
+
+ /**
+ * Deletes custom audiences whose buyer ad techs which are not enrolled to use FLEDGE.
+ *
+ * <p>Also clears corresponding update information from the background fetch table.
+ */
+ public void deleteDisallowedBuyerCustomAudiences() {
+ LogUtil.d("Starting custom audience disallowed buyer garbage collection");
+ CustomAudienceStats deletedCAStats =
+ mCustomAudienceDao.deleteAllDisallowedBuyerCustomAudienceData(
+ mEnrollmentDao, mFlags);
+ LogUtil.d(
+ "Deleted %d custom audiences belonging to %d disallowed buyer ad techs",
+ deletedCAStats.getTotalCustomAudienceCount(), deletedCAStats.getTotalBuyerCount());
+ }
+
/** Updates a single given custom audience and persists the results. */
public void updateCustomAudience(
@NonNull Instant jobStartTime, @NonNull DBCustomAudienceBackgroundFetchData fetchData) {
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchWorker.java b/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchWorker.java
index 35a3add0f..10fe1460e 100644
--- a/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/BackgroundFetchWorker.java
@@ -24,6 +24,7 @@ import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.CustomAudienceDatabase;
import com.android.adservices.data.customaudience.DBCustomAudienceBackgroundFetchData;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.internal.annotations.VisibleForTesting;
@@ -91,7 +92,11 @@ public class BackgroundFetchWorker {
new BackgroundFetchWorker(
customAudienceDao,
flags,
- new BackgroundFetchRunner(customAudienceDao, flags));
+ new BackgroundFetchRunner(
+ customAudienceDao,
+ context.getPackageManager(),
+ EnrollmentDao.getInstance(context),
+ flags));
}
}
}
@@ -125,8 +130,10 @@ public class BackgroundFetchWorker {
mWorkInProgress = true;
mStopWorkRequested = false;
- // Clean up expired custom audiences first so the actual fetch won't do unnecessary work
+ // Clean up custom audiences first so the actual fetch won't do unnecessary work
mBackgroundFetchRunner.deleteExpiredCustomAudiences(jobStartTime);
+ mBackgroundFetchRunner.deleteDisallowedOwnerCustomAudiences();
+ mBackgroundFetchRunner.deleteDisallowedBuyerCustomAudiences();
if (mStopWorkRequested) {
LogUtil.d("Stopping FLEDGE background fetch");
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
index 4bd2e5d57..93850ae05 100644
--- a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java
@@ -20,6 +20,7 @@ import android.adservices.customaudience.CustomAudience;
import android.annotation.NonNull;
import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.customaudience.CustomAudienceStats;
import com.android.adservices.service.Flags;
import com.android.internal.annotations.VisibleForTesting;
@@ -79,16 +80,16 @@ public class CustomAudienceQuantityChecker {
long mCustomAudiencePerAppMaxCount = mFlags.getFledgeCustomAudiencePerAppMaxCount();
List<String> violations = new ArrayList<>();
- CustomAudienceDao.CustomAudienceStats customAudienceStats =
+ CustomAudienceStats customAudienceStats =
mCustomAudienceDao.getCustomAudienceStats(callerPackageName);
- if (customAudienceStats.getPerOwnerCount() == 0
- && customAudienceStats.getOwnerCount() >= mCustomAudienceMaxOwnerCount) {
+ if (customAudienceStats.getPerOwnerCustomAudienceCount() == 0
+ && customAudienceStats.getTotalOwnerCount() >= mCustomAudienceMaxOwnerCount) {
violations.add(THE_MAX_NUMBER_OF_OWNER_ALLOWED_FOR_THE_DEVICE_HAD_REACHED);
}
- if (customAudienceStats.getTotalCount() >= mCustomAudienceMaxCount) {
+ if (customAudienceStats.getTotalCustomAudienceCount() >= mCustomAudienceMaxCount) {
violations.add(THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED);
}
- if (customAudienceStats.getPerOwnerCount() >= mCustomAudiencePerAppMaxCount) {
+ if (customAudienceStats.getPerOwnerCustomAudienceCount() >= mCustomAudiencePerAppMaxCount) {
violations.add(THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED);
}
if (!violations.isEmpty()) {
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceServiceImpl.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceServiceImpl.java
index 61d26521f..67cd7df4c 100644
--- a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceServiceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceServiceImpl.java
@@ -170,7 +170,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow because we want to fail fast
throw exception;
}
@@ -223,7 +223,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
// Fail silently for revoked user consent
if (!mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- mContext.getPackageManager(), ownerPackageName)) {
+ ownerPackageName)) {
LogUtil.v("Joining custom audience");
mCustomAudienceImpl.joinCustomAudience(customAudience, ownerPackageName);
BackgroundFetchJobService.scheduleIfNeeded(mContext, mFlags, false);
@@ -245,7 +245,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
resultCode = AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
} finally {
if (shouldLog) {
- mAdServicesLogger.logFledgeApiCallStats(apiName, resultCode);
+ mAdServicesLogger.logFledgeApiCallStats(apiName, resultCode, 0);
}
}
}
@@ -302,7 +302,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow because we want to fail fast
throw exception;
}
@@ -350,8 +350,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
shouldLog = true;
// Fail silently for revoked user consent
- if (!mConsentManager.isFledgeConsentRevokedForApp(
- mContext.getPackageManager(), ownerPackageName)) {
+ if (!mConsentManager.isFledgeConsentRevokedForApp(ownerPackageName)) {
// TODO(b/233681870): Investigate implementation of actual failures
// in logs/metrics
mCustomAudienceImpl.leaveCustomAudience(ownerPackageName, buyer, name);
@@ -381,7 +380,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
resultCode = AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
} finally {
if (shouldLog) {
- mAdServicesLogger.logFledgeApiCallStats(apiName, resultCode);
+ mAdServicesLogger.logFledgeApiCallStats(apiName, resultCode, 0);
}
}
}
@@ -415,7 +414,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow to fail fast
throw exception;
}
@@ -424,7 +423,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
if (!devContext.getDevOptionsEnabled()) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
+ apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR, 0);
throw new SecurityException(API_NOT_AUTHORIZED_MSG);
}
@@ -468,7 +467,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow to fail fast
throw exception;
}
@@ -477,7 +476,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
if (!devContext.getDevOptionsEnabled()) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
+ apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR, 0);
throw new SecurityException(API_NOT_AUTHORIZED_MSG);
}
@@ -534,7 +533,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
Objects.requireNonNull(callback);
} catch (NullPointerException exception) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT);
+ apiName, AdServicesStatusUtils.STATUS_INVALID_ARGUMENT, 0);
// Rethrow to fail fast
throw exception;
}
@@ -545,7 +544,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
if (!devContext.getDevOptionsEnabled()) {
mAdServicesLogger.logFledgeApiCallStats(
- apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
+ apiName, AdServicesStatusUtils.STATUS_INTERNAL_ERROR, 0);
throw new SecurityException(API_NOT_AUTHORIZED_MSG);
}
@@ -570,7 +569,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
return mCallingAppUidSupplier.getCallingAppUid();
} catch (IllegalStateException illegalStateException) {
mAdServicesLogger.logFledgeApiCallStats(
- apiNameLoggingId, AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
+ apiNameLoggingId, AdServicesStatusUtils.STATUS_INTERNAL_ERROR, 0);
throw illegalStateException;
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/devapi/AdSelectionOverrider.java b/adservices/service-core/java/com/android/adservices/service/devapi/AdSelectionOverrider.java
index 414d688cc..28011b0b7 100644
--- a/adservices/service-core/java/com/android/adservices/service/devapi/AdSelectionOverrider.java
+++ b/adservices/service-core/java/com/android/adservices/service/devapi/AdSelectionOverrider.java
@@ -240,7 +240,7 @@ public class AdSelectionOverrider {
return FluentFuture.from(
mBackgroundExecutorService.submit(
() -> {
- if (!mConsentManager.getConsent(mPackageManager).isGiven()) {
+ if (!mConsentManager.getConsent().isGiven()) {
return AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
}
@@ -254,7 +254,7 @@ public class AdSelectionOverrider {
return FluentFuture.from(
mBackgroundExecutorService.submit(
() -> {
- if (!mConsentManager.getConsent(mPackageManager).isGiven()) {
+ if (!mConsentManager.getConsent().isGiven()) {
return AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
}
@@ -268,7 +268,7 @@ public class AdSelectionOverrider {
return FluentFuture.from(
mBackgroundExecutorService.submit(
() -> {
- if (!mConsentManager.getConsent(mPackageManager).isGiven()) {
+ if (!mConsentManager.getConsent().isGiven()) {
return AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
}
@@ -295,7 +295,7 @@ public class AdSelectionOverrider {
resultCode = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR;
throw e.rethrowFromSystemServer();
} finally {
- mAdServicesLogger.logFledgeApiCallStats(apiName, resultCode);
+ mAdServicesLogger.logFledgeApiCallStats(apiName, resultCode, 0);
}
}
@@ -313,7 +313,7 @@ public class AdSelectionOverrider {
resultCodeInt = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR;
throw e.rethrowFromSystemServer();
} finally {
- mAdServicesLogger.logFledgeApiCallStats(apiName, resultCodeInt);
+ mAdServicesLogger.logFledgeApiCallStats(apiName, resultCodeInt, 0);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/devapi/CustomAudienceOverrider.java b/adservices/service-core/java/com/android/adservices/service/devapi/CustomAudienceOverrider.java
index 627bd3daf..8edff6a98 100644
--- a/adservices/service-core/java/com/android/adservices/service/devapi/CustomAudienceOverrider.java
+++ b/adservices/service-core/java/com/android/adservices/service/devapi/CustomAudienceOverrider.java
@@ -253,7 +253,7 @@ public class CustomAudienceOverrider {
mListeningExecutorService.submit(
() -> {
if (mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManager, mDevContext.getCallingAppPackageName())) {
+ mDevContext.getCallingAppPackageName())) {
LogUtil.v("User consent is revoked!");
return AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
}
@@ -270,7 +270,7 @@ public class CustomAudienceOverrider {
mListeningExecutorService.submit(
() -> {
if (mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManager, mDevContext.getCallingAppPackageName())) {
+ mDevContext.getCallingAppPackageName())) {
return AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
}
@@ -284,7 +284,7 @@ public class CustomAudienceOverrider {
mListeningExecutorService.submit(
() -> {
if (mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManager, mDevContext.getCallingAppPackageName())) {
+ mDevContext.getCallingAppPackageName())) {
return AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
}
@@ -311,7 +311,7 @@ public class CustomAudienceOverrider {
resultCode = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR;
throw e.rethrowAsRuntimeException();
} finally {
- mAdServicesLogger.logFledgeApiCallStats(apiName, resultCode);
+ mAdServicesLogger.logFledgeApiCallStats(apiName, resultCode, 0);
}
}
@@ -329,7 +329,7 @@ public class CustomAudienceOverrider {
resultCodeInt = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR;
throw e.rethrowAsRuntimeException();
} finally {
- mAdServicesLogger.logFledgeApiCallStats(apiName, resultCodeInt);
+ mAdServicesLogger.logFledgeApiCallStats(apiName, resultCodeInt, 0);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/enrollment/EnrollmentData.java b/adservices/service-core/java/com/android/adservices/service/enrollment/EnrollmentData.java
index 6d0d0e49f..f1f3ae665 100644
--- a/adservices/service-core/java/com/android/adservices/service/enrollment/EnrollmentData.java
+++ b/adservices/service-core/java/com/android/adservices/service/enrollment/EnrollmentData.java
@@ -16,13 +16,20 @@
package com.android.adservices.service.enrollment;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
/** POJO for Adtech EnrollmentData, store the data download using MDD. */
public class EnrollmentData {
+ @VisibleForTesting public static String SEPARATOR = " ";
private String mEnrollmentId;
private String mCompanyId;
@@ -119,6 +126,19 @@ public class EnrollmentData {
return mEncryptionKeyUrl;
}
+ /**
+ * Returns the given {@code input} as a list of values split by the separator value used for all
+ * enrollment data.
+ */
+ @NonNull
+ public static List<String> splitEnrollmentInputToList(@Nullable String input) {
+ if (input == null || input.trim().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return Arrays.asList(input.trim().split(SEPARATOR));
+ }
+
/** Builder for {@link EnrollmentData}. */
public static final class Builder {
private final EnrollmentData mBuilding;
@@ -147,7 +167,7 @@ public class EnrollmentData {
/** See {@link EnrollmentData#getSdkNames()} */
public Builder setSdkNames(String sdkNames) {
- mBuilding.mSdkNames = Arrays.asList(sdkNames.split(" "));
+ mBuilding.mSdkNames = splitEnrollmentInputToList(sdkNames);
return this;
}
@@ -162,7 +182,7 @@ public class EnrollmentData {
public Builder setAttributionSourceRegistrationUrl(
String attributionSourceRegistrationUrl) {
mBuilding.mAttributionSourceRegistrationUrl =
- Arrays.asList(attributionSourceRegistrationUrl.split(" "));
+ splitEnrollmentInputToList(attributionSourceRegistrationUrl);
return this;
}
@@ -177,7 +197,7 @@ public class EnrollmentData {
public Builder setAttributionTriggerRegistrationUrl(
String attributionTriggerRegistrationUrl) {
mBuilding.mAttributionTriggerRegistrationUrl =
- Arrays.asList(attributionTriggerRegistrationUrl.split(" "));
+ splitEnrollmentInputToList(attributionTriggerRegistrationUrl);
return this;
}
@@ -189,7 +209,8 @@ public class EnrollmentData {
/** See {@link EnrollmentData#getAttributionReportingUrl()}. */
public Builder setAttributionReportingUrl(String attributionReportingUrl) {
- mBuilding.mAttributionReportingUrl = Arrays.asList(attributionReportingUrl.split(" "));
+ mBuilding.mAttributionReportingUrl =
+ splitEnrollmentInputToList(attributionReportingUrl);
return this;
}
@@ -205,7 +226,7 @@ public class EnrollmentData {
public Builder setRemarketingResponseBasedRegistrationUrl(
String remarketingResponseBasedRegistrationUrl) {
mBuilding.mRemarketingResponseBasedRegistrationUrl =
- Arrays.asList(remarketingResponseBasedRegistrationUrl.split(" "));
+ splitEnrollmentInputToList(remarketingResponseBasedRegistrationUrl);
return this;
}
@@ -217,7 +238,7 @@ public class EnrollmentData {
/** See {@link EnrollmentData#getEncryptionKeyUrl()}. */
public Builder setEncryptionKeyUrl(String encryptionKeyUrl) {
- mBuilding.mEncryptionKeyUrl = Arrays.asList(encryptionKeyUrl.split(" "));
+ mBuilding.mEncryptionKeyUrl = splitEnrollmentInputToList(encryptionKeyUrl);
return this;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/js/JSSandboxIsNotAvailableException.java b/adservices/service-core/java/com/android/adservices/service/js/JSSandboxIsNotAvailableException.java
new file mode 100644
index 000000000..32d5c14c5
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/js/JSSandboxIsNotAvailableException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.js;
+
+/**
+ * Internal exception for thrown when the current active WebView version doesn't have JS Sandbox
+ * available.
+ *
+ * <p>This exception is not meant to be exposed externally and should not be passed outside of the
+ * service.
+ */
+public class JSSandboxIsNotAvailableException extends RuntimeException {
+ private static final String JS_SANDBOX_IS_NOT_AVAILABLE_EXCEPTION_MSG =
+ "JS Sandbox is not available in the current version of WebView";
+
+ public JSSandboxIsNotAvailableException() {
+ super(JS_SANDBOX_IS_NOT_AVAILABLE_EXCEPTION_MSG);
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/js/JSScriptEngine.java b/adservices/service-core/java/com/android/adservices/service/js/JSScriptEngine.java
index 9075c42fa..6cdbc25bd 100644
--- a/adservices/service-core/java/com/android/adservices/service/js/JSScriptEngine.java
+++ b/adservices/service-core/java/com/android/adservices/service/js/JSScriptEngine.java
@@ -20,6 +20,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.Build;
+import android.webkit.WebView;
import androidx.javascriptengine.IsolateStartupParameters;
import androidx.javascriptengine.JavaScriptIsolate;
@@ -63,9 +66,9 @@ public class JSScriptEngine {
public static final String WASM_MODULE_BYTES_ID = "__wasmModuleBytes";
public static final String WASM_MODULE_ARG_NAME = "wasmModule";
- public static final String NON_SUPPORTED_MAX_HEAP_SIZE_ERROR =
+ public static final String NON_SUPPORTED_MAX_HEAP_SIZE_EXCEPTION_MSG =
"JS isolate does not support Max heap size";
- public static final String JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_ERROR_MSG =
+ public static final String JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG =
"Unable to create isolate";
@SuppressLint("StaticFieldLeak")
@@ -82,6 +85,9 @@ public class JSScriptEngine {
* the instance is invalidated by calling {@link
* JavaScriptSandboxProvider#destroyCurrentInstance()}. The instance is returned wrapped in a
* {@code Future}
+ *
+ * <p>Throws {@link JSSandboxIsNotAvailableException} if JS Sandbox is not available in the
+ * current version of the WebView
*/
@VisibleForTesting
static class JavaScriptSandboxProvider {
@@ -99,6 +105,13 @@ public class JSScriptEngine {
public FluentFuture<JavaScriptSandbox> getFutureInstance(Context context) {
synchronized (mSandboxLock) {
if (mFutureSandbox == null) {
+ if (!AvailabilityChecker.isJSSandboxAvailable()) {
+ LogUtil.e(
+ "JS Sandbox is not available in this version of WebView "
+ + "or WebView is not installed at all!");
+ throw new JSSandboxIsNotAvailableException();
+ }
+
LogUtil.i("Creating JavaScriptSandbox");
mSandboxInitStopWatch =
mProfiler.start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME);
@@ -459,7 +472,7 @@ public class JSScriptEngine {
if (!isConfigurableHeapSizeSupported(jsSandbox)
&& isolateSettings.getEnforceMaxHeapSizeFeature()) {
LogUtil.e("Memory limit enforcement required, but not supported by Isolate");
- throw new IllegalStateException(NON_SUPPORTED_MAX_HEAP_SIZE_ERROR);
+ throw new IllegalStateException(NON_SUPPORTED_MAX_HEAP_SIZE_EXCEPTION_MSG);
}
JavaScriptIsolate javaScriptIsolate;
@@ -485,7 +498,7 @@ public class JSScriptEngine {
"JavaScriptIsolate does not support setting max heap size, cannot create an"
+ " isolate to run JS code into.");
throw new JSScriptEngineConnectionException(
- JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_ERROR_MSG, isolateMemoryLimitUnsupported);
+ JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG, isolateMemoryLimitUnsupported);
} catch (RuntimeException jsSandboxIsDisconnected) {
LogUtil.e(
"JavaScriptSandboxProcess is disconnected, cannot create an isolate to run JS"
@@ -493,7 +506,7 @@ public class JSScriptEngine {
+ " future calls.");
mJsSandboxProvider.destroyCurrentInstance();
throw new JSScriptEngineConnectionException(
- JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_ERROR_MSG, jsSandboxIsDisconnected);
+ JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG, jsSandboxIsDisconnected);
} finally {
isolateStopWatch.stop();
}
@@ -547,6 +560,42 @@ public class JSScriptEngine {
}
/**
+ * Checks if JS Sandbox is available in the WebView version that is installed on the device
+ * before attempting to create it. Attempting to create JS Sandbox when it's not available
+ * results in returning of a null value.
+ */
+ public static class AvailabilityChecker {
+
+ /**
+ * @return true if JS Sandbox is available in the current WebView version, false otherwise.
+ */
+ public static boolean isJSSandboxAvailable() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ return false;
+ }
+
+ PackageInfo systemWebViewPackage;
+ if ((systemWebViewPackage = WebView.getCurrentWebViewPackage()) == null) {
+ return false;
+ }
+
+ // The current IPC interface was introduced in 102.0.4976.0 (crrev.com/3560402), so all
+ // versions above that are supported.
+ final long introducedVersion = 4976_000_00L;
+
+ // Additionally, the relevant IPC changes were cherry-picked into M101 at 101.0.4951.24
+ // (crrev.com/3568575), so versions between 101.0.4951.24 inclusive and 102.0.4952.0
+ // exclusive are also supported.
+ final long cpWindowStartInclusive = 4951_240_00L;
+ final long cpWindowEndExclusive = 4952_000_00L;
+
+ return systemWebViewPackage.getLongVersionCode() >= introducedVersion
+ || (systemWebViewPackage.getLongVersionCode() >= cpWindowStartInclusive
+ && systemWebViewPackage.getLongVersionCode() < cpWindowEndExclusive);
+ }
+ }
+
+ /**
* Wrapper class required to convert an {@link java.lang.AutoCloseable} {@link
* JavaScriptIsolate} into a {@link Closeable} type.
*/
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistration.java b/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistration.java
new file mode 100644
index 000000000..9a138aa60
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistration.java
@@ -0,0 +1,352 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_REDIRECTS_PER_REGISTRATION;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.net.Uri;
+
+import androidx.annotation.Nullable;
+
+import com.android.adservices.service.measurement.util.Validation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** POJO for AsyncRegistration. */
+public class AsyncRegistration {
+
+ public enum RegistrationType {
+ APP_SOURCE,
+ APP_TRIGGER,
+ WEB_SOURCE,
+ WEB_TRIGGER
+ }
+
+ private final String mId;
+ private final String mEnrollmentId;
+ private final Uri mOsDestination;
+ private final Uri mWebDestination;
+ private final Uri mRegistrationUri;
+ private final Uri mVerifiedDestination;
+ private final Uri mTopOrigin;
+ @RedirectType private int mRedirectType;
+ private final int mRedirectCount;
+ private final Uri mRegistrant;
+ private final Source.SourceType mSourceType;
+ private long mRequestTime;
+ private long mRetryCount;
+ private long mLastProcessingTime;
+ private final RegistrationType mType;
+ private final boolean mDebugKeyAllowed;
+
+ @IntDef(value = {
+ RedirectType.NONE,
+ RedirectType.ANY,
+ RedirectType.DAISY_CHAIN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RedirectType {
+ int NONE = 0;
+ int ANY = 1;
+ int DAISY_CHAIN = 2;
+ }
+
+ public AsyncRegistration(@NonNull AsyncRegistration.Builder builder) {
+ mId = builder.mId;
+ mEnrollmentId = builder.mEnrollmentId;
+ mOsDestination = builder.mOsDestination;
+ mWebDestination = builder.mWebDestination;
+ mRegistrationUri = builder.mRegistrationUri;
+ mVerifiedDestination = builder.mVerifiedDestination;
+ mTopOrigin = builder.mTopOrigin;
+ mRedirectType = builder.mRedirectType;
+ mRedirectCount = builder.mRedirectCount;
+ mRegistrant = builder.mRegistrant;
+ mSourceType = builder.mSourceType;
+ mRequestTime = builder.mRequestTime;
+ mRetryCount = builder.mRetryCount;
+ mLastProcessingTime = builder.mLastProcessingTime;
+ mType = builder.mType;
+ mDebugKeyAllowed = builder.mDebugKeyAllowed;
+ }
+
+ /** Unique identifier for the {@link AsyncRegistration}. */
+ public String getId() {
+ return mId;
+ }
+
+ /** App destination of the {@link Source}. */
+ @Nullable
+ public Uri getOsDestination() {
+ return mOsDestination;
+ }
+
+ /** Web destination of the {@link Source}. */
+ @Nullable
+ public Uri getWebDestination() {
+ return mWebDestination;
+ }
+
+ /** Represents the location of registration payload. */
+ @NonNull
+ public Uri getRegistrationUri() {
+ return mRegistrationUri;
+ }
+
+ /** Uri used to identify and locate a {@link Source} originating from the web. */
+ @Nullable
+ public Uri getVerifiedDestination() {
+ return mVerifiedDestination;
+ }
+
+ /** Package name of caller app. */
+ @NonNull
+ public Uri getTopOrigin() {
+ return mTopOrigin;
+ }
+
+ /** Package name of caller app, name comes from context. */
+ @NonNull
+ public Uri getRegistrant() {
+ return mRegistrant;
+ }
+
+ /** Derived from the Ad tech domain. */
+ @NonNull
+ public String getEnrollmentId() {
+ return mEnrollmentId;
+ }
+
+ /** Determines the type of redirects of a {@link Source} or {@link Trigger}, as well as the
+ * states, None and Completed. */
+ @RedirectType
+ public int getRedirectType() {
+ return mRedirectType;
+ }
+
+ /** Determines the count of remaining redirects to observe when fetching a {@link Source} or
+ * {@link Trigger}, provided the {@link RedirectType} is not None or Compeleted */
+ public int getRedirectCount() {
+ return mRedirectCount;
+ }
+
+ /** Determines whether the input event was a click or view. */
+ public Source.SourceType getSourceType() {
+ return mSourceType;
+ }
+
+ /** Time in ms that record arrived at Registration Queue. */
+ public long getRequestTime() {
+ return mRequestTime;
+ }
+
+ /** Retry attempt counter. */
+ public long getRetryCount() {
+ return mRetryCount;
+ }
+
+ /** Processing time in ms. */
+ public long getLastProcessingTime() {
+ return mLastProcessingTime;
+ }
+
+ /** Indicates how the record will be processed . */
+ public RegistrationType getType() {
+ return mType;
+ }
+
+ /** Indicates whether the debug key provided by Ad-Tech is allowed to be used or not. */
+ public boolean getDebugKeyAllowed() {
+ return mDebugKeyAllowed;
+ }
+
+ /** Increments the retry count of the current record. */
+ public void incrementRetryCount() {
+ ++mRetryCount;
+ }
+
+ /** Indicates whether the registration runner should process redirects for this registration. */
+ public boolean shouldProcessRedirects() {
+ if (mRedirectType == RedirectType.NONE) {
+ return false;
+ }
+ return mRedirectType == RedirectType.ANY || mRedirectCount < MAX_REDIRECTS_PER_REGISTRATION;
+ }
+
+ /** Gets the next expected redirect count for this registration. */
+ public int getNextRedirectCount() {
+ if (mRedirectType == AsyncRegistration.RedirectType.NONE) {
+ return 0;
+ // Redirect type is being set for the first time for this registration-sequence.
+ } else if (mRedirectType == AsyncRegistration.RedirectType.ANY) {
+ return 1;
+ // This registration-sequence already has an assigned redirect type.
+ } else {
+ return mRedirectCount + 1;
+ }
+ }
+
+ /** Builder for {@link AsyncRegistration}. */
+ public static class Builder {
+ private String mId;
+ private String mEnrollmentId;
+ private Uri mOsDestination;
+ private Uri mWebDestination;
+ private Uri mRegistrationUri;
+ private Uri mVerifiedDestination;
+ private Uri mTopOrigin;
+ private @RedirectType int mRedirectType = RedirectType.ANY;
+ private int mRedirectCount = 0;
+ private Uri mRegistrant;
+ private Source.SourceType mSourceType;
+ private long mRequestTime;
+ private long mRetryCount = 0;
+ private long mLastProcessingTime;
+ private AsyncRegistration.RegistrationType mType;
+ private boolean mDebugKeyAllowed;
+
+ /** See {@link AsyncRegistration#getId()}. */
+ @NonNull
+ public Builder setId(@NonNull String id) {
+ Validation.validateNonNull(id);
+ mId = id;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getEnrollmentId()}. */
+ @NonNull
+ public Builder setEnrollmentId(@NonNull String enrollmentId) {
+ Validation.validateNonNull(enrollmentId);
+ mEnrollmentId = enrollmentId;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getOsDestination()}. */
+ @NonNull
+ public Builder setOsDestination(@Nullable Uri osDestination) {
+ mOsDestination = osDestination;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getRegistrationUri()}. */
+ @NonNull
+ public Builder setRegistrationUri(@NonNull Uri registrationUri) {
+ Validation.validateNonNull(registrationUri);
+ mRegistrationUri = registrationUri;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getVerifiedDestination()}. */
+ @NonNull
+ public Builder setVerifiedDestination(@Nullable Uri verifiedDestination) {
+ mVerifiedDestination = verifiedDestination;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getWebDestination()}. */
+ @NonNull
+ public Builder setWebDestination(@Nullable Uri webDestination) {
+ mWebDestination = webDestination;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getTopOrigin()}. */
+ @NonNull
+ public Builder setTopOrigin(@Nullable Uri topOrigin) {
+ mTopOrigin = topOrigin;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getRedirectType()}. */
+ @NonNull
+ public Builder setRedirectType(@RedirectType int redirectType) {
+ mRedirectType = redirectType;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getRedirectCount()}. */
+ @NonNull
+ public Builder setRedirectCount(int redirectCount) {
+ mRedirectCount = redirectCount;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getRegistrant()}. */
+ @NonNull
+ public Builder setRegistrant(@NonNull Uri registrant) {
+ Validation.validateNonNull(registrant);
+ mRegistrant = registrant;
+ return this;
+ }
+
+ /**
+ * See {@link AsyncRegistration#getSourceType()}. Valid inputs are ordinals of {@link
+ * Source.SourceType} enum values.
+ */
+ @NonNull
+ public Builder setSourceType(Source.SourceType sourceType) {
+ mSourceType = sourceType;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getRequestTime()}. */
+ @NonNull
+ public Builder setRequestTime(long requestTime) {
+ mRequestTime = requestTime;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getRetryCount()}. */
+ @NonNull
+ public Builder setRetryCount(long retryCount) {
+ mRetryCount = retryCount;
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getLastProcessingTime()}. */
+ @NonNull
+ public Builder setLastProcessingTime(long lastProcessingTime) {
+ mLastProcessingTime = lastProcessingTime;
+ return this;
+ }
+
+ /**
+ * See {@link AsyncRegistration#getType()}. Valid inputs are ordinals of {@link
+ * AsyncRegistration.RegistrationType} enum values.
+ */
+ @NonNull
+ public Builder setType(int type) {
+ mType = RegistrationType.values()[type];
+ return this;
+ }
+
+ /** See {@link AsyncRegistration#getDebugKeyAllowed()}. */
+ @NonNull
+ public Builder setDebugKeyAllowed(boolean debugKeyAllowed) {
+ mDebugKeyAllowed = debugKeyAllowed;
+ return this;
+ }
+
+ /** Build the {@link AsyncRegistration}. */
+ public AsyncRegistration build() {
+ return new AsyncRegistration(this);
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueJobService.java
new file mode 100644
index 000000000..fecae7fa3
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueJobService.java
@@ -0,0 +1,127 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.AdServicesConfig.ASYNC_REGISTRATION_QUEUE_JOB_ID;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.time.Clock;
+import java.time.Instant;
+
+/** Job Service for servicing queued registration requests */
+public class AsyncRegistrationQueueJobService extends JobService {
+
+ private long mRecordServiceLimit = 50;
+ private short mRetryLimit = 5;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (FlagsFactory.getFlags().getRegistrationJobQueueKillSwitch()) {
+ LogUtil.e("AsyncRegistrationQueueJobService is disabled");
+ return skipAndCancelBackgroundJob(params);
+ }
+
+ Instant jobStartTime = Clock.systemUTC().instant();
+ LogUtil.d(
+ "AsyncRegistrationQueueJobService.onStartJob " + "at %s", jobStartTime.toString());
+ AsyncRegistrationQueueRunner asyncQueueRunner =
+ AsyncRegistrationQueueRunner.getInstance(getApplicationContext());
+
+ AdServicesExecutors.getBackgroundExecutor()
+ .execute(
+ () -> {
+ asyncQueueRunner.runAsyncRegistrationQueueWorker(
+ mRecordServiceLimit, mRetryLimit);
+ jobFinished(params, false);
+ });
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ LogUtil.d("AsyncRegistrationQueueJobService.onStopJob");
+ return false;
+ }
+
+ @VisibleForTesting
+ protected static void schedule(Context context, JobScheduler jobScheduler) {
+ Flags flags = FlagsFactory.getFlags();
+ final JobInfo job =
+ new JobInfo.Builder(
+ ASYNC_REGISTRATION_QUEUE_JOB_ID,
+ new ComponentName(context, AsyncRegistrationQueueJobService.class))
+ .setRequiresBatteryNotLow(true)
+ .setPeriodic(flags.getRegistrationJobQueueIntervalMs())
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setPersisted(true)
+ .build();
+ jobScheduler.schedule(job);
+ }
+
+ /**
+ * Schedule Async Registration Queue Job Service if it is not already scheduled
+ *
+ * @param context the context
+ * @param forceSchedule flag to indicate whether to force rescheduling the job.
+ */
+ public static void scheduleIfNeeded(Context context, boolean forceSchedule) {
+ if (FlagsFactory.getFlags().getRegistrationJobQueueKillSwitch()) {
+ LogUtil.e("AsyncRegistrationQueueJobService is disabled, skip scheduling");
+ return;
+ }
+
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ LogUtil.e("JobScheduler not found");
+ return;
+ }
+
+ final JobInfo job = jobScheduler.getPendingJob(ASYNC_REGISTRATION_QUEUE_JOB_ID);
+ // Schedule if it hasn't been scheduled already or force rescheduling
+ if (job == null || forceSchedule) {
+ schedule(context, jobScheduler);
+ LogUtil.d("Scheduled AsyncRegistrationQueueJobService");
+ } else {
+ LogUtil.d(
+ "AsyncRegistrationQueueJobService already scheduled," + " skipping reschedule");
+ }
+ }
+
+ private boolean skipAndCancelBackgroundJob(final JobParameters params) {
+ final JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
+ if (jobScheduler != null) {
+ jobScheduler.cancel(ASYNC_REGISTRATION_QUEUE_JOB_ID);
+ }
+ // Tell the JobScheduler that the job is done and does not need to be rescheduled
+ jobFinished(params, false);
+
+ // Returning false to reschedule this job.
+ return false;
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java b/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java
new file mode 100644
index 000000000..e1378ae11
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java
@@ -0,0 +1,687 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.measurement.attribution.TriggerContentProvider.TRIGGER_URI;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.data.measurement.IMeasurementDao;
+import com.android.adservices.service.measurement.registration.AsyncSourceFetcher;
+import com.android.adservices.service.measurement.registration.AsyncTriggerFetcher;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
+import com.android.adservices.service.measurement.util.Enrollment;
+import com.android.adservices.service.measurement.util.Web;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/** Runner for servicing queued registration requests */
+public class AsyncRegistrationQueueRunner {
+ private static AsyncRegistrationQueueRunner sAsyncRegistrationQueueRunner;
+ private DatastoreManager mDatastoreManager;
+ private AsyncSourceFetcher mAsyncSourceFetcher;
+ private AsyncTriggerFetcher mAsyncTriggerFetcher;
+ private EnrollmentDao mEnrollmentDao;
+ private final ContentResolver mContentResolver;
+
+ private AsyncRegistrationQueueRunner(Context context) {
+ mDatastoreManager = DatastoreManagerFactory.getDatastoreManager(context);
+ mAsyncSourceFetcher = new AsyncSourceFetcher(context);
+ mAsyncTriggerFetcher = new AsyncTriggerFetcher(context);
+ mEnrollmentDao = EnrollmentDao.getInstance(context);
+ mContentResolver = context.getContentResolver();
+ }
+
+ @VisibleForTesting
+ AsyncRegistrationQueueRunner(
+ ContentResolver contentResolver,
+ AsyncSourceFetcher asyncSourceFetcher,
+ AsyncTriggerFetcher asyncTriggerFetcher,
+ EnrollmentDao enrollmentDao,
+ DatastoreManager datastoreManager) {
+ mAsyncSourceFetcher = asyncSourceFetcher;
+ mAsyncTriggerFetcher = asyncTriggerFetcher;
+ mDatastoreManager = datastoreManager;
+ mEnrollmentDao = enrollmentDao;
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Returns an instance of AsyncRegistrationQueueRunner.
+ *
+ * @param context the current {@link Context}.
+ */
+ public static synchronized AsyncRegistrationQueueRunner getInstance(Context context) {
+ Objects.requireNonNull(context);
+ if (sAsyncRegistrationQueueRunner == null) {
+ sAsyncRegistrationQueueRunner = new AsyncRegistrationQueueRunner(context);
+ }
+ return sAsyncRegistrationQueueRunner;
+ }
+
+ /**
+ * Service records in the AsyncRegistration Queue table.
+ *
+ * @param recordServiceLimit a long representing how many records will be serviced during this
+ * run.
+ * @param retryLimit represents the amount of retries that will be allowed for each record.
+ */
+ public void runAsyncRegistrationQueueWorker(long recordServiceLimit, short retryLimit) {
+ Set<String> failedAdTechEnrollmentIds = new HashSet<>();
+ for (int i = 0; i < recordServiceLimit; i++) {
+ Optional<AsyncRegistration> optionalAsyncRegistration =
+ mDatastoreManager.runInTransactionWithResult(
+ (dao) -> {
+ List<String> failedAdTechEnrollmentIdsList =
+ new ArrayList<String>();
+ failedAdTechEnrollmentIdsList.addAll(failedAdTechEnrollmentIds);
+ return dao.fetchNextQueuedAsyncRegistration(
+ retryLimit, failedAdTechEnrollmentIdsList);
+ });
+
+ AsyncRegistration asyncRegistration;
+ if (optionalAsyncRegistration.isPresent()) {
+ asyncRegistration = optionalAsyncRegistration.get();
+ } else {
+ LogUtil.d("AsyncRegistrationQueueRunner:" + " no async registration fetched.");
+ return;
+ }
+
+ if (asyncRegistration.getType() == AsyncRegistration.RegistrationType.APP_SOURCE
+ || asyncRegistration.getType()
+ == AsyncRegistration.RegistrationType.WEB_SOURCE) {
+ LogUtil.d("AsyncRegistrationQueueRunner:" + " processing source");
+ processSourceRegistration(asyncRegistration, failedAdTechEnrollmentIds);
+ } else if (asyncRegistration.getType() == AsyncRegistration.RegistrationType.APP_TRIGGER
+ || asyncRegistration.getType()
+ == AsyncRegistration.RegistrationType.WEB_TRIGGER) {
+ LogUtil.d("AsyncRegistrationQueueRunner:" + " processing trigger");
+ processTriggerRegistration(asyncRegistration, failedAdTechEnrollmentIds);
+ }
+ }
+ }
+
+ private void processSourceRegistration(
+ AsyncRegistration asyncRegistration, Set<String> failedAdTechEnrollmentIds) {
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ Optional<Source> resultSource =
+ mAsyncSourceFetcher.fetchSource(asyncRegistration, asyncFetchStatus, asyncRedirect);
+
+ mDatastoreManager.runInTransaction(
+ (dao) -> {
+ if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE
+ || asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.NETWORK_ERROR) {
+ failedAdTechEnrollmentIds.add(asyncRegistration.getEnrollmentId());
+ asyncRegistration.incrementRetryCount();
+ dao.updateRetryCount(asyncRegistration);
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "source registration will be queued for retry "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ } else if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.PARSING_ERROR
+ || asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT) {
+ dao.deleteAsyncRegistration(asyncRegistration.getId());
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "source registration will not be queued for retry. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ } else if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.SUCCESS) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "source registration success case. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ if (resultSource.isPresent()) {
+ Source source = resultSource.get();
+ Uri topOrigin =
+ asyncRegistration.getType()
+ == AsyncRegistration.RegistrationType.WEB_SOURCE
+ ? asyncRegistration.getTopOrigin()
+ : getPublisher(asyncRegistration);
+ @EventSurfaceType
+ int publisherType =
+ asyncRegistration.getType()
+ == AsyncRegistration.RegistrationType.WEB_SOURCE
+ ? EventSurfaceType.WEB
+ : EventSurfaceType.APP;
+ if (isSourceAllowedToInsert(source, topOrigin, publisherType, dao)) {
+ insertSourcesFromTransaction(source, dao);
+ }
+ Uri osDestination =
+ asyncRegistration.getRedirectType()
+ != AsyncRegistration.RedirectType.ANY
+ ? asyncRegistration.getOsDestination()
+ : source.getAppDestination();
+ Uri webDestination =
+ asyncRegistration.getRedirectType()
+ != AsyncRegistration.RedirectType.ANY
+ ? asyncRegistration.getWebDestination()
+ : source.getWebDestination();
+ if (asyncRegistration.shouldProcessRedirects()) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "source registration; processing redirects. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ processRedirects(
+ asyncRegistration,
+ asyncRedirect,
+ webDestination,
+ osDestination,
+ dao);
+ }
+ }
+ dao.deleteAsyncRegistration(asyncRegistration.getId());
+ }
+ });
+ }
+
+ private void processTriggerRegistration(
+ AsyncRegistration asyncRegistration, Set<String> failedAdTechEnrollmentIds) {
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ Optional<Trigger> resultTrigger = mAsyncTriggerFetcher.fetchTrigger(
+ asyncRegistration, asyncFetchStatus, asyncRedirect);
+ boolean status =
+ mDatastoreManager.runInTransaction(
+ (dao) -> {
+ if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE
+ || asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.NETWORK_ERROR) {
+ failedAdTechEnrollmentIds.add(asyncRegistration.getEnrollmentId());
+ asyncRegistration.incrementRetryCount();
+ dao.updateRetryCount(asyncRegistration);
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "trigger registration will be queued for retry "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ } else if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.PARSING_ERROR
+ || asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT) {
+ dao.deleteAsyncRegistration(asyncRegistration.getId());
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: async trigger "
+ + "registration will not be queued for retry. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ } else if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.SUCCESS) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "trigger registration success case. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ if (resultTrigger.isPresent()) {
+ Trigger trigger = resultTrigger.get();
+ if (asyncRegistration.shouldProcessRedirects()) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: async trigger"
+ + " registration; processing redirects. Fetch"
+ + " Status : "
+ + asyncFetchStatus.getStatus());
+ processRedirects(
+ asyncRegistration,
+ asyncRedirect,
+ asyncRegistration.getWebDestination(),
+ asyncRegistration.getOsDestination(),
+ dao);
+ }
+ dao.insertTrigger(trigger);
+ }
+ dao.deleteAsyncRegistration(asyncRegistration.getId());
+ }
+ });
+ if (status && asyncFetchStatus.getStatus() == AsyncFetchStatus.ResponseStatus.SUCCESS) {
+ notifyTriggerContentProvider();
+ }
+ }
+
+ private static Integer countDistinctDestinationsPerPublisher(
+ Uri publisher,
+ @EventSurfaceType int publisherType,
+ String enrollmentId,
+ Uri destination,
+ @EventSurfaceType int destinationType,
+ long windowStartTime,
+ long requestTime,
+ IMeasurementDao dao) {
+
+ try {
+ return dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ publisher,
+ publisherType,
+ enrollmentId,
+ destination,
+ destinationType,
+ windowStartTime,
+ requestTime);
+ } catch (DatastoreException e) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + " countDistinctDestinationsPerPublisher failed: "
+ + e);
+ return null;
+ }
+ }
+
+ private static Integer countDistinctEnrollmentsPerPublisher(
+ Uri publisher,
+ @EventSurfaceType int publisherType,
+ Uri destination,
+ String enrollmentId,
+ long windowStartTime,
+ long requestTime,
+ IMeasurementDao dao) {
+
+ try {
+ return dao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ publisher,
+ publisherType,
+ destination,
+ enrollmentId,
+ windowStartTime,
+ requestTime);
+ } catch (DatastoreException e) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + " countDistinctEnrollmentsPerPublisher failed "
+ + e);
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ static boolean isSourceAllowedToInsert(
+ Source source,
+ Uri topOrigin,
+ @EventSurfaceType int publisherType,
+ IMeasurementDao dao) {
+ long windowStartTime = source.getEventTime() - PrivacyParams.RATE_LIMIT_WINDOW_MILLISECONDS;
+ Optional<Uri> publisher = getTopLevelPublisher(topOrigin, publisherType);
+ if (!publisher.isPresent()) {
+ LogUtil.d("insertSources: getTopLevelPublisher failed", topOrigin);
+ return false;
+ }
+ Long numOfSourcesPerPublisher;
+
+ try {
+ numOfSourcesPerPublisher =
+ dao.getNumSourcesPerPublisher(publisher.get(), publisherType);
+ } catch (DatastoreException e) {
+ LogUtil.d("insertSources: getNumSourcesPerPublisher failed", topOrigin);
+ return false;
+ }
+
+ if (numOfSourcesPerPublisher == null) {
+ LogUtil.d("insertSources: getNumSourcesPerPublisher failed", publisher.get());
+ return false;
+ }
+
+ if (numOfSourcesPerPublisher >= SystemHealthParams.MAX_SOURCES_PER_PUBLISHER) {
+ LogUtil.d(
+ "insertSources: Max limit of %s sources for publisher - %s reached.",
+ SystemHealthParams.MAX_SOURCES_PER_PUBLISHER, publisher);
+ return false;
+ }
+ if (source.getAppDestination() != null) {
+ Integer optionalAppDestinationCount =
+ countDistinctDestinationsPerPublisher(
+ publisher.get(),
+ publisherType,
+ source.getEnrollmentId(),
+ source.getAppDestination(),
+ EventSurfaceType.APP,
+ windowStartTime,
+ source.getEventTime(),
+ dao);
+ if (optionalAppDestinationCount != null) {
+ if (optionalAppDestinationCount >= PrivacyParams
+ .getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource()) {
+ return false;
+ }
+ } else {
+ LogUtil.e(
+ "isDestinationWithinPrivacyBounds:"
+ + " dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource"
+ + " not present. %s ::: %s ::: %s ::: %s ::: %s",
+ source.getPublisher(),
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime());
+ return false;
+ }
+ Integer optionalAppEnrollmentsCount =
+ countDistinctEnrollmentsPerPublisher(
+ publisher.get(),
+ publisherType,
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime(),
+ dao);
+ if (optionalAppEnrollmentsCount != null) {
+ if (optionalAppEnrollmentsCount >= PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInSource()) {
+ return false;
+ }
+ } else {
+ LogUtil.e(
+ "isAdTechWithinPrivacyBounds: "
+ + "dao.countDistinctEnrollmentsPerPublisherXDestinationInSource"
+ + " not present"
+ + ". %s ::: %s ::: %s ::: %s ::: $s",
+ source.getPublisher(),
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime());
+ return false;
+ }
+ }
+ if (source.getWebDestination() != null) {
+ Integer optionalDestinationCountWeb =
+ countDistinctDestinationsPerPublisher(
+ publisher.get(),
+ publisherType,
+ source.getEnrollmentId(),
+ source.getWebDestination(),
+ EventSurfaceType.WEB,
+ windowStartTime,
+ source.getEventTime(),
+ dao);
+ if (optionalDestinationCountWeb != null) {
+ if (optionalDestinationCountWeb >= PrivacyParams
+ .getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource()) {
+ return false;
+ }
+ } else {
+ LogUtil.e(
+ "isDestinationWithinPrivacyBounds:"
+ + " dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource"
+ + " not present. %s ::: %s ::: %s ::: %s ::: %s",
+ source.getPublisher(),
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime());
+ return false;
+ }
+ Integer optionalWebEnrollmentsCount =
+ countDistinctEnrollmentsPerPublisher(
+ publisher.get(),
+ publisherType,
+ source.getWebDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime(),
+ dao);
+
+ if (optionalWebEnrollmentsCount != null) {
+ if (optionalWebEnrollmentsCount >= PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInSource()) {
+ return false;
+ }
+ } else {
+ LogUtil.e(
+ "isAdTechWithinPrivacyBounds: "
+ + "dao.countDistinctEnrollmentsPerPublisherXDestinationInSource"
+ + " not present"
+ + ". %s ::: %s ::: %s ::: %s ::: $s",
+ source.getPublisher(),
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static AsyncRegistration createAsyncRegistrationRedirect(
+ String id,
+ String enrollmentId,
+ Uri registrationUri,
+ Uri webDestination,
+ Uri osDestination,
+ Uri registrant,
+ Uri verifiedDestination,
+ Uri topOrigin,
+ AsyncRegistration.RegistrationType registrationType,
+ Source.SourceType sourceType,
+ long requestTime,
+ long retryCount,
+ long lastProcessingTime,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount,
+ boolean debugKeyAllowed) {
+ return new AsyncRegistration.Builder()
+ .setId(id)
+ .setEnrollmentId(enrollmentId)
+ .setRegistrationUri(registrationUri)
+ .setWebDestination(webDestination)
+ .setOsDestination(osDestination)
+ .setRegistrant(registrant)
+ .setVerifiedDestination(verifiedDestination)
+ .setTopOrigin(topOrigin)
+ .setType(registrationType.ordinal())
+ .setSourceType(
+ registrationType == AsyncRegistration.RegistrationType.APP_SOURCE
+ || registrationType
+ == AsyncRegistration.RegistrationType.WEB_SOURCE
+ ? sourceType
+ : null)
+ .setRequestTime(requestTime)
+ .setRetryCount(retryCount)
+ .setLastProcessingTime(lastProcessingTime)
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(debugKeyAllowed)
+ .build();
+ }
+
+ private static void insertAsyncRegistrationFromTransaction(
+ AsyncRegistration asyncRegistration, IMeasurementDao dao) throws DatastoreException {
+ dao.insertAsyncRegistration(asyncRegistration);
+ }
+
+ @VisibleForTesting
+ List<EventReport> generateFakeEventReports(Source source) {
+ List<Source.FakeReport> fakeReports = source.assignAttributionModeAndGenerateFakeReports();
+ return fakeReports.stream()
+ .map(
+ fakeReport ->
+ new EventReport.Builder()
+ .setSourceEventId(source.getEventId())
+ .setReportTime(fakeReport.getReportingTime())
+ .setTriggerData(fakeReport.getTriggerData())
+ .setAttributionDestination(fakeReport.getDestination())
+ .setEnrollmentId(source.getEnrollmentId())
+ // The query for attribution check is from
+ // (triggerTime - 30 days) to triggerTime and max expiry is
+ // 30 days, so it's safe to choose triggerTime as source
+ // event time so that it gets considered when the query is
+ // fired for attribution rate limit check.
+ .setTriggerTime(source.getEventTime())
+ .setTriggerPriority(0L)
+ .setTriggerDedupKey(null)
+ .setSourceType(source.getSourceType())
+ .setStatus(EventReport.Status.PENDING)
+ .setRandomizedTriggerRate(
+ source.getRandomAttributionProbability())
+ .build())
+ .collect(Collectors.toList());
+ }
+
+
+ @VisibleForTesting
+ void insertSourcesFromTransaction(Source source, IMeasurementDao dao)
+ throws DatastoreException {
+ List<EventReport> er = generateFakeEventReports(source);
+ dao.insertSource(source);
+ for (EventReport report : er) {
+ dao.insertEventReport(report);
+ }
+ // We want to account for attribution if fake report generation was considered
+ // based on the probability. In that case the attribution mode will be NEVER
+ // (empty fake reports state) or FALSELY (non-empty fake reports).
+ if (source.getAttributionMode() != Source.AttributionMode.TRUTHFULLY) {
+ // Attribution rate limits for app and web destinations are counted
+ // separately, so add a fake report entry for each type of destination if
+ // non-null.
+ if (!Objects.isNull(source.getAppDestination())) {
+ dao.insertAttribution(
+ createFakeAttributionRateLimit(source, source.getAppDestination()));
+ }
+
+ if (!Objects.isNull(source.getWebDestination())) {
+ dao.insertAttribution(
+ createFakeAttributionRateLimit(source, source.getWebDestination()));
+ }
+ }
+ }
+
+ private void processRedirects(
+ AsyncRegistration asyncRegistration,
+ AsyncRedirect redirectsAndType,
+ Uri webDestination,
+ Uri osDestination,
+ IMeasurementDao dao)
+ throws DatastoreException {
+ for (Uri redirectUri : redirectsAndType.getRedirects()) {
+ Optional<String> enrollmentData =
+ Enrollment.maybeGetEnrollmentId(redirectUri, mEnrollmentDao);
+ if (enrollmentData == null || enrollmentData.isEmpty()) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: Invalid enrollment data while "
+ + "processing redirects");
+ return;
+ }
+ String enrollmentId = enrollmentData.get();
+ insertAsyncRegistrationFromTransaction(
+ createAsyncRegistrationRedirect(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ redirectUri,
+ webDestination,
+ osDestination,
+ asyncRegistration.getRegistrant(),
+ asyncRegistration.getVerifiedDestination(),
+ asyncRegistration.getTopOrigin(),
+ asyncRegistration.getType(),
+ asyncRegistration.getSourceType(),
+ asyncRegistration.getRequestTime(),
+ /* mRetryCount */ 0,
+ System.currentTimeMillis(),
+ redirectsAndType.getRedirectType(),
+ asyncRegistration.getNextRedirectCount(),
+ asyncRegistration.getDebugKeyAllowed()),
+ dao);
+ }
+ }
+
+ /**
+ * {@link Attribution} generated from here will only be used for fake report attribution.
+ *
+ * @param source source to derive parameters from
+ * @param destination destination for attribution
+ * @return a fake {@link Attribution}
+ */
+ private Attribution createFakeAttributionRateLimit(Source source, Uri destination) {
+ Optional<Uri> topLevelPublisher =
+ getTopLevelPublisher(source.getPublisher(), source.getPublisherType());
+
+ if (!topLevelPublisher.isPresent()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "insertAttributionRateLimit: getSourceAndDestinationTopPrivateDomains"
+ + " failed. Publisher: %s; Attribution destination: %s",
+ source.getPublisher(), destination));
+ }
+
+ return new Attribution.Builder()
+ .setSourceSite(topLevelPublisher.get().toString())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setDestinationSite(destination.toString())
+ .setDestinationOrigin(destination.toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setTriggerTime(source.getEventTime())
+ .setRegistrant(source.getRegistrant().toString())
+ .setSourceId(source.getId())
+ // Intentionally kept it as null because it's a fake attribution
+ .setTriggerId(null)
+ .build();
+ }
+
+ private static Optional<Uri> getTopLevelPublisher(
+ Uri topOrigin, @EventSurfaceType int publisherType) {
+ return publisherType == EventSurfaceType.APP
+ ? Optional.of(topOrigin)
+ : Web.topPrivateDomainAndScheme(topOrigin);
+ }
+
+ private Uri getPublisher(AsyncRegistration request) {
+ return request.getRegistrant();
+ }
+
+ private void notifyTriggerContentProvider() {
+ try (ContentProviderClient contentProviderClient =
+ mContentResolver.acquireContentProviderClient(TRIGGER_URI)) {
+ if (contentProviderClient != null) {
+ contentProviderClient.insert(TRIGGER_URI, null);
+ }
+ } catch (RemoteException e) {
+ LogUtil.e(e, "Trigger Content Provider invocation failed.");
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/Attribution.java b/adservices/service-core/java/com/android/adservices/service/measurement/Attribution.java
index 813101a7d..416731ba8 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/Attribution.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/Attribution.java
@@ -34,6 +34,8 @@ public class Attribution {
private final String mEnrollmentId;
private final long mTriggerTime;
private final String mRegistrant;
+ private final String mSourceId;
+ private final String mTriggerId;
private Attribution(Builder builder) {
this.mId = builder.mId;
@@ -44,6 +46,8 @@ public class Attribution {
this.mEnrollmentId = builder.mEnrollmentId;
this.mTriggerTime = builder.mTriggerTime;
this.mRegistrant = builder.mRegistrant;
+ this.mSourceId = builder.mSourceId;
+ this.mTriggerId = builder.mTriggerId;
}
@Override
@@ -58,7 +62,9 @@ public class Attribution {
&& Objects.equals(mDestinationSite, attr.mDestinationSite)
&& Objects.equals(mDestinationOrigin, attr.mDestinationOrigin)
&& Objects.equals(mEnrollmentId, attr.mEnrollmentId)
- && Objects.equals(mRegistrant, attr.mRegistrant);
+ && Objects.equals(mRegistrant, attr.mRegistrant)
+ && Objects.equals(mSourceId, attr.mSourceId)
+ && Objects.equals(mTriggerId, attr.mTriggerId);
}
@Override
@@ -70,7 +76,9 @@ public class Attribution {
mDestinationOrigin,
mEnrollmentId,
mTriggerTime,
- mRegistrant);
+ mRegistrant,
+ mSourceId,
+ mTriggerId);
}
/** @return unique identifier for {@link Attribution} */
@@ -113,6 +121,16 @@ public class Attribution {
return mRegistrant;
}
+ /** @return {@link Source} ID */
+ public String getSourceId() {
+ return mSourceId;
+ }
+
+ /** @return {@link Trigger} ID */
+ public String getTriggerId() {
+ return mTriggerId;
+ }
+
/** Builder for AttributionRateLimit */
public static final class Builder {
private String mId;
@@ -123,6 +141,8 @@ public class Attribution {
private String mEnrollmentId;
private long mTriggerTime;
private String mRegistrant;
+ private String mSourceId;
+ private String mTriggerId;
/** See {@link Attribution#getId()}. */
public Builder setId(String id) {
@@ -172,6 +192,18 @@ public class Attribution {
return this;
}
+ /** See {@link Attribution#getSourceId()}. */
+ public Builder setSourceId(String sourceId) {
+ mSourceId = sourceId;
+ return this;
+ }
+
+ /** See {@link Attribution#getTriggerId()}. */
+ public Builder setTriggerId(String triggerId) {
+ mTriggerId = triggerId;
+ return this;
+ }
+
/** Validate and build the {@link Attribution}. */
public Attribution build() {
Validation.validateNonNull(
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/DeleteExpiredJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/DeleteExpiredJobService.java
index e4170076f..05f81c312 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/DeleteExpiredJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/DeleteExpiredJobService.java
@@ -68,11 +68,14 @@ public final class DeleteExpiredJobService extends JobService {
/** Schedule the job. */
@VisibleForTesting
static void schedule(Context context, JobScheduler jobScheduler) {
- final JobInfo job = new JobInfo.Builder(MEASUREMENT_DELETE_EXPIRED_JOB_ID,
- new ComponentName(context, DeleteExpiredJobService.class))
- .setRequiresDeviceIdle(true)
- .setPeriodic(AdServicesConfig.getMeasurementDeleteExpiredJobPeriodMs())
- .build();
+ final JobInfo job =
+ new JobInfo.Builder(
+ MEASUREMENT_DELETE_EXPIRED_JOB_ID,
+ new ComponentName(context, DeleteExpiredJobService.class))
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(AdServicesConfig.getMeasurementDeleteExpiredJobPeriodMs())
+ .setPersisted(true)
+ .build();
jobScheduler.schedule(job);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/DeleteUninstalledJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/DeleteUninstalledJobService.java
new file mode 100644
index 000000000..175d9de1f
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/DeleteUninstalledJobService.java
@@ -0,0 +1,122 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DELETE_UNINSTALLED_JOB_ID;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.FlagsFactory;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Service for deleting data for uninstalled packages that the package change receiver has missed.
+ */
+public final class DeleteUninstalledJobService extends JobService {
+ private static final Executor sBackgroundExecutor = AdServicesExecutors.getBackgroundExecutor();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (FlagsFactory.getFlags().getMeasurementJobDeleteUninstalledKillSwitch()) {
+ LogUtil.e("DeleteUninstalledJobService is disabled");
+ return skipAndCancelBackgroundJob(params);
+ }
+
+ LogUtil.d("DeleteUninstalledJobService.onStartJob");
+ sBackgroundExecutor.execute(
+ () -> {
+ MeasurementImpl.getInstance(this).deleteAllUninstalledMeasurementData();
+ jobFinished(params, /* wantsReschedule */ false);
+ });
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ LogUtil.d("DeleteUninstalledJobService.onStopJob");
+ return false;
+ }
+
+ /** Schedule the job. */
+ @VisibleForTesting
+ static void schedule(Context context, JobScheduler jobScheduler) {
+ final JobInfo job =
+ new JobInfo.Builder(
+ MEASUREMENT_DELETE_UNINSTALLED_JOB_ID,
+ new ComponentName(context, DeleteUninstalledJobService.class))
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(AdServicesConfig.getMeasurementDeleteExpiredJobPeriodMs())
+ .setPersisted(true)
+ .build();
+ jobScheduler.schedule(job);
+ }
+
+ /**
+ * Schedule Delete Uninstalled Job Service if it is not already scheduled.
+ *
+ * @param context the context
+ * @param forceSchedule flag to indicate whether to force rescheduling the job.
+ */
+ public static void scheduleIfNeeded(Context context, boolean forceSchedule) {
+ if (FlagsFactory.getFlags().getMeasurementJobDeleteUninstalledKillSwitch()) {
+ LogUtil.e("DeleteUninstalledJobService is disabled, skip scheduling");
+ return;
+ }
+
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ LogUtil.e("JobScheduler not found");
+ return;
+ }
+
+ final JobInfo job = jobScheduler.getPendingJob(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID);
+ // Schedule if it hasn't been scheduled already or force rescheduling.
+ if (job == null || forceSchedule) {
+ schedule(context, jobScheduler);
+ LogUtil.d("Scheduled DeleteUninstalledJobService");
+ } else {
+ LogUtil.d("DeleteUninstalledJobService already scheduled, skipping reschedule");
+ }
+ }
+
+ private boolean skipAndCancelBackgroundJob(final JobParameters params) {
+ final JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
+ if (jobScheduler != null) {
+ jobScheduler.cancel(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID);
+ }
+ // Tell the JobScheduler that the job has completed and does not need to be rescheduled.
+ jobFinished(params, /* wantsReschedule */ false);
+
+ // Returning false meas that this job has completed its work.
+ return false;
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java b/adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java
index 69454886b..c409df4ef 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java
@@ -21,6 +21,8 @@ import android.net.Uri;
import androidx.annotation.Nullable;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -31,28 +33,42 @@ import java.util.Objects;
public class EventReport {
private String mId;
- private long mSourceId;
+ private UnsignedLong mSourceEventId;
private long mReportTime;
private long mTriggerTime;
private long mTriggerPriority;
private Uri mAttributionDestination;
private String mEnrollmentId;
- private long mTriggerData;
- private Long mTriggerDedupKey;
+ private UnsignedLong mTriggerData;
+ private UnsignedLong mTriggerDedupKey;
private double mRandomizedTriggerRate;
private @Status int mStatus;
+ private @DebugReportStatus int mDebugReportStatus;
private Source.SourceType mSourceType;
- @Nullable private Long mSourceDebugKey;
- @Nullable private Long mTriggerDebugKey;
+ @Nullable private UnsignedLong mSourceDebugKey;
+ @Nullable private UnsignedLong mTriggerDebugKey;
+ private String mSourceId;
+ private String mTriggerId;
- @IntDef(value = {
- Status.PENDING,
- Status.DELIVERED,
- })
+ @IntDef(value = {Status.PENDING, Status.DELIVERED, Status.MARKED_TO_DELETE})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {
int PENDING = 0;
int DELIVERED = 1;
+ int MARKED_TO_DELETE = 2;
+ }
+
+ @IntDef(
+ value = {
+ DebugReportStatus.NONE,
+ DebugReportStatus.PENDING,
+ DebugReportStatus.DELIVERED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DebugReportStatus {
+ int NONE = 0;
+ int PENDING = 1;
+ int DELIVERED = 2;
}
private EventReport() {
@@ -66,50 +82,52 @@ public class EventReport {
}
EventReport eventReport = (EventReport) obj;
return mStatus == eventReport.mStatus
+ && mDebugReportStatus == eventReport.mDebugReportStatus
&& mReportTime == eventReport.mReportTime
&& Objects.equals(mAttributionDestination, eventReport.mAttributionDestination)
&& Objects.equals(mEnrollmentId, eventReport.mEnrollmentId)
&& mTriggerTime == eventReport.mTriggerTime
- && mTriggerData == eventReport.mTriggerData
- && mSourceId == eventReport.mSourceId
+ && Objects.equals(mTriggerData, eventReport.mTriggerData)
+ && Objects.equals(mSourceEventId, eventReport.mSourceEventId)
&& mTriggerPriority == eventReport.mTriggerPriority
&& Objects.equals(mTriggerDedupKey, eventReport.mTriggerDedupKey)
&& mSourceType == eventReport.mSourceType
&& mRandomizedTriggerRate == eventReport.mRandomizedTriggerRate
&& Objects.equals(mSourceDebugKey, eventReport.mSourceDebugKey)
- && Objects.equals(mTriggerDebugKey, eventReport.mTriggerDebugKey);
+ && Objects.equals(mTriggerDebugKey, eventReport.mTriggerDebugKey)
+ && Objects.equals(mSourceId, eventReport.mSourceId)
+ && Objects.equals(mTriggerId, eventReport.mTriggerId);
}
@Override
public int hashCode() {
return Objects.hash(
mStatus,
+ mDebugReportStatus,
mReportTime,
mAttributionDestination,
mEnrollmentId,
mTriggerTime,
mTriggerData,
- mSourceId,
+ mSourceEventId,
mTriggerPriority,
mTriggerDedupKey,
mSourceType,
mRandomizedTriggerRate,
mSourceDebugKey,
- mTriggerDebugKey);
+ mTriggerDebugKey,
+ mSourceId,
+ mTriggerId);
}
- /**
- * Unique identifier for the report.
- */
+ /** Unique identifier for the report. */
public String getId() {
return mId;
}
- /**
- * Identifier of the associated {@link Source} event.
- */
- public long getSourceId() {
- return mSourceId;
+ /** Identifier of the associated {@link Source} event. */
+ public UnsignedLong getSourceEventId() {
+ return mSourceEventId;
}
/**
@@ -150,14 +168,14 @@ public class EventReport {
/**
* Metadata for the report.
*/
- public long getTriggerData() {
+ public UnsignedLong getTriggerData() {
return mTriggerData;
}
/**
* Deduplication key of the associated {@link Trigger}
*/
- public Long getTriggerDedupKey() {
+ public UnsignedLong getTriggerDedupKey() {
return mTriggerDedupKey;
}
@@ -168,6 +186,11 @@ public class EventReport {
return mStatus;
}
+ /** Current {@link DebugReportStatus} of the report. */
+ public @DebugReportStatus int getDebugReportStatus() {
+ return mDebugReportStatus;
+ }
+
/**
* SourceType of the event's source.
*/
@@ -184,16 +207,26 @@ public class EventReport {
/** Source Debug Key */
@Nullable
- public Long getSourceDebugKey() {
+ public UnsignedLong getSourceDebugKey() {
return mSourceDebugKey;
}
/** Trigger Debug Key */
@Nullable
- public Long getTriggerDebugKey() {
+ public UnsignedLong getTriggerDebugKey() {
return mTriggerDebugKey;
}
+ /** Source ID */
+ public String getSourceId() {
+ return mSourceId;
+ }
+
+ /** Trigger ID */
+ public String getTriggerId() {
+ return mTriggerId;
+ }
+
/** Builder for {@link EventReport} */
public static final class Builder {
@@ -211,11 +244,9 @@ public class EventReport {
return this;
}
- /**
- * See {@link EventReport#getSourceId()}
- */
- public Builder setSourceId(long sourceId) {
- mBuilding.mSourceId = sourceId;
+ /** See {@link EventReport#getSourceEventId()} */
+ public Builder setSourceEventId(UnsignedLong sourceEventId) {
+ mBuilding.mSourceEventId = sourceEventId;
return this;
}
@@ -246,7 +277,7 @@ public class EventReport {
/**
* See {@link EventReport#getTriggerData()}
*/
- public Builder setTriggerData(long triggerData) {
+ public Builder setTriggerData(UnsignedLong triggerData) {
mBuilding.mTriggerData = triggerData;
return this;
}
@@ -262,7 +293,7 @@ public class EventReport {
/**
* See {@link EventReport#getTriggerDedupKey()}
*/
- public Builder setTriggerDedupKey(Long triggerDedupKey) {
+ public Builder setTriggerDedupKey(UnsignedLong triggerDedupKey) {
mBuilding.mTriggerDedupKey = triggerDedupKey;
return this;
}
@@ -283,6 +314,12 @@ public class EventReport {
return this;
}
+ /** See {@link EventReport#getDebugReportStatus()} ()} */
+ public Builder setDebugReportStatus(@DebugReportStatus int debugReportStatus) {
+ mBuilding.mDebugReportStatus = debugReportStatus;
+ return this;
+ }
+
/**
* See {@link EventReport#getSourceType()}
*/
@@ -300,17 +337,29 @@ public class EventReport {
}
/** See {@link EventReport#getSourceDebugKey()} ()} */
- public Builder setSourceDebugKey(Long sourceDebugKey) {
+ public Builder setSourceDebugKey(UnsignedLong sourceDebugKey) {
mBuilding.mSourceDebugKey = sourceDebugKey;
return this;
}
/** See {@link EventReport#getTriggerDebugKey()} ()} */
- public Builder setTriggerDebugKey(Long triggerDebugKey) {
+ public Builder setTriggerDebugKey(UnsignedLong triggerDebugKey) {
mBuilding.mTriggerDebugKey = triggerDebugKey;
return this;
}
+ /** See {@link EventReport#getSourceId()} */
+ public Builder setSourceId(String sourceId) {
+ mBuilding.mSourceId = sourceId;
+ return this;
+ }
+
+ /** See {@link EventReport#getTriggerId()} */
+ public Builder setTriggerId(String triggerId) {
+ mBuilding.mTriggerId = triggerId;
+ return this;
+ }
+
/** Populates fields using {@link Source}, {@link Trigger} and {@link EventTrigger}. */
public Builder populateFromSourceAndTrigger(
Source source, Trigger trigger, EventTrigger eventTrigger) {
@@ -319,23 +368,33 @@ public class EventReport {
// truncate trigger data to 3-bit or 1-bit based on {@link Source.SourceType}
mBuilding.mTriggerData = getTruncatedTriggerData(source, eventTrigger);
mBuilding.mTriggerTime = trigger.getTriggerTime();
- mBuilding.mSourceId = source.getEventId();
+ mBuilding.mSourceEventId = source.getEventId();
mBuilding.mEnrollmentId = source.getEnrollmentId();
mBuilding.mStatus = Status.PENDING;
- mBuilding.mAttributionDestination = trigger.getAttributionDestination();
+ mBuilding.mAttributionDestination = trigger.getAttributionDestinationBaseUri();
mBuilding.mReportTime =
source.getReportingTime(
trigger.getTriggerTime(),
trigger.getDestinationType());
mBuilding.mSourceType = source.getSourceType();
mBuilding.mRandomizedTriggerRate = source.getRandomAttributionProbability();
+ mBuilding.mDebugReportStatus = DebugReportStatus.NONE;
+ if (source.getDebugKey() != null || trigger.getDebugKey() != null) {
+ mBuilding.mDebugReportStatus = DebugReportStatus.PENDING;
+ }
mBuilding.mSourceDebugKey = source.getDebugKey();
mBuilding.mTriggerDebugKey = trigger.getDebugKey();
+ mBuilding.mSourceId = source.getId();
+ mBuilding.mTriggerId = trigger.getId();
return this;
}
- private long getTruncatedTriggerData(Source source, EventTrigger eventTrigger) {
- return eventTrigger.getTriggerData() % source.getTriggerDataCardinality();
+ private UnsignedLong getTruncatedTriggerData(Source source, EventTrigger eventTrigger) {
+ UnsignedLong triggerData = eventTrigger.getTriggerData();
+ if (triggerData == null) {
+ return new UnsignedLong(0L);
+ }
+ return triggerData.mod(source.getTriggerDataCardinality());
}
/**
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/EventTrigger.java b/adservices/service-core/java/com/android/adservices/service/measurement/EventTrigger.java
index 99074922a..91b13854f 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/EventTrigger.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/EventTrigger.java
@@ -17,18 +17,18 @@
package com.android.adservices.service.measurement;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionTrigger;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import java.util.Objects;
import java.util.Optional;
/** Event trigger containing trigger data, priority, de-deup key and filters info. */
public class EventTrigger {
- private Long mTriggerData;
+ private UnsignedLong mTriggerData;
private long mTriggerPriority;
- private Long mDedupKey;
- private Optional<AggregateFilterData> mFilter;
- private Optional<AggregateFilterData> mNotFilter;
+ private UnsignedLong mDedupKey;
+ private Optional<FilterData> mFilter;
+ private Optional<FilterData> mNotFilter;
private EventTrigger() {
mFilter = Optional.empty();
@@ -54,7 +54,7 @@ public class EventTrigger {
}
/** Returns trigger_data for the event. */
- public Long getTriggerData() {
+ public UnsignedLong getTriggerData() {
return mTriggerData;
}
@@ -64,17 +64,17 @@ public class EventTrigger {
}
/** De-deuplication key.. */
- public Long getDedupKey() {
+ public UnsignedLong getDedupKey() {
return mDedupKey;
}
/** Filters that should match with source's. */
- public Optional<AggregateFilterData> getFilterData() {
+ public Optional<FilterData> getFilterData() {
return mFilter;
}
/** Filters that should not match with source's. */
- public Optional<AggregateFilterData> getNotFilterData() {
+ public Optional<FilterData> getNotFilterData() {
return mNotFilter;
}
@@ -87,7 +87,7 @@ public class EventTrigger {
}
/** See {@link EventTrigger#getTriggerData()}. */
- public EventTrigger.Builder setTriggerData(Long triggerData) {
+ public EventTrigger.Builder setTriggerData(UnsignedLong triggerData) {
mBuilding.mTriggerData = triggerData;
return this;
}
@@ -99,19 +99,19 @@ public class EventTrigger {
}
/** See {@link EventTrigger#getDedupKey()}. */
- public EventTrigger.Builder setDedupKey(Long dedupKey) {
+ public EventTrigger.Builder setDedupKey(UnsignedLong dedupKey) {
mBuilding.mDedupKey = dedupKey;
return this;
}
/** See {@link EventTrigger#getFilterData()}. */
- public EventTrigger.Builder setFilter(AggregateFilterData filterData) {
+ public EventTrigger.Builder setFilter(FilterData filterData) {
mBuilding.mFilter = Optional.ofNullable(filterData);
return this;
}
/** See {@link EventTrigger#getNotFilterData()} ()}. */
- public EventTrigger.Builder setNotFilter(AggregateFilterData notFilterData) {
+ public EventTrigger.Builder setNotFilter(FilterData notFilterData) {
mBuilding.mNotFilter = Optional.ofNullable(notFilterData);
return this;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateFilterData.java b/adservices/service-core/java/com/android/adservices/service/measurement/FilterData.java
index 5f8e91bd9..64a9ea8e9 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateFilterData.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/FilterData.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.adservices.service.measurement.aggregation;
+package com.android.adservices.service.measurement;
import org.json.JSONArray;
import org.json.JSONException;
@@ -30,20 +30,20 @@ import java.util.Objects;
/**
* POJO for AggregatableAttributionFilterData.
*/
-public class AggregateFilterData {
+public class FilterData {
private Map<String, List<String>> mAttributionFilterMap;
- AggregateFilterData() {
+ FilterData() {
mAttributionFilterMap = new HashMap<>();
}
@Override
public boolean equals(Object obj) {
- if (!(obj instanceof AggregateFilterData)) {
+ if (!(obj instanceof FilterData)) {
return false;
}
- AggregateFilterData attributionFilterData = (AggregateFilterData) obj;
+ FilterData attributionFilterData = (FilterData) obj;
return Objects.equals(mAttributionFilterMap, attributionFilterData.mAttributionFilterMap);
}
@@ -60,17 +60,17 @@ public class AggregateFilterData {
}
/**
- * Builder for {@link AggregateFilterData}.
+ * Builder for {@link FilterData}.
*/
public static final class Builder {
- private final AggregateFilterData mBuilding;
+ private final FilterData mBuilding;
public Builder() {
- mBuilding = new AggregateFilterData();
+ mBuilding = new FilterData();
}
/**
- * See {@link AggregateFilterData#getAttributionFilterMap()}.
+ * See {@link FilterData#getAttributionFilterMap()}.
*/
public Builder setAttributionFilterMap(Map<String, List<String>> attributionFilterMap) {
mBuilding.mAttributionFilterMap = attributionFilterMap;
@@ -78,27 +78,27 @@ public class AggregateFilterData {
}
/**
- * Builds AggregateFilterData from JSONObject.
+ * Builds FilterData from JSONObject.
*/
- public Builder buildAggregateFilterData(JSONObject jsonObject)
+ public Builder buildFilterData(JSONObject jsonObject)
throws JSONException {
- Map<String, List<String>> aggregateFilterData = new HashMap<>();
+ Map<String, List<String>> filterData = new HashMap<>();
for (String key : jsonObject.keySet()) {
JSONArray jsonArray = jsonObject.getJSONArray(key);
List<String> filterDataList = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
filterDataList.add(jsonArray.getString(i));
}
- aggregateFilterData.put(key, filterDataList);
+ filterData.put(key, filterDataList);
}
- mBuilding.mAttributionFilterMap = aggregateFilterData;
+ mBuilding.mAttributionFilterMap = filterData;
return this;
}
/**
- * Build the {@link AggregateFilterData}.
+ * Build the {@link FilterData}.
*/
- public AggregateFilterData build() {
+ public FilterData build() {
return mBuilding;
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/MeasurementHttpClient.java b/adservices/service-core/java/com/android/adservices/service/measurement/MeasurementHttpClient.java
index a67a69327..8f05c536d 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/MeasurementHttpClient.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/MeasurementHttpClient.java
@@ -64,6 +64,9 @@ public class MeasurementHttpClient {
urlConnection.setConnectTimeout(flags.getMeasurementNetworkConnectTimeoutMs());
urlConnection.setReadTimeout(flags.getMeasurementNetworkReadTimeoutMs());
+ // Overriding default headers to avoid leaking information
+ urlConnection.setRequestProperty("User-Agent", "");
+
return urlConnection;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/MeasurementImpl.java b/adservices/service-core/java/com/android/adservices/service/measurement/MeasurementImpl.java
index cea6c770f..2733a8195 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/MeasurementImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/MeasurementImpl.java
@@ -21,8 +21,6 @@ import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARG
import static android.adservices.common.AdServicesStatusUtils.STATUS_IO_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
-import static com.android.adservices.service.measurement.attribution.TriggerContentProvider.TRIGGER_URI;
-import static com.android.adservices.service.measurement.util.BaseUriExtractor.getBaseUri;
import android.adservices.common.AdServicesStatusUtils;
import android.adservices.measurement.DeletionParam;
@@ -33,38 +31,33 @@ import android.adservices.measurement.WebSourceRegistrationRequestInternal;
import android.adservices.measurement.WebTriggerRegistrationRequest;
import android.adservices.measurement.WebTriggerRegistrationRequestInternal;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.content.ComponentName;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.net.Uri;
-import android.os.RemoteException;
import android.view.InputEvent;
import com.android.adservices.LogUtil;
+import com.android.adservices.data.DbHelper;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.consent.AdServicesApiConsent;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.measurement.inputverification.ClickVerifier;
-import com.android.adservices.service.measurement.registration.SourceFetcher;
-import com.android.adservices.service.measurement.registration.SourceRegistration;
-import com.android.adservices.service.measurement.registration.TriggerFetcher;
-import com.android.adservices.service.measurement.registration.TriggerRegistration;
-import com.android.adservices.service.measurement.util.BaseUriExtractor;
+import com.android.adservices.service.measurement.registration.EnqueueAsyncRegistration;
import com.android.adservices.service.measurement.util.Web;
import com.android.internal.annotations.VisibleForTesting;
import java.net.URISyntaxException;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
@@ -84,37 +77,34 @@ public final class MeasurementImpl {
private final Context mContext;
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
private final DatastoreManager mDatastoreManager;
- private final SourceFetcher mSourceFetcher;
- private final TriggerFetcher mTriggerFetcher;
- private final ContentResolver mContentResolver;
private final ClickVerifier mClickVerifier;
+ private final MeasurementDataDeleter mMeasurementDataDeleter;
private final Flags mFlags;
+ private EnrollmentDao mEnrollmentDao;
- private MeasurementImpl(Context context) {
+ @VisibleForTesting
+ MeasurementImpl(Context context) {
mContext = context;
- mContentResolver = context.getContentResolver();
mDatastoreManager = DatastoreManagerFactory.getDatastoreManager(context);
- mSourceFetcher = new SourceFetcher(context);
- mTriggerFetcher = new TriggerFetcher(context);
mClickVerifier = new ClickVerifier(context);
mFlags = FlagsFactory.getFlags();
+ mMeasurementDataDeleter = new MeasurementDataDeleter(mDatastoreManager);
+ mEnrollmentDao = new EnrollmentDao(context, DbHelper.getInstance(mContext));
}
@VisibleForTesting
MeasurementImpl(
Context context,
- ContentResolver contentResolver,
DatastoreManager datastoreManager,
- SourceFetcher sourceFetcher,
- TriggerFetcher triggerFetcher,
- ClickVerifier clickVerifier) {
+ ClickVerifier clickVerifier,
+ MeasurementDataDeleter measurementDataDeleter,
+ EnrollmentDao enrollmentDao) {
mContext = context;
- mContentResolver = contentResolver;
mDatastoreManager = datastoreManager;
- mSourceFetcher = sourceFetcher;
- mTriggerFetcher = triggerFetcher;
mClickVerifier = clickVerifier;
+ mMeasurementDataDeleter = measurementDataDeleter;
mFlags = FlagsFactory.getFlagsForTest();
+ mEnrollmentDao = enrollmentDao;
}
/**
@@ -160,10 +150,15 @@ public final class MeasurementImpl {
try {
switch (request.getRegistrationType()) {
case RegistrationRequest.REGISTER_SOURCE:
- return fetchAndInsertSources(request, requestTime);
-
case RegistrationRequest.REGISTER_TRIGGER:
- return fetchAndInsertTriggers(request, requestTime);
+ return EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ request,
+ getRegistrant(request.getPackageName()),
+ requestTime,
+ mEnrollmentDao,
+ mDatastoreManager)
+ ? STATUS_SUCCESS
+ : STATUS_IO_ERROR;
default:
return STATUS_INVALID_ARGUMENT;
@@ -186,22 +181,17 @@ public final class MeasurementImpl {
}
mReadWriteLock.readLock().lock();
try {
- Optional<List<SourceRegistration>> fetch =
- mSourceFetcher.fetchWebSources(
- sourceRegistrationRequest, request.isAdIdPermissionGranted());
- LogUtil.d("MeasurementImpl: registerWebSource: success=" + fetch.isPresent());
- if (fetch.isPresent()) {
- insertSources(
- fetch.get(),
- requestTime,
- sourceRegistrationRequest.getTopOriginUri(),
- EventSurfaceType.WEB,
- getRegistrant(request.getPackageName()),
- getSourceType(
- sourceRegistrationRequest.getInputEvent(),
- request.getRequestTime()));
+ boolean enqueueStatus =
+ EnqueueAsyncRegistration.webSourceRegistrationRequest(
+ sourceRegistrationRequest,
+ getRegistrant(request.getPackageName()),
+ requestTime,
+ mEnrollmentDao,
+ mDatastoreManager);
+ if (enqueueStatus) {
return STATUS_SUCCESS;
} else {
+
return STATUS_IO_ERROR;
}
} finally {
@@ -223,19 +213,17 @@ public final class MeasurementImpl {
}
mReadWriteLock.readLock().lock();
try {
- Optional<List<TriggerRegistration>> fetch =
- mTriggerFetcher.fetchWebTriggers(
- triggerRegistrationRequest, request.isAdIdPermissionGranted());
- LogUtil.d("MeasurementImpl: registerWebTrigger: success=" + fetch.isPresent());
- if (fetch.isPresent()) {
- insertTriggers(
- fetch.get(),
- requestTime,
- triggerRegistrationRequest.getDestination(),
- getRegistrant(request.getPackageName()),
- EventSurfaceType.WEB);
+ boolean enqueueStatus =
+ EnqueueAsyncRegistration.webTriggerRegistrationRequest(
+ triggerRegistrationRequest,
+ getRegistrant(request.getPackageName()),
+ requestTime,
+ mEnrollmentDao,
+ mDatastoreManager);
+ if (enqueueStatus) {
return STATUS_SUCCESS;
} else {
+
return STATUS_IO_ERROR;
}
} finally {
@@ -251,17 +239,7 @@ public final class MeasurementImpl {
int deleteRegistrations(@NonNull DeletionParam request) {
mReadWriteLock.readLock().lock();
try {
- final boolean deleteResult =
- mDatastoreManager.runInTransaction(
- (dao) ->
- dao.deleteMeasurementData(
- getRegistrant(request.getPackageName()),
- request.getStart(),
- request.getEnd(),
- request.getOriginUris(),
- request.getDomainUris(),
- request.getMatchBehavior(),
- request.getDeletionMode()));
+ boolean deleteResult = mMeasurementDataDeleter.delete(request);
return deleteResult ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR;
} catch (NullPointerException | IllegalArgumentException e) {
LogUtil.e(e, "Delete registration received invalid parameters");
@@ -275,8 +253,7 @@ public final class MeasurementImpl {
* Implement a getMeasurementApiStatus request, returning a result code.
*/
@MeasurementManager.MeasurementApiState int getMeasurementApiStatus() {
- AdServicesApiConsent consent =
- ConsentManager.getInstance(mContext).getConsent(mContext.getPackageManager());
+ AdServicesApiConsent consent = ConsentManager.getInstance(mContext).getConsent();
if (consent.isGiven()) {
return MeasurementManager.MEASUREMENT_API_STATE_ENABLED;
} else {
@@ -321,187 +298,25 @@ public final class MeasurementImpl {
}
}
- private int fetchAndInsertTriggers(RegistrationRequest request, long requestTime) {
- Optional<List<TriggerRegistration>> fetch = mTriggerFetcher.fetchTrigger(request);
- LogUtil.d("MeasurementImpl: register: success=" + fetch.isPresent());
- if (fetch.isPresent()) {
- insertTriggers(
- fetch.get(),
- requestTime,
- request.getTopOriginUri(),
- getRegistrant(request.getPackageName()),
- EventSurfaceType.APP);
- return STATUS_SUCCESS;
- } else {
- return STATUS_IO_ERROR;
- }
- }
-
- private int fetchAndInsertSources(RegistrationRequest request, long requestTime) {
- Optional<List<SourceRegistration>> fetch = mSourceFetcher.fetchSource(request);
- LogUtil.d("MeasurementImpl: register: success=" + fetch.isPresent());
- if (fetch.isPresent()) {
- insertSources(
- fetch.get(),
- requestTime,
- request.getTopOriginUri(),
- EventSurfaceType.APP,
- getRegistrant(request.getPackageName()),
- getSourceType(request.getInputEvent(), request.getRequestTime()));
- return STATUS_SUCCESS;
- } else {
- return STATUS_IO_ERROR;
- }
- }
-
- private void insertSources(
- List<SourceRegistration> sourceRegistrations,
- long sourceEventTime,
- Uri topOriginUri,
- @EventSurfaceType int publisherType,
- Uri registrant,
- Source.SourceType sourceType) {
- Optional<Uri> publisher = getTopLevelPublisher(topOriginUri, publisherType);
- if (!publisher.isPresent()) {
- LogUtil.d("insertSources: getTopLevelPublisher failed", topOriginUri);
- return;
- }
- // Only first destination to avoid AdTechs change this
- Uri appDestination = sourceRegistrations.get(0).getAppDestination();
- Uri webDestination = sourceRegistrations.get(0).getWebDestination();
- for (SourceRegistration registration : sourceRegistrations) {
- if (!isDestinationWithinPrivacyBounds(
- publisher.get(),
- publisherType,
- registration.getEnrollmentId(),
- sourceEventTime,
- appDestination,
- webDestination)) {
- LogUtil.d("insertSources: destination exceeds privacy bound. %s %s %s %s",
- appDestination, webDestination, publisher.get(),
- registration.getEnrollmentId());
- continue;
- }
- if (!isAdTechWithinPrivacyBounds(
- publisher.get(),
- publisherType,
- sourceEventTime,
- appDestination,
- webDestination,
- registration.getEnrollmentId())) {
- LogUtil.d("insertSources: ad-tech exceeds privacy bound. %s %s %s %s",
- registration.getEnrollmentId(), publisher.get(), appDestination,
- webDestination);
- continue;
- }
- Source source =
- createSource(
- sourceEventTime,
- registration,
- registration.getEnrollmentId(),
- topOriginUri,
- publisherType,
- registrant,
- sourceType,
- appDestination,
- webDestination);
- insertSource(source);
+ /** Delete all data generated from apps that are not currently installed. */
+ public void deleteAllUninstalledMeasurementData() {
+ List<Uri> installedApplicationsList = getCurrentInstalledApplicationsList(mContext);
+ mReadWriteLock.writeLock().lock();
+ try {
+ mDatastoreManager.runInTransaction(
+ (dao) -> dao.deleteAppRecordsNotPresent(installedApplicationsList));
+ } finally {
+ mReadWriteLock.writeLock().unlock();
}
}
- private Source createSource(
- long sourceEventTime,
- SourceRegistration registration,
- String enrollmentId,
- Uri topOriginUri,
- @EventSurfaceType int publisherType,
- Uri registrant,
- Source.SourceType sourceType,
- Uri destination,
- Uri webDestination) {
- return new Source.Builder()
- .setEventId(registration.getSourceEventId())
- .setPublisher(getBaseUri(topOriginUri))
- .setPublisherType(publisherType)
- .setAppDestination(destination)
- .setWebDestination(webDestination)
- .setEnrollmentId(enrollmentId)
- .setRegistrant(registrant)
- .setSourceType(sourceType)
- .setPriority(registration.getSourcePriority())
- .setEventTime(sourceEventTime)
- .setExpiryTime(
- sourceEventTime + TimeUnit.SECONDS.toMillis(registration.getExpiry()))
- .setInstallAttributionWindow(
- TimeUnit.SECONDS.toMillis(registration.getInstallAttributionWindow()))
- .setInstallCooldownWindow(
- TimeUnit.SECONDS.toMillis(registration.getInstallCooldownWindow()))
- // Setting as TRUTHFULLY as default value for tests.
- // This will be overwritten by getSourceEventReports.
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateSource(registration.getAggregateSource())
- .setAggregateFilterData(registration.getAggregateFilterData())
- .setDebugKey(registration.getDebugKey())
- .build();
- }
-
- @VisibleForTesting
- void insertSource(Source source) {
- List<EventReport> fakeEventReports = generateFakeEventReports(source);
- mDatastoreManager.runInTransaction(
- (dao) -> {
- dao.insertSource(source);
- for (EventReport report : fakeEventReports) {
- dao.insertEventReport(report);
- }
-
- // We want to account for attribution if fake report generation was considered
- // based on the probability. In that case the attribution mode will be NEVER
- // (empty fake reports state) or FALSELY (non-empty fake reports).
- if (source.getAttributionMode() != Source.AttributionMode.TRUTHFULLY) {
- // Attribution rate limits for app and web destinations are counted
- // separately, so add a fake report entry for each type of destination if
- // non-null.
- if (!Objects.isNull(source.getAppDestination())) {
- dao.insertAttribution(
- createFakeAttributionRateLimit(
- source, source.getAppDestination()));
- }
-
- if (!Objects.isNull(source.getWebDestination())) {
- dao.insertAttribution(
- createFakeAttributionRateLimit(
- source, source.getWebDestination()));
- }
- }
- });
- }
-
- @VisibleForTesting
- List<EventReport> generateFakeEventReports(Source source) {
- List<Source.FakeReport> fakeReports = source.assignAttributionModeAndGenerateFakeReports();
- return fakeReports.stream()
- .map(
- fakeReport ->
- new EventReport.Builder()
- .setSourceId(source.getEventId())
- .setReportTime(fakeReport.getReportingTime())
- .setTriggerData(fakeReport.getTriggerData())
- .setAttributionDestination(fakeReport.getDestination())
- .setEnrollmentId(source.getEnrollmentId())
- // The query for attribution check is from
- // (triggerTime - 30 days) to triggerTime and max expiry is
- // 30 days, so it's safe to choose triggerTime as source
- // event time so that it gets considered when the query is
- // fired for attribution rate limit check.
- .setTriggerTime(source.getEventTime())
- .setTriggerPriority(0L)
- .setTriggerDedupKey(null)
- .setSourceType(source.getSourceType())
- .setStatus(EventReport.Status.PENDING)
- .setRandomizedTriggerRate(
- source.getRandomAttributionProbability())
- .build())
+ private List<Uri> getCurrentInstalledApplicationsList(Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ List<ApplicationInfo> applicationInfoList =
+ packageManager.getInstalledApplications(
+ PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA));
+ return applicationInfoList.stream()
+ .map(applicationInfo -> Uri.parse("android-app://" + applicationInfo.packageName))
.collect(Collectors.toList());
}
@@ -518,57 +333,6 @@ public final class MeasurementImpl {
}
}
- private void insertTriggers(
- List<TriggerRegistration> responseBasedRegistrations,
- long triggerTime,
- Uri topOrigin,
- Uri registrant,
- @EventSurfaceType int destinationType) {
- for (TriggerRegistration registration : responseBasedRegistrations) {
- Trigger trigger = createTrigger(
- registration,
- registration.getEnrollmentId(),
- triggerTime,
- topOrigin,
- registrant,
- destinationType);
- mDatastoreManager.runInTransaction((dao) -> dao.insertTrigger(trigger));
- }
- notifyTriggerContentProvider();
- }
-
- private void notifyTriggerContentProvider() {
- try (ContentProviderClient contentProviderClient =
- mContentResolver.acquireContentProviderClient(TRIGGER_URI)) {
- if (contentProviderClient != null) {
- contentProviderClient.insert(TRIGGER_URI, null);
- }
- } catch (RemoteException e) {
- LogUtil.e(e, "Trigger Content Provider invocation failed.");
- }
- }
-
- private Trigger createTrigger(
- TriggerRegistration registration,
- String enrollmentId,
- long triggerTime,
- Uri topOrigin,
- Uri registrant,
- @EventSurfaceType int destinationType) {
- return new Trigger.Builder()
- .setAttributionDestination(topOrigin)
- .setDestinationType(destinationType)
- .setEnrollmentId(enrollmentId)
- .setRegistrant(registrant)
- .setTriggerTime(triggerTime)
- .setEventTriggers(registration.getEventTriggers())
- .setAggregateTriggerData(registration.getAggregateTriggerData())
- .setAggregateValues(registration.getAggregateValues())
- .setFilters(registration.getFilters())
- .setDebugKey(registration.getDebugKey())
- .build();
- }
-
private Uri getRegistrant(String packageName) {
return Uri.parse(ANDROID_APP_SCHEME + "://" + packageName);
}
@@ -645,38 +409,6 @@ public final class MeasurementImpl {
}
}
- /**
- * {@link Attribution} generated from here will only be used for fake report attribution.
- *
- * @param source source to derive parameters from
- * @param destination destination for attribution
- * @return a fake {@link Attribution}
- */
- private Attribution createFakeAttributionRateLimit(Source source, Uri destination) {
- Optional<Uri> publisherBaseUri = extractBaseUri(source.getPublisher());
- Optional<Uri> destinationBaseUri = extractBaseUri(destination);
-
- if (!publisherBaseUri.isPresent() || !destinationBaseUri.isPresent()) {
- throw new IllegalArgumentException(
- String.format(
- "insertAttributionRateLimit: getSourceAndDestinationTopPrivateDomains"
- + " failed. Publisher: %s; Attribution destination: %s",
- source.getPublisher(), destination));
- }
-
- String publisherTopPrivateDomain = publisherBaseUri.get().toString();
- String triggerDestinationTopPrivateDomain = destinationBaseUri.get().toString();
- return new Attribution.Builder()
- .setSourceSite(publisherTopPrivateDomain)
- .setSourceOrigin(BaseUriExtractor.getBaseUri(source.getPublisher()).toString())
- .setDestinationSite(triggerDestinationTopPrivateDomain)
- .setDestinationOrigin(BaseUriExtractor.getBaseUri(destination).toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setTriggerTime(source.getEventTime())
- .setRegistrant(source.getRegistrant().toString())
- .build();
- }
-
private static boolean isValid(WebTriggerRegistrationRequest triggerRegistrationRequest) {
Uri destination = triggerRegistrationRequest.getDestination();
return Web.topPrivateDomainAndScheme(destination).isPresent();
@@ -686,145 +418,7 @@ public final class MeasurementImpl {
return uri.getQueryParameter("id");
}
- private static Optional<Uri> extractBaseUri(Uri uri) {
- return hasAndroidAppScheme(uri)
- ? Optional.of(BaseUriExtractor.getBaseUri(uri))
- : Web.topPrivateDomainAndScheme(uri);
- }
-
- private static boolean hasAndroidAppScheme(Uri uri) {
- String scheme = uri.getScheme();
- return scheme != null && scheme.equals(ANDROID_APP_SCHEME);
- }
-
private interface AppVendorPackages {
String PLAY_STORE = "com.android.vending";
}
-
- private boolean isDestinationWithinPrivacyBounds(
- Uri publisher,
- @EventSurfaceType int publisherType,
- String enrollmentId,
- long requestTime,
- @Nullable Uri appDestination,
- @Nullable Uri webDestination) {
- long windowStartTime = requestTime - PrivacyParams.RATE_LIMIT_WINDOW_MILLISECONDS;
- if (appDestination != null && !isDestinationWithinPrivacyBounds(
- publisher,
- publisherType,
- enrollmentId,
- appDestination,
- EventSurfaceType.APP,
- windowStartTime,
- requestTime)) {
- return false;
- }
- if (webDestination != null && !isDestinationWithinPrivacyBounds(
- publisher,
- publisherType,
- enrollmentId,
- webDestination,
- EventSurfaceType.WEB,
- windowStartTime,
- requestTime)) {
- return false;
- }
- return true;
- }
-
- private boolean isDestinationWithinPrivacyBounds(
- Uri publisher,
- @EventSurfaceType int publisherType,
- String enrollmentId,
- Uri destination,
- @EventSurfaceType int destinationType,
- long windowStartTime,
- long requestTime) {
- Optional<Integer> destinationCount =
- mDatastoreManager.runInTransactionWithResult((dao) ->
- dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- publisher,
- publisherType,
- enrollmentId,
- destination,
- destinationType,
- windowStartTime,
- requestTime));
-
- if (destinationCount.isPresent()) {
- return destinationCount.get() < PrivacyParams
- .MAX_DISTINCT_DESTINATIONS_PER_PUBLISHER_X_ENROLLMENT_IN_ACTIVE_SOURCE;
- } else {
- LogUtil.e("isDestinationWithinPrivacyBounds: "
- + "dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource not "
- + "present. %s ::: %s ::: %s ::: %s ::: %s", publisher, enrollmentId,
- destination, windowStartTime, requestTime);
- return false;
- }
- }
-
- private boolean isAdTechWithinPrivacyBounds(
- Uri publisher,
- @EventSurfaceType int publisherType,
- long requestTime,
- @Nullable Uri appDestination,
- @Nullable Uri webDestination,
- String enrollmentId) {
- long windowStartTime = requestTime - PrivacyParams.RATE_LIMIT_WINDOW_MILLISECONDS;
- if (appDestination != null && !isAdTechWithinPrivacyBounds(
- publisher,
- publisherType,
- appDestination,
- enrollmentId,
- windowStartTime,
- requestTime)) {
- return false;
- }
- if (webDestination != null && !isAdTechWithinPrivacyBounds(
- publisher,
- publisherType,
- webDestination,
- enrollmentId,
- windowStartTime,
- requestTime)) {
- return false;
- }
- return true;
- }
-
- private boolean isAdTechWithinPrivacyBounds(
- Uri publisher,
- @EventSurfaceType int publisherType,
- Uri destination,
- String enrollmentId,
- long windowStartTime,
- long requestTime) {
- Optional<Integer> adTechCount =
- mDatastoreManager.runInTransactionWithResult((dao) ->
- dao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- publisher,
- publisherType,
- destination,
- enrollmentId,
- windowStartTime,
- requestTime));
-
- if (adTechCount.isPresent()) {
- return adTechCount.get() < PrivacyParams
- .MAX_DISTINCT_ENROLLMENTS_PER_PUBLISHER_X_DESTINATION_IN_SOURCE;
- } else {
- LogUtil.e("isAdTechWithinPrivacyBounds: "
- + "dao.countDistinctEnrollmentsPerPublisherXDestinationInSource not present"
- + ". %s ::: %s ::: %s ::: %s ::: $s", publisher, destination, enrollmentId,
- windowStartTime, requestTime);
- return false;
- }
- }
-
- private static Optional<Uri> getTopLevelPublisher(Uri topOrigin,
- @EventSurfaceType int publisherType) {
- return publisherType == EventSurfaceType.APP
- ? Optional.of(topOrigin)
- : Web.topPrivateDomainAndScheme(topOrigin);
- }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/PrivacyParams.java b/adservices/service-core/java/com/android/adservices/service/measurement/PrivacyParams.java
index dd512e5c5..f0e1eb549 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/PrivacyParams.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/PrivacyParams.java
@@ -48,11 +48,15 @@ public final class PrivacyParams {
* Maximum attributions per rate limit window.
* Rate limit unit: (Source Site, Destination Site, Reporting Site, Window).
*/
- public static final int MAX_ATTRIBUTION_PER_RATE_LIMIT_WINDOW = 100;
+ private static final int MAX_ATTRIBUTION_PER_RATE_LIMIT_WINDOW = 100;
+
+ public static int getMaxAttributionPerRateLimitWindow() {
+ return MAX_ATTRIBUTION_PER_RATE_LIMIT_WINDOW;
+ }
/**
* Rate limit window for (Source Site, Destination Site, Reporting Site, Window) privacy unit.
- * 28 days.
+ * 30 days.
*/
public static final long RATE_LIMIT_WINDOW_MILLISECONDS = TimeUnit.DAYS.toMillis(30);
@@ -133,11 +137,15 @@ public final class PrivacyParams {
/**
* Trigger data cardinality for 'Navigation' {@link Source} attribution.
*/
- public static final int NAVIGATION_TRIGGER_DATA_CARDINALITY = 8;
+ private static final int NAVIGATION_TRIGGER_DATA_CARDINALITY = 8;
+
+ public static int getNavigationTriggerDataCardinality() {
+ return NAVIGATION_TRIGGER_DATA_CARDINALITY;
+ }
/** Min expiration value in seconds for attribution reporting register source. */
public static final long MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS =
- TimeUnit.DAYS.toSeconds(2);
+ TimeUnit.DAYS.toSeconds(1);
/**
* Max expiration value in seconds for attribution reporting register source. This value is also
@@ -149,7 +157,7 @@ public final class PrivacyParams {
/**
* Minimum limit of duration to determine attribution for a verified installation.
*/
- public static final long MIN_INSTALL_ATTRIBUTION_WINDOW = TimeUnit.DAYS.toSeconds(2);
+ public static final long MIN_INSTALL_ATTRIBUTION_WINDOW = TimeUnit.DAYS.toSeconds(1);
/**
* Maximum limit of duration to determine attribution for a verified installation.
@@ -178,24 +186,42 @@ public final class PrivacyParams {
/** Amount of bytes allocated for aggregate histogram value */
public static final int AGGREGATE_HISTOGRAM_VALUE_BYTE_SIZE = 4;
+ /** Minimum time an aggregate report is delayed after trigger */
+ public static final long AGGREGATE_MIN_REPORT_DELAY = TimeUnit.MINUTES.toMillis(10L);
+
+ /** Maximum time an aggregate report is delayed after trigger */
+ public static final long AGGREGATE_MAX_REPORT_DELAY = TimeUnit.MINUTES.toMillis(60L);
+
/**
* Max distinct enrollments for attribution per { Advertiser X Publisher X TimePeriod }.
*/
- public static final int MAX_DISTINCT_ENROLLMENTS_PER_PUBLISHER_X_DESTINATION_IN_ATTRIBUTION =
+ private static final int MAX_DISTINCT_ENROLLMENTS_PER_PUBLISHER_X_DESTINATION_IN_ATTRIBUTION =
10;
+ public static int getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution() {
+ return MAX_DISTINCT_ENROLLMENTS_PER_PUBLISHER_X_DESTINATION_IN_ATTRIBUTION;
+ }
+
/**
* Max distinct advertisers with pending impressions per
* { Publisher X Enrollment X TimePeriod }.
*/
- public static final int MAX_DISTINCT_DESTINATIONS_PER_PUBLISHER_X_ENROLLMENT_IN_ACTIVE_SOURCE =
+ private static final int MAX_DISTINCT_DESTINATIONS_PER_PUBLISHER_X_ENROLLMENT_IN_ACTIVE_SOURCE =
100;
+ public static int getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource() {
+ return MAX_DISTINCT_DESTINATIONS_PER_PUBLISHER_X_ENROLLMENT_IN_ACTIVE_SOURCE;
+ }
+
/**
* Max distinct enrollments with source registration per
* { Publisher X Advertiser X TimePeriod }.
*/
- public static final int MAX_DISTINCT_ENROLLMENTS_PER_PUBLISHER_X_DESTINATION_IN_SOURCE = 100;
+ private static final int MAX_DISTINCT_ENROLLMENTS_PER_PUBLISHER_X_DESTINATION_IN_SOURCE = 100;
+
+ public static int getMaxDistinctEnrollmentsPerPublisherXDestinationInSource() {
+ return MAX_DISTINCT_ENROLLMENTS_PER_PUBLISHER_X_DESTINATION_IN_SOURCE;
+ }
private PrivacyParams() {
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/Source.java b/adservices/service-core/java/com/android/adservices/service/measurement/Source.java
index 343149a8a..a36a12298 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/Source.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/Source.java
@@ -22,15 +22,14 @@ import android.annotation.Nullable;
import android.net.Uri;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionSource;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
import com.android.adservices.service.measurement.noising.ImpressionNoiseParams;
import com.android.adservices.service.measurement.noising.ImpressionNoiseUtil;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import com.android.adservices.service.measurement.util.Validation;
import com.android.internal.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
-import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -66,7 +65,7 @@ public class Source {
public static final int DUAL_DESTINATION_IMPRESSION_NOISE_MULTIPLIER = 2;
private String mId;
- private long mEventId;
+ private UnsignedLong mEventId;
private Uri mPublisher;
@EventSurfaceType private int mPublisherType;
private Uri mAppDestination;
@@ -78,25 +77,23 @@ public class Source {
@Status private int mStatus;
private long mEventTime;
private long mExpiryTime;
- private List<Long> mDedupKeys;
+ private List<UnsignedLong> mDedupKeys;
@AttributionMode private int mAttributionMode;
private long mInstallAttributionWindow;
private long mInstallCooldownWindow;
- private @Nullable Long mDebugKey;
+ private @Nullable UnsignedLong mDebugKey;
private boolean mIsInstallAttributed;
- private String mAggregateFilterData;
+ private String mFilterData;
private String mAggregateSource;
private int mAggregateContributions;
private AggregatableAttributionSource mAggregatableAttributionSource;
- @IntDef(value = {
- Status.ACTIVE,
- Status.IGNORED,
- })
+ @IntDef(value = {Status.ACTIVE, Status.IGNORED, Status.MARKED_TO_DELETE})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {
int ACTIVE = 0;
int IGNORED = 1;
+ int MARKED_TO_DELETE = 2;
}
@IntDef(value = {
@@ -120,7 +117,7 @@ public class Source {
private final String mValue;
SourceType(String value) {
- this.mValue = value;
+ mValue = value;
}
public String getValue() {
@@ -140,14 +137,14 @@ public class Source {
/** Class for storing fake report data. */
public static class FakeReport {
- private final long mTriggerData;
+ private final UnsignedLong mTriggerData;
private final long mReportingTime;
private final Uri mDestination;
- public FakeReport(long triggerData, long reportingTime, Uri destination) {
- this.mTriggerData = triggerData;
- this.mReportingTime = reportingTime;
- this.mDestination = destination;
+ public FakeReport(UnsignedLong triggerData, long reportingTime, Uri destination) {
+ mTriggerData = triggerData;
+ mReportingTime = reportingTime;
+ mDestination = destination;
}
@Override
@@ -155,7 +152,7 @@ public class Source {
if (this == o) return true;
if (!(o instanceof FakeReport)) return false;
FakeReport that = (FakeReport) o;
- return mTriggerData == that.mTriggerData
+ return Objects.equals(mTriggerData, that.mTriggerData)
&& mReportingTime == that.mReportingTime
&& Objects.equals(mDestination, that.mDestination);
}
@@ -169,7 +166,7 @@ public class Source {
return mReportingTime;
}
- public long getTriggerData() {
+ public UnsignedLong getTriggerData() {
return mTriggerData;
}
@@ -241,7 +238,7 @@ public class Source {
public int getTriggerDataCardinality() {
return mSourceType == SourceType.EVENT
? PrivacyParams.EVENT_TRIGGER_DATA_CARDINALITY
- : PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY;
+ : PrivacyParams.getNavigationTriggerDataCardinality();
}
/**
@@ -316,13 +313,13 @@ public class Source {
&& mStatus == source.mStatus
&& mExpiryTime == source.mExpiryTime
&& mEventTime == source.mEventTime
- && mEventId == source.mEventId
+ && Objects.equals(mEventId, source.mEventId)
&& Objects.equals(mDebugKey, source.mDebugKey)
&& mSourceType == source.mSourceType
&& Objects.equals(mDedupKeys, source.mDedupKeys)
&& Objects.equals(mRegistrant, source.mRegistrant)
&& mAttributionMode == source.mAttributionMode
- && Objects.equals(mAggregateFilterData, source.mAggregateFilterData)
+ && Objects.equals(mFilterData, source.mFilterData)
&& Objects.equals(mAggregateSource, source.mAggregateSource)
&& mAggregateContributions == source.mAggregateContributions
&& Objects.equals(
@@ -345,7 +342,7 @@ public class Source {
mEventId,
mSourceType,
mDedupKeys,
- mAggregateFilterData,
+ mFilterData,
mAggregateSource,
mAggregateContributions,
mAggregatableAttributionSource,
@@ -409,7 +406,7 @@ public class Source {
.map(
reportConfig ->
new FakeReport(
- reportConfig[0],
+ new UnsignedLong(Long.valueOf(reportConfig[0])),
getReportingTimeForNoising(reportConfig[1]),
resolveFakeReportDestination(reportConfig[2])))
.collect(Collectors.toList());
@@ -429,7 +426,7 @@ public class Source {
/**
* Identifier provided by the registrant.
*/
- public long getEventId() {
+ public UnsignedLong getEventId() {
return mEventId;
}
@@ -483,7 +480,7 @@ public class Source {
}
/** Debug key of {@link Source}. */
- public @Nullable Long getDebugKey() {
+ public @Nullable UnsignedLong getDebugKey() {
return mDebugKey;
}
@@ -497,7 +494,7 @@ public class Source {
/**
* List of dedup keys for the attributed {@link Trigger}.
*/
- public List<Long> getDedupKeys() {
+ public List<UnsignedLong> getDedupKeys() {
return mDedupKeys;
}
@@ -555,8 +552,8 @@ public class Source {
* }
* }
*/
- public String getAggregateFilterData() {
- return mAggregateFilterData;
+ public String getFilterData() {
+ return mFilterData;
}
/**
@@ -614,34 +611,46 @@ public class Source {
}
/**
+ * Generates AggregatableFilterData from aggregate filter string in Source, including an entry
+ * for source type.
+ */
+ public FilterData parseFilterData() throws JSONException {
+ FilterData filterData;
+ if (mFilterData == null || mFilterData.isEmpty()) {
+ filterData = new FilterData.Builder().build();
+ } else {
+ filterData =
+ new FilterData.Builder()
+ .buildFilterData(new JSONObject(mFilterData))
+ .build();
+ }
+ filterData.getAttributionFilterMap().put("source_type",
+ Collections.singletonList(mSourceType.getValue()));
+ return filterData;
+ }
+
+ /**
* Generates AggregatableAttributionSource from aggregate source string and aggregate filter
* data string in Source.
*/
public Optional<AggregatableAttributionSource> parseAggregateSource()
throws JSONException, NumberFormatException {
- if (this.mAggregateSource == null) {
+ if (mAggregateSource == null) {
return Optional.empty();
}
- JSONArray jsonArray = new JSONArray(this.mAggregateSource);
+ JSONObject jsonObject = new JSONObject(mAggregateSource);
Map<String, BigInteger> aggregateSourceMap = new HashMap<>();
- for (int i = 0; i < jsonArray.length(); i++) {
- JSONObject jsonObject = jsonArray.getJSONObject(i);
- String id = jsonObject.getString("id");
+ for (String key : jsonObject.keySet()) {
// Remove "0x" prefix.
- String hexString = jsonObject.getString("key_piece").substring(2);
+ String hexString = jsonObject.getString(key).substring(2);
BigInteger bigInteger = new BigInteger(hexString, 16);
- aggregateSourceMap.put(id, bigInteger);
+ aggregateSourceMap.put(key, bigInteger);
}
- AggregatableAttributionSource.Builder asBuilder =
+ AggregatableAttributionSource.Builder aggregatableAttributionSourceBuilder =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregateSourceMap);
- if (this.mAggregateFilterData != null) {
- asBuilder.setAggregateFilterData(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(new JSONObject(this.mAggregateFilterData))
- .build());
- }
- return Optional.of(asBuilder.build());
+ aggregatableAttributionSourceBuilder.setFilterData(parseFilterData());
+ return Optional.of(aggregatableAttributionSourceBuilder.build());
}
private List<FakeReport> generateVtcDualDestinationPostInstallFakeReports() {
@@ -653,7 +662,7 @@ public class Source {
.map(
reportConfig ->
new FakeReport(
- reportConfig[0],
+ new UnsignedLong(Long.valueOf(reportConfig[0])),
getReportingTimeForNoising(reportConfig[1]),
resolveFakeReportDestination(reportConfig[2])))
.collect(Collectors.toList());
@@ -704,7 +713,7 @@ public class Source {
/** See {@link Source#getEventId()}. */
@NonNull
- public Builder setEventId(long eventId) {
+ public Builder setEventId(UnsignedLong eventId) {
mBuilding.mEventId = eventId;
return this;
}
@@ -769,7 +778,7 @@ public class Source {
}
/** See {@link Source#getDebugKey()} ()}. */
- public Builder setDebugKey(@Nullable Long debugKey) {
+ public Builder setDebugKey(@Nullable UnsignedLong debugKey) {
mBuilding.mDebugKey = debugKey;
return this;
}
@@ -784,7 +793,7 @@ public class Source {
/** See {@link Source#getDedupKeys()}. */
@NonNull
- public Builder setDedupKeys(@Nullable List<Long> dedupKeys) {
+ public Builder setDedupKeys(@Nullable List<UnsignedLong> dedupKeys) {
mBuilding.mDedupKeys = dedupKeys;
return this;
}
@@ -832,9 +841,9 @@ public class Source {
return this;
}
- /** See {@link Source#getAggregateFilterData()}. */
- public Builder setAggregateFilterData(@Nullable String aggregateFilterData) {
- mBuilding.mAggregateFilterData = aggregateFilterData;
+ /** See {@link Source#getFilterData()}. */
+ public Builder setFilterData(@Nullable String filterData) {
+ mBuilding.mFilterData = filterData;
return this;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/SystemHealthParams.java b/adservices/service-core/java/com/android/adservices/service/measurement/SystemHealthParams.java
index 87d026b92..d411f3bca 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/SystemHealthParams.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/SystemHealthParams.java
@@ -28,26 +28,30 @@ public class SystemHealthParams {
}
/**
- * Max number of sources an app can register.
- */
- public static final int MAX_SOURCE_REGISTERS_PER_REGISTRANT = 1600; // placeholder value
-
- /**
* Max number of triggers an app can register.
*/
public static final int MAX_TRIGGER_REGISTERS_PER_REGISTRANT = 1000; // placeholder value
- /**
- * Delay for attribution job triggering.
- */
- public static final long ATTRIBUTION_JOB_TRIGGERING_DELAY_MS =
- TimeUnit.MINUTES.toMillis(2);
+ /** Max number of sources per publisher. */
+ public static final long MAX_SOURCES_PER_PUBLISHER = 1024L;
+
+ /** Max number of redirects per registration. */
+ public static final int MAX_REDIRECTS_PER_REGISTRATION = 5;
+
+ /** Delay for attribution job triggering. */
+ public static final long ATTRIBUTION_JOB_TRIGGERING_DELAY_MS = TimeUnit.MINUTES.toMillis(2);
/**
* Max number of {@link Trigger} to process per job for {@link AttributionJobService}
*/
public static final int MAX_ATTRIBUTIONS_PER_INVOCATION = 100;
+ /** Max number of aggregate reports in storage per destination */
+ public static final long MAX_AGGREGATE_REPORTS_PER_DESTINATION = 1024;
+
+ /** Max number of event reports in storage per destination */
+ public static final long MAX_EVENT_REPORTS_PER_DESTINATION = 1024;
+
/**
* Maximum event report upload retry window.
*/
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/Trigger.java b/adservices/service-core/java/com/android/adservices/service/measurement/Trigger.java
index 13182a1bb..41394bcbf 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/Trigger.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/Trigger.java
@@ -22,9 +22,11 @@ import android.annotation.Nullable;
import android.net.Uri;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionTrigger;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
import com.android.adservices.service.measurement.aggregation.AggregateTriggerData;
+import com.android.adservices.service.measurement.util.BaseUriExtractor;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import com.android.adservices.service.measurement.util.Validation;
+import com.android.adservices.service.measurement.util.Web;
import org.json.JSONArray;
import org.json.JSONException;
@@ -60,18 +62,16 @@ public class Trigger {
private String mAggregateValues;
private AggregatableAttributionTrigger mAggregatableAttributionTrigger;
private String mFilters;
- private @Nullable Long mDebugKey;
+ private String mNotFilters;
+ private @Nullable UnsignedLong mDebugKey;
- @IntDef(value = {
- Status.PENDING,
- Status.IGNORED,
- Status.ATTRIBUTED,
- })
+ @IntDef(value = {Status.PENDING, Status.IGNORED, Status.ATTRIBUTED, Status.MARKED_TO_DELETE})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {
int PENDING = 0;
int IGNORED = 1;
int ATTRIBUTED = 2;
+ int MARKED_TO_DELETE = 3;
}
private Trigger() {
@@ -99,7 +99,8 @@ public class Trigger {
&& Objects.equals(mAggregateValues, trigger.mAggregateValues)
&& Objects.equals(
mAggregatableAttributionTrigger, trigger.mAggregatableAttributionTrigger)
- && Objects.equals(mFilters, trigger.mFilters);
+ && Objects.equals(mFilters, trigger.mFilters)
+ && Objects.equals(mNotFilters, trigger.mNotFilters);
}
@Override
@@ -116,6 +117,7 @@ public class Trigger {
mAggregateValues,
mAggregatableAttributionTrigger,
mFilters,
+ mNotFilters,
mDebugKey);
}
@@ -239,8 +241,15 @@ public class Trigger {
return mFilters;
}
+ /**
+ * Returns top level not-filters. The value is in json format.
+ */
+ public String getNotFilters() {
+ return mNotFilters;
+ }
+
/** Debug key of {@link Trigger}. */
- public @Nullable Long getDebugKey() {
+ public @Nullable UnsignedLong getDebugKey() {
return mDebugKey;
}
/**
@@ -269,14 +278,14 @@ public class Trigger {
.setKey(bigInteger)
.setSourceKeys(sourceKeySet);
if (jsonObject.has("filters") && !jsonObject.isNull("filters")) {
- AggregateFilterData filters = new AggregateFilterData.Builder()
- .buildAggregateFilterData(jsonObject.getJSONObject("filters")).build();
+ FilterData filters = new FilterData.Builder()
+ .buildFilterData(jsonObject.getJSONObject("filters")).build();
builder.setFilter(filters);
}
if (jsonObject.has("not_filters")
&& !jsonObject.isNull("not_filters")) {
- AggregateFilterData notFilters = new AggregateFilterData.Builder()
- .buildAggregateFilterData(
+ FilterData notFilters = new FilterData.Builder()
+ .buildFilterData(
jsonObject.getJSONObject("not_filters")).build();
builder.setNotFilter(notFilters);
}
@@ -306,8 +315,8 @@ public class Trigger {
JSONObject eventTriggersJsonString = jsonArray.getJSONObject(i);
if (!eventTriggersJsonString.isNull(EventTriggerContract.TRIGGER_DATA)) {
- eventTriggerBuilder.setTriggerData(
- eventTriggersJsonString.getLong(EventTriggerContract.TRIGGER_DATA));
+ eventTriggerBuilder.setTriggerData(new UnsignedLong(
+ eventTriggersJsonString.getString(EventTriggerContract.TRIGGER_DATA)));
}
if (!eventTriggersJsonString.isNull(EventTriggerContract.PRIORITY)) {
@@ -316,14 +325,14 @@ public class Trigger {
}
if (!eventTriggersJsonString.isNull(EventTriggerContract.DEDUPLICATION_KEY)) {
- eventTriggerBuilder.setDedupKey(
- eventTriggersJsonString.getLong(EventTriggerContract.DEDUPLICATION_KEY));
+ eventTriggerBuilder.setDedupKey(new UnsignedLong(
+ eventTriggersJsonString.getString(EventTriggerContract.DEDUPLICATION_KEY)));
}
if (!eventTriggersJsonString.isNull(EventTriggerContract.FILTERS)) {
- AggregateFilterData filters =
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(
+ FilterData filters =
+ new FilterData.Builder()
+ .buildFilterData(
eventTriggersJsonString.getJSONObject(
EventTriggerContract.FILTERS))
.build();
@@ -331,9 +340,9 @@ public class Trigger {
}
if (!eventTriggersJsonString.isNull(EventTriggerContract.NOT_FILTERS)) {
- AggregateFilterData notFilters =
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(
+ FilterData notFilters =
+ new FilterData.Builder()
+ .buildFilterData(
eventTriggersJsonString.getJSONObject(
EventTriggerContract.NOT_FILTERS))
.build();
@@ -346,6 +355,20 @@ public class Trigger {
}
/**
+ * Returns a {@code Uri} with scheme and (1) public suffix + 1 in case of a web destination, or
+ * (2) the Android package name in case of an app destination. Returns null if extracting the
+ * public suffix + 1 fails.
+ */
+ public @Nullable Uri getAttributionDestinationBaseUri() {
+ if (mDestinationType == EventSurfaceType.APP) {
+ return BaseUriExtractor.getBaseUri(mAttributionDestination);
+ } else {
+ Optional<Uri> uri = Web.topPrivateDomainAndScheme(mAttributionDestination);
+ return uri.orElse(null);
+ }
+ }
+
+ /**
* Builder for {@link Trigger}.
*/
public static final class Builder {
@@ -436,8 +459,15 @@ public class Trigger {
return this;
}
+ /** See {@link Trigger#getNotFilters()} */
+ @NonNull
+ public Builder setNotFilters(@Nullable String notFilters) {
+ mBuilding.mNotFilters = notFilters;
+ return this;
+ }
+
/** See {@link Trigger#getDebugKey()} ()} */
- public Builder setDebugKey(@Nullable Long debugKey) {
+ public Builder setDebugKey(@Nullable UnsignedLong debugKey) {
mBuilding.mDebugKey = debugKey;
return this;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolver.java b/adservices/service-core/java/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolver.java
index 44d048f61..9246a2896 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolver.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolver.java
@@ -63,13 +63,16 @@ public class ManifestBasedAdtechAccessResolver implements IAccessResolver {
if (mUrl == null || TextUtils.isEmpty(mUrl.toString())) {
return false;
}
- String uriWithoutParams = mUrl.buildUpon().clearQuery().fragment(null).build().toString();
+ Uri uriWithoutParams = mUrl.buildUpon().clearQuery().fragment(null).build();
EnrollmentData enrollment =
mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(uriWithoutParams);
- boolean enrollmentKnown = (enrollment != null) && (enrollment.getEnrollmentId() != null);
- return enrollmentKnown
- && AppManifestConfigHelper.isAllowedAttributionAccess(
- context, mPackageName, enrollment.getEnrollmentId());
+ if (enrollment == null || enrollment.getEnrollmentId() == null) {
+ return false;
+ }
+ String enrollmentId = enrollment.getEnrollmentId();
+ return AppManifestConfigHelper.isAllowedAttributionAccess(
+ context, mPackageName, enrollmentId)
+ && !mFlags.isEnrollmentBlocklisted(enrollmentId);
}
@NonNull
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/access/UserConsentAccessResolver.java b/adservices/service-core/java/com/android/adservices/service/measurement/access/UserConsentAccessResolver.java
index 758fce65e..fb0e63948 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/access/UserConsentAccessResolver.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/access/UserConsentAccessResolver.java
@@ -36,7 +36,7 @@ public class UserConsentAccessResolver implements IAccessResolver {
@Override
public boolean isAllowed(@NonNull Context context) {
- AdServicesApiConsent userConsent = mConsentManager.getConsent(context.getPackageManager());
+ AdServicesApiConsent userConsent = mConsentManager.getConsent();
return userConsent.isGiven();
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatableAttributionSource.java b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatableAttributionSource.java
index e2c2983b9..9dfa01606 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatableAttributionSource.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatableAttributionSource.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement.aggregation;
+import com.android.adservices.service.measurement.FilterData;
+
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
@@ -27,11 +29,11 @@ import java.util.Objects;
public class AggregatableAttributionSource {
private Map<String, BigInteger> mAggregatableSource;
- private AggregateFilterData mAggregateFilterData;
+ private FilterData mFilterData;
private AggregatableAttributionSource() {
mAggregatableSource = new HashMap<>();
- mAggregateFilterData = null;
+ mFilterData = null;
}
@Override
@@ -41,7 +43,7 @@ public class AggregatableAttributionSource {
}
AggregatableAttributionSource attributionSource = (AggregatableAttributionSource) obj;
return Objects.equals(mAggregatableSource, attributionSource.mAggregatableSource)
- && Objects.equals(mAggregateFilterData, attributionSource.mAggregateFilterData);
+ && Objects.equals(mFilterData, attributionSource.mFilterData);
}
@Override
@@ -60,8 +62,8 @@ public class AggregatableAttributionSource {
/**
* Returns aggregate filter data which represents a map in JSONObject.
*/
- public AggregateFilterData getAggregateFilterData() {
- return mAggregateFilterData;
+ public FilterData getFilterData() {
+ return mFilterData;
}
/**
@@ -84,10 +86,10 @@ public class AggregatableAttributionSource {
}
/**
- * See {@link AggregatableAttributionSource#getAggregateFilterData()}.
+ * See {@link AggregatableAttributionSource#getFilterData()}.
*/
- public Builder setAggregateFilterData(AggregateFilterData aggregateFilterData) {
- mBuilding.mAggregateFilterData = aggregateFilterData;
+ public Builder setFilterData(FilterData filterData) {
+ mBuilding.mFilterData = filterData;
return this;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManager.java b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManager.java
index 292002d8f..1333a3a7a 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManager.java
@@ -24,6 +24,7 @@ import com.android.internal.annotations.VisibleForTesting;
import java.time.Clock;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Random;
@@ -39,8 +40,13 @@ public final class AggregateEncryptionKeyManager {
mDatastoreManager = datastoreManager;
mAggregateEncryptionKeyFetcher = new AggregateEncryptionKeyFetcher();
mClock = Clock.systemUTC();
- mAggregateEncryptionKeyCoordinatorUrl =
- Uri.parse(AdServicesConfig.getMeasurementAggregateEncryptionKeyCoordinatorUrl());
+ String encryptionKeyCoordinatorUrl =
+ AdServicesConfig.getMeasurementAggregateEncryptionKeyCoordinatorUrl();
+ if (encryptionKeyCoordinatorUrl != null) {
+ mAggregateEncryptionKeyCoordinatorUrl = Uri.parse(encryptionKeyCoordinatorUrl);
+ } else {
+ mAggregateEncryptionKeyCoordinatorUrl = null;
+ }
}
@VisibleForTesting
@@ -59,6 +65,11 @@ public final class AggregateEncryptionKeyManager {
* the numKeys specified in the parameters. If no keys are found, the collection would be empty.
*/
public List<AggregateEncryptionKey> getAggregateEncryptionKeys(int numKeys) {
+ if (mAggregateEncryptionKeyCoordinatorUrl == null) {
+ LogUtil.w("Fetching aggregate encryption keys failed, empty coordinator url.");
+ return Collections.emptyList();
+ }
+
long eventTime = mClock.millis();
Optional<List<AggregateEncryptionKey>> aggregateEncryptionKeysOptional =
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatePayloadGenerator.java b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatePayloadGenerator.java
index 5cffcd311..55c6ccd9b 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatePayloadGenerator.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregatePayloadGenerator.java
@@ -16,11 +16,11 @@
package com.android.adservices.service.measurement.aggregation;
+import com.android.adservices.service.measurement.FilterData;
import com.android.adservices.service.measurement.util.Filter;
import java.math.BigInteger;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -40,53 +40,44 @@ public class AggregatePayloadGenerator {
* @param attributionSource the aggregate attribution source used for aggregation.
* @param attributionTrigger the aggregate attribution trigger used for aggregation.
* @return the aggregate report generated by the given aggregate attribution source and
- * aggregate attribution trigger.
+ * aggregate attribution trigger.
*/
public static Optional<List<AggregateHistogramContribution>> generateAttributionReport(
AggregatableAttributionSource attributionSource,
AggregatableAttributionTrigger attributionTrigger) {
- AggregateFilterData sourceFilterData = attributionSource.getAggregateFilterData();
- Map<String, BigInteger> aggregateKeys = new HashMap<>();
+ FilterData sourceFilterData = attributionSource.getFilterData();
Map<String, BigInteger> aggregateSourceMap =
attributionSource.getAggregatableSource();
- for (String sourceKey : aggregateSourceMap.keySet()) {
- for (AggregateTriggerData triggerData : attributionTrigger.getTriggerData()) {
- Optional<AggregateFilterData> filterData = triggerData.getFilter();
- Optional<AggregateFilterData> notFilterData = triggerData.getNotFilter();
- // Skip this trigger data when filter doesn't match.
- if (filterData.isPresent()
- && !Filter.isFilterMatch(sourceFilterData, filterData.get(), true)) {
- continue;
- }
- // Skip this trigger data when not_filters doesn't match.
- if (notFilterData.isPresent()
- && !Filter.isFilterMatch(
- sourceFilterData, notFilterData.get(), false)) {
- continue;
- }
- if (triggerData.getSourceKeys().contains(sourceKey)) {
- BigInteger currentKey = aggregateSourceMap.get(sourceKey);
- BigInteger triggerKey = triggerData.getKey();
- BigInteger currentInt;
- if (aggregateKeys.containsKey(sourceKey)) {
- currentInt = aggregateKeys.get(sourceKey);
- } else {
- currentInt = currentKey;
- }
- aggregateKeys.put(sourceKey, currentInt.or(triggerKey));
- }
+ for (AggregateTriggerData triggerData : attributionTrigger.getTriggerData()) {
+ Optional<FilterData> filterData = triggerData.getFilter();
+ Optional<FilterData> notFilterData = triggerData.getNotFilter();
+ // Skip this trigger data when filter doesn't match.
+ if (filterData.isPresent()
+ && !Filter.isFilterMatch(sourceFilterData, filterData.get(), true)) {
+ continue;
+ }
+ // Skip this trigger data when not_filters doesn't match.
+ if (notFilterData.isPresent()
+ && !Filter.isFilterMatch(
+ sourceFilterData, notFilterData.get(), false)) {
+ continue;
+ }
+ for (String sourceKey : triggerData.getSourceKeys()) {
+ aggregateSourceMap.computeIfPresent(sourceKey, (k, v) ->
+ v.or(triggerData.getKey()));
}
}
List<AggregateHistogramContribution> contributions = new ArrayList<>();
- for (String key : attributionTrigger.getValues().keySet()) {
- if (aggregateKeys.containsKey(key)) {
- AggregateHistogramContribution contribution =
- new AggregateHistogramContribution.Builder()
- .setKey(aggregateKeys.get(key))
- .setValue(attributionTrigger.getValues().get(key)).build();
- contributions.add(contribution);
+ for (String id : aggregateSourceMap.keySet()) {
+ if (!attributionTrigger.getValues().containsKey(id)) {
+ continue;
}
+ AggregateHistogramContribution contribution =
+ new AggregateHistogramContribution.Builder()
+ .setKey(aggregateSourceMap.get(id))
+ .setValue(attributionTrigger.getValues().get(id)).build();
+ contributions.add(contribution);
}
if (contributions.size() > 0) {
return Optional.of(contributions);
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateReport.java b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateReport.java
index b46de5be3..cc8417ded 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateReport.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateReport.java
@@ -21,6 +21,8 @@ import android.net.Uri;
import androidx.annotation.Nullable;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -43,18 +45,32 @@ public class AggregateReport {
private String mDebugCleartextPayload;
private AggregateAttributionData mAggregateAttributionData;
private @Status int mStatus;
+ private @DebugReportStatus int mDebugReportStatus;
private String mApiVersion;
- @Nullable private Long mSourceDebugKey;
- @Nullable private Long mTriggerDebugKey;
+ @Nullable private UnsignedLong mSourceDebugKey;
+ @Nullable private UnsignedLong mTriggerDebugKey;
+ private String mSourceId;
+ private String mTriggerId;
- @IntDef(value = {
- Status.PENDING,
- Status.DELIVERED,
- })
+ @IntDef(value = {Status.PENDING, Status.DELIVERED, Status.MARKED_TO_DELETE})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {
int PENDING = 0;
int DELIVERED = 1;
+ int MARKED_TO_DELETE = 2;
+ }
+
+ @IntDef(
+ value = {
+ DebugReportStatus.NONE,
+ DebugReportStatus.PENDING,
+ DebugReportStatus.DELIVERED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DebugReportStatus {
+ int NONE = 0;
+ int PENDING = 1;
+ int DELIVERED = 2;
}
private AggregateReport() {
@@ -67,6 +83,7 @@ public class AggregateReport {
mDebugCleartextPayload = null;
mAggregateAttributionData = null;
mStatus = AggregateReport.Status.PENDING;
+ mDebugReportStatus = AggregateReport.DebugReportStatus.NONE;
mSourceDebugKey = null;
mTriggerDebugKey = null;
}
@@ -86,9 +103,12 @@ public class AggregateReport {
&& Objects.equals(
mAggregateAttributionData, aggregateReport.mAggregateAttributionData)
&& mStatus == aggregateReport.mStatus
+ && mDebugReportStatus == aggregateReport.mDebugReportStatus
&& Objects.equals(mApiVersion, aggregateReport.mApiVersion)
&& Objects.equals(mSourceDebugKey, aggregateReport.mSourceDebugKey)
- && Objects.equals(mTriggerDebugKey, aggregateReport.mTriggerDebugKey);
+ && Objects.equals(mTriggerDebugKey, aggregateReport.mTriggerDebugKey)
+ && Objects.equals(mSourceId, aggregateReport.mSourceId)
+ && Objects.equals(mTriggerId, aggregateReport.mTriggerId);
}
@Override
@@ -103,8 +123,11 @@ public class AggregateReport {
mDebugCleartextPayload,
mAggregateAttributionData,
mStatus,
+ mDebugReportStatus,
mSourceDebugKey,
- mTriggerDebugKey);
+ mTriggerDebugKey,
+ mSourceId,
+ mTriggerId);
}
/**
@@ -158,13 +181,13 @@ public class AggregateReport {
/** Source Debug Key */
@Nullable
- public Long getSourceDebugKey() {
+ public UnsignedLong getSourceDebugKey() {
return mSourceDebugKey;
}
/** Trigger Debug Key */
@Nullable
- public Long getTriggerDebugKey() {
+ public UnsignedLong getTriggerDebugKey() {
return mTriggerDebugKey;
}
@@ -182,6 +205,11 @@ public class AggregateReport {
return mStatus;
}
+ /** Current {@link DebugReportStatus} of the report. */
+ public @DebugReportStatus int getDebugReportStatus() {
+ return mDebugReportStatus;
+ }
+
/**
* Api version when the report was issued.
*/
@@ -216,6 +244,16 @@ public class AggregateReport {
return debugPayload.toString();
}
+ /** Source ID */
+ public String getSourceId() {
+ return mSourceId;
+ }
+
+ /** Trigger ID */
+ public String getTriggerId() {
+ return mTriggerId;
+ }
+
/**
* Builder for {@link AggregateReport}.
*/
@@ -298,6 +336,11 @@ public class AggregateReport {
mAttributionReport.mStatus = status;
return this;
}
+ /** See {@link AggregateReport#getDebugReportStatus()} */
+ public Builder setDebugReportStatus(@DebugReportStatus int debugReportStatus) {
+ mAttributionReport.mDebugReportStatus = debugReportStatus;
+ return this;
+ }
/**
* See {@link AggregateReport#getApiVersion()}
@@ -308,17 +351,29 @@ public class AggregateReport {
}
/** See {@link AggregateReport#getSourceDebugKey()} ()} */
- public Builder setSourceDebugKey(Long sourceDebugKey) {
+ public Builder setSourceDebugKey(UnsignedLong sourceDebugKey) {
mAttributionReport.mSourceDebugKey = sourceDebugKey;
return this;
}
/** See {@link AggregateReport#getTriggerDebugKey()} ()} */
- public Builder setTriggerDebugKey(Long triggerDebugKey) {
+ public Builder setTriggerDebugKey(UnsignedLong triggerDebugKey) {
mAttributionReport.mTriggerDebugKey = triggerDebugKey;
return this;
}
+ /** See {@link AggregateReport#getSourceId()} */
+ public AggregateReport.Builder setSourceId(String sourceId) {
+ mAttributionReport.mSourceId = sourceId;
+ return this;
+ }
+
+ /** See {@link AggregateReport#getTriggerId()} */
+ public AggregateReport.Builder setTriggerId(String triggerId) {
+ mAttributionReport.mTriggerId = triggerId;
+ return this;
+ }
+
/**
* Build the {@link AggregateReport}.
*/
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateTriggerData.java b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateTriggerData.java
index 0f802cd5c..ab883f095 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateTriggerData.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/aggregation/AggregateTriggerData.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement.aggregation;
+import com.android.adservices.service.measurement.FilterData;
+
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Objects;
@@ -29,8 +31,8 @@ public class AggregateTriggerData {
private BigInteger mKey;
private Set<String> mSourceKeys;
- private Optional<AggregateFilterData> mFilter;
- private Optional<AggregateFilterData> mNotFilter;
+ private Optional<FilterData> mFilter;
+ private Optional<FilterData> mNotFilter;
private AggregateTriggerData() {
mKey = null;
@@ -74,14 +76,14 @@ public class AggregateTriggerData {
* Returns the filter which controls when aggregate trigger data ise used based on impression
* side information.
*/
- public Optional<AggregateFilterData> getFilter() {
+ public Optional<FilterData> getFilter() {
return mFilter;
}
/**
* Returns the not_filter, reverse of filter.
*/
- public Optional<AggregateFilterData> getNotFilter() {
+ public Optional<FilterData> getNotFilter() {
return mNotFilter;
}
@@ -114,7 +116,7 @@ public class AggregateTriggerData {
/**
* See {@link AggregateTriggerData#getFilter()}.
*/
- public Builder setFilter(AggregateFilterData filter) {
+ public Builder setFilter(FilterData filter) {
mBuilding.mFilter = Optional.of(filter);
return this;
}
@@ -122,7 +124,7 @@ public class AggregateTriggerData {
/**
* See {@link AggregateTriggerData#getNotFilter()}
*/
- public Builder setNotFilter(AggregateFilterData notFilter) {
+ public Builder setNotFilter(FilterData notFilter) {
mBuilding.mNotFilter = Optional.of(notFilter);
return this;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java b/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java
index 6274fa64e..302fc14ff 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java
@@ -16,6 +16,9 @@
package com.android.adservices.service.measurement.attribution;
+import static com.android.adservices.service.measurement.PrivacyParams.AGGREGATE_MAX_REPORT_DELAY;
+import static com.android.adservices.service.measurement.PrivacyParams.AGGREGATE_MIN_REPORT_DELAY;
+
import android.annotation.NonNull;
import android.net.Uri;
import android.util.Pair;
@@ -28,6 +31,7 @@ import com.android.adservices.service.measurement.Attribution;
import com.android.adservices.service.measurement.EventReport;
import com.android.adservices.service.measurement.EventSurfaceType;
import com.android.adservices.service.measurement.EventTrigger;
+import com.android.adservices.service.measurement.FilterData;
import com.android.adservices.service.measurement.PrivacyParams;
import com.android.adservices.service.measurement.Source;
import com.android.adservices.service.measurement.SystemHealthParams;
@@ -35,7 +39,6 @@ import com.android.adservices.service.measurement.Trigger;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionSource;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionTrigger;
import com.android.adservices.service.measurement.aggregation.AggregateAttributionData;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
import com.android.adservices.service.measurement.aggregation.AggregateHistogramContribution;
import com.android.adservices.service.measurement.aggregation.AggregatePayloadGenerator;
import com.android.adservices.service.measurement.aggregation.AggregateReport;
@@ -49,7 +52,7 @@ import org.json.JSONObject;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Map;
+import java.util.Locale;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.concurrent.TimeUnit;
@@ -58,8 +61,6 @@ import java.util.stream.Collectors;
class AttributionJobHandler {
private static final String API_VERSION = "0.1";
- private static final long MIN_TIME_MS = TimeUnit.MINUTES.toMillis(10L);
- private static final long MAX_TIME_MS = TimeUnit.MINUTES.toMillis(60L);
private final DatastoreManager mDatastoreManager;
AttributionJobHandler(DatastoreManager datastoreManager) {
@@ -142,6 +143,20 @@ class AttributionJobHandler {
private boolean maybeGenerateAggregateReport(Source source, Trigger trigger,
IMeasurementDao measurementDao) throws DatastoreException {
+ int numReports =
+ measurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType());
+
+ if (numReports >= SystemHealthParams.MAX_AGGREGATE_REPORTS_PER_DESTINATION) {
+ LogUtil.d(
+ String.format(Locale.ENGLISH,
+ "Aggregate reports for destination %1$s exceeds system health limit of"
+ + " %2$d.",
+ trigger.getAttributionDestination(),
+ SystemHealthParams.MAX_AGGREGATE_REPORTS_PER_DESTINATION));
+ return false;
+ }
+
try {
Optional<AggregatableAttributionSource> aggregateAttributionSource =
source.parseAggregateSource();
@@ -164,13 +179,19 @@ class AttributionJobHandler {
return false;
}
- long randomTime = (long) ((Math.random() * (MAX_TIME_MS - MIN_TIME_MS))
- + MIN_TIME_MS);
+ long randomTime = (long) ((Math.random()
+ * (AGGREGATE_MAX_REPORT_DELAY - AGGREGATE_MIN_REPORT_DELAY))
+ + AGGREGATE_MIN_REPORT_DELAY);
+ int debugReportStatus = AggregateReport.DebugReportStatus.NONE;
+ if (source.getDebugKey() != null || trigger.getDebugKey() != null) {
+ debugReportStatus = AggregateReport.DebugReportStatus.PENDING;
+ }
AggregateReport aggregateReport =
new AggregateReport.Builder()
- // TODO: Unused field, incorrect value; cleanup
+ // TODO: b/254855494 unused field, incorrect value; cleanup
.setPublisher(source.getRegistrant())
- .setAttributionDestination(trigger.getAttributionDestination())
+ .setAttributionDestination(
+ trigger.getAttributionDestinationBaseUri())
.setSourceRegistrationTime(
roundDownToDay(source.getEventTime()))
.setScheduledReportTime(trigger.getTriggerTime() + randomTime)
@@ -183,7 +204,12 @@ class AttributionJobHandler {
.setContributions(contributions.get())
.build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(debugReportStatus)
.setApiVersion(API_VERSION)
+ .setSourceDebugKey(source.getDebugKey())
+ .setTriggerDebugKey(trigger.getDebugKey())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
.build();
measurementDao.updateSourceAggregateContributions(source);
@@ -223,7 +249,9 @@ class AttributionJobHandler {
matchingSources.remove(0);
if (!matchingSources.isEmpty()) {
matchingSources.forEach((s) -> s.setStatus(Source.Status.IGNORED));
- measurementDao.updateSourceStatus(matchingSources, Source.Status.IGNORED);
+ List<String> sourceIds =
+ matchingSources.stream().map(Source::getId).collect(Collectors.toList());
+ measurementDao.updateSourceStatus(sourceIds, Source.Status.IGNORED);
}
return Optional.of(selectedSource);
}
@@ -231,6 +259,24 @@ class AttributionJobHandler {
private boolean maybeGenerateEventReport(
Source source, Trigger trigger, IMeasurementDao measurementDao)
throws DatastoreException {
+ if (trigger.getEventTriggers() == null) {
+ return false;
+ }
+
+ int numReports =
+ measurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType());
+
+ if (numReports >= SystemHealthParams.MAX_EVENT_REPORTS_PER_DESTINATION) {
+ LogUtil.d(
+ String.format(Locale.ENGLISH,
+ "Event reports for destination %1$s exceeds system health limit of"
+ + " %2$d.",
+ trigger.getAttributionDestination(),
+ SystemHealthParams.MAX_EVENT_REPORTS_PER_DESTINATION));
+ return false;
+ }
+
// Do not generate event reports for source which have attributionMode != Truthfully.
// TODO: Handle attribution rate limit consideration for non-truthful cases.
if (source.getAttributionMode() != Source.AttributionMode.TRUTHFULLY) {
@@ -317,21 +363,23 @@ class AttributionJobHandler {
IMeasurementDao measurementDao)
throws DatastoreException {
trigger.setStatus(Trigger.Status.ATTRIBUTED);
- measurementDao.updateTriggerStatus(trigger);
+ measurementDao.updateTriggerStatus(
+ Collections.singletonList(trigger.getId()), Trigger.Status.ATTRIBUTED);
measurementDao.insertAttribution(createAttribution(source, trigger));
}
private void ignoreTrigger(Trigger trigger, IMeasurementDao measurementDao)
throws DatastoreException {
trigger.setStatus(Trigger.Status.IGNORED);
- measurementDao.updateTriggerStatus(trigger);
+ measurementDao.updateTriggerStatus(
+ Collections.singletonList(trigger.getId()), Trigger.Status.IGNORED);
}
private boolean hasAttributionQuota(Source source, Trigger trigger,
IMeasurementDao measurementDao) throws DatastoreException {
long attributionCount =
measurementDao.getAttributionsPerRateLimitWindow(source, trigger);
- return attributionCount < PrivacyParams.MAX_ATTRIBUTION_PER_RATE_LIMIT_WINDOW;
+ return attributionCount < PrivacyParams.getMaxAttributionPerRateLimitWindow();
}
private boolean isWithinReportLimit(
@@ -353,45 +401,22 @@ class AttributionJobHandler {
* @return true for a match, false otherwise
*/
private boolean doTopLevelFiltersMatch(@NonNull Source source, @NonNull Trigger trigger) {
- String triggerFilters = trigger.getFilters();
- String sourceFilters = source.getAggregateFilterData();
- if (triggerFilters == null
- || sourceFilters == null
- || triggerFilters.isEmpty()
- || sourceFilters.isEmpty()) {
- // Nothing to match
- return true;
- }
-
try {
- AggregateFilterData sourceFiltersData = extractFilterMap(sourceFilters);
- AggregateFilterData triggerFiltersData = extractFilterMap(triggerFilters);
- return Filter.isFilterMatch(sourceFiltersData, triggerFiltersData, true);
+ FilterData sourceFilters = source.parseFilterData();
+ FilterData triggerFilters = extractFilterMap(trigger.getFilters());
+ FilterData triggerNotFilters = extractFilterMap(trigger.getNotFilters());
+ return Filter.isFilterMatch(sourceFilters, triggerFilters, true)
+ && Filter.isFilterMatch(sourceFilters, triggerNotFilters, false);
} catch (JSONException e) {
// If JSON is malformed, we shall consider as not matched.
- LogUtil.e(e, "Malformed JSON string.");
+ LogUtil.e(e, "doTopLevelFiltersMatch: JSON parse failed.");
return false;
}
}
private Optional<EventTrigger> findFirstMatchingEventTrigger(Source source, Trigger trigger) {
try {
- String sourceFilters = source.getAggregateFilterData();
-
- AggregateFilterData sourceFiltersData;
- if (sourceFilters == null || sourceFilters.isEmpty()) {
- // Initialize an empty map to add source_type to it later
- sourceFiltersData = new AggregateFilterData.Builder().build();
- } else {
- sourceFiltersData = extractFilterMap(sourceFilters);
- }
-
- // Add source type
- appendToAggregateFilterData(
- sourceFiltersData,
- "source_type",
- Collections.singletonList(source.getSourceType().getValue()));
-
+ FilterData sourceFiltersData = source.parseFilterData();
List<EventTrigger> eventTriggers = trigger.parseEventTriggers();
return eventTriggers.stream()
.filter(
@@ -406,7 +431,7 @@ class AttributionJobHandler {
}
private boolean doEventLevelFiltersMatch(
- AggregateFilterData sourceFiltersData, EventTrigger eventTrigger) {
+ FilterData sourceFiltersData, EventTrigger eventTrigger) {
if (eventTrigger.getFilterData().isPresent()
&& !Filter.isFilterMatch(
sourceFiltersData, eventTrigger.getFilterData().get(), true)) {
@@ -422,19 +447,14 @@ class AttributionJobHandler {
return true;
}
- private AggregateFilterData extractFilterMap(String object) throws JSONException {
- JSONObject sourceFilterObject = new JSONObject(object);
- return new AggregateFilterData.Builder()
- .buildAggregateFilterData(sourceFilterObject)
+ private static FilterData extractFilterMap(String str) throws JSONException {
+ String json = (str == null || str.isEmpty()) ? "{}" : str;
+ JSONObject filterObject = new JSONObject(json);
+ return new FilterData.Builder()
+ .buildFilterData(filterObject)
.build();
}
- private void appendToAggregateFilterData(
- AggregateFilterData filterData, String key, List<String> value) {
- Map<String, List<String>> attributeFilterMap = filterData.getAttributionFilterMap();
- attributeFilterMap.put(key, value);
- }
-
private static OptionalInt validateAndGetUpdatedAggregateContributions(
List<AggregateHistogramContribution> contributions, Source source) {
int newAggregateContributions = source.getAggregateContributions();
@@ -473,7 +493,7 @@ class AttributionJobHandler {
trigger.getTriggerTime());
return count < PrivacyParams
- .MAX_DISTINCT_ENROLLMENTS_PER_PUBLISHER_X_DESTINATION_IN_ATTRIBUTION;
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution();
} else {
LogUtil.d("isEnrollmentWithinPrivacyBounds: getPublisherAndDestinationTopPrivateDomains"
+ " failed. %s %s", source.getPublisher(), trigger.getAttributionDestination());
@@ -528,6 +548,8 @@ class AttributionJobHandler {
.setEnrollmentId(trigger.getEnrollmentId())
.setTriggerTime(trigger.getTriggerTime())
.setRegistrant(trigger.getRegistrant().toString())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
.build();
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobService.java
index 994c504bf..99cbbfef1 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobService.java
@@ -32,6 +32,7 @@ import com.android.adservices.service.AdServicesConfig;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.measurement.SystemHealthParams;
import com.android.adservices.service.measurement.Trigger;
+import com.android.adservices.service.measurement.reporting.DebugReportingJobService;
import com.android.internal.annotations.VisibleForTesting;
import java.util.concurrent.Executor;
@@ -67,6 +68,7 @@ public class AttributionJobService extends JobService {
jobFinished(params, !success);
// jobFinished is asynchronous, so forcing scheduling avoiding concurrency issue
scheduleIfNeeded(this, /* forceSchedule */ true);
+ DebugReportingJobService.scheduleIfNeeded(getApplicationContext(), true);
});
return true;
}
@@ -80,15 +82,18 @@ public class AttributionJobService extends JobService {
/** Schedules {@link AttributionJobService} to observer {@link Trigger} content URI change. */
@VisibleForTesting
static void schedule(Context context, JobScheduler jobScheduler) {
- final JobInfo job = new JobInfo.Builder(AdServicesConfig.MEASUREMENT_ATTRIBUTION_JOB_ID,
- new ComponentName(context, AttributionJobService.class))
- .addTriggerContentUri(new JobInfo.TriggerContentUri(
- TriggerContentProvider.TRIGGER_URI,
- JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
- ))
- .setTriggerContentUpdateDelay(
- SystemHealthParams.ATTRIBUTION_JOB_TRIGGERING_DELAY_MS)
- .build();
+ final JobInfo job =
+ new JobInfo.Builder(
+ AdServicesConfig.MEASUREMENT_ATTRIBUTION_JOB_ID,
+ new ComponentName(context, AttributionJobService.class))
+ .addTriggerContentUri(
+ new JobInfo.TriggerContentUri(
+ TriggerContentProvider.TRIGGER_URI,
+ JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS))
+ .setTriggerContentUpdateDelay(
+ SystemHealthParams.ATTRIBUTION_JOB_TRIGGERING_DELAY_MS)
+ .setPersisted(false) // Can't call addTriggerContentUri() on a persisted job
+ .build();
jobScheduler.schedule(job);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/SourceFetcher.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncSourceFetcher.java
index 7962ebb2f..33c0a5324 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/SourceFetcher.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncSourceFetcher.java
@@ -25,27 +25,28 @@ import static com.android.adservices.service.measurement.SystemHealthParams.MAX_
import static com.android.adservices.service.measurement.util.BaseUriExtractor.getBaseUri;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE;
-import android.adservices.measurement.RegistrationRequest;
-import android.adservices.measurement.WebSourceParams;
-import android.adservices.measurement.WebSourceRegistrationRequest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.Uri;
import com.android.adservices.LogUtil;
-import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.EventSurfaceType;
import com.android.adservices.service.measurement.MeasurementHttpClient;
+import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
import com.android.adservices.service.measurement.util.Enrollment;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import com.android.adservices.service.measurement.util.Web;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.internal.annotations.VisibleForTesting;
-import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -59,18 +60,15 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
+import java.util.concurrent.TimeUnit;
/**
* Download and decode Response Based Registration
*
* @hide
*/
-public class SourceFetcher {
- private final ExecutorService mIoExecutor = AdServicesExecutors.getBlockingExecutor();
+public class AsyncSourceFetcher {
+
private final String mDefaultAndroidAppScheme = "android-app";
private final String mDefaultAndroidAppUriPrefix = mDefaultAndroidAppScheme + "://";
private final MeasurementHttpClient mNetworkConnection = new MeasurementHttpClient();
@@ -78,7 +76,7 @@ public class SourceFetcher {
private final Flags mFlags;
private final AdServicesLogger mLogger;
- public SourceFetcher(Context context) {
+ public AsyncSourceFetcher(Context context) {
this(
EnrollmentDao.getInstance(context),
FlagsFactory.getFlags(),
@@ -86,10 +84,7 @@ public class SourceFetcher {
}
@VisibleForTesting
- public SourceFetcher(
- EnrollmentDao enrollmentDao,
- Flags flags,
- AdServicesLogger logger) {
+ public AsyncSourceFetcher(EnrollmentDao enrollmentDao, Flags flags, AdServicesLogger logger) {
mEnrollmentDao = enrollmentDao;
mFlags = flags;
mLogger = logger;
@@ -99,8 +94,9 @@ public class SourceFetcher {
@NonNull JSONObject json,
@Nullable Uri appDestinationFromRequest,
@Nullable Uri webDestinationFromRequest,
+ long sourceEventTime,
boolean shouldValidateDestination,
- SourceRegistration.Builder result,
+ Source.Builder result,
boolean isWebSource,
boolean isAllowDebugKey,
boolean isAdIdPermissionGranted)
@@ -111,27 +107,31 @@ public class SourceFetcher {
String.format(
"Expected %s and a destination", SourceHeaderContract.SOURCE_EVENT_ID));
}
-
- result.setSourceEventId(json.getLong(SourceHeaderContract.SOURCE_EVENT_ID));
+ result.setEventId(new UnsignedLong(json.getString(SourceHeaderContract.SOURCE_EVENT_ID)));
if (!json.isNull(SourceHeaderContract.EXPIRY)) {
long expiry =
extractValidNumberInRange(
json.getLong(SourceHeaderContract.EXPIRY),
MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS,
MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
- result.setExpiry(expiry);
+ result.setExpiryTime(sourceEventTime + TimeUnit.SECONDS.toMillis(expiry));
+ } else {
+ result.setExpiryTime(
+ sourceEventTime
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS));
}
if (!json.isNull(SourceHeaderContract.PRIORITY)) {
- result.setSourcePriority(json.getLong(SourceHeaderContract.PRIORITY));
+ result.setPriority(json.getLong(SourceHeaderContract.PRIORITY));
}
boolean isWebAllow = isWebSource && isAllowDebugKey && isAdIdPermissionGranted;
boolean isAppAllow = !isWebSource && isAdIdPermissionGranted;
if (!json.isNull(SourceHeaderContract.DEBUG_KEY) && (isWebAllow || isAppAllow)) {
try {
result.setDebugKey(
- Long.parseUnsignedLong(json.getString(SourceHeaderContract.DEBUG_KEY)));
+ new UnsignedLong(json.getString(SourceHeaderContract.DEBUG_KEY)));
} catch (NumberFormatException e) {
- LogUtil.e(e, "Parsing source debug key failed");
+ LogUtil.e(e, "parseCommonSourceParams: parsing debug key failed");
}
}
if (!json.isNull(SourceHeaderContract.INSTALL_ATTRIBUTION_WINDOW_KEY)) {
@@ -140,7 +140,10 @@ public class SourceFetcher {
json.getLong(SourceHeaderContract.INSTALL_ATTRIBUTION_WINDOW_KEY),
MIN_INSTALL_ATTRIBUTION_WINDOW,
MAX_INSTALL_ATTRIBUTION_WINDOW);
- result.setInstallAttributionWindow(installAttributionWindow);
+ result.setInstallAttributionWindow(TimeUnit.SECONDS.toMillis(installAttributionWindow));
+ } else {
+ result.setInstallAttributionWindow(
+ TimeUnit.SECONDS.toMillis(MAX_INSTALL_ATTRIBUTION_WINDOW));
}
if (!json.isNull(SourceHeaderContract.POST_INSTALL_EXCLUSIVITY_WINDOW_KEY)) {
long installCooldownWindow =
@@ -148,9 +151,11 @@ public class SourceFetcher {
json.getLong(SourceHeaderContract.POST_INSTALL_EXCLUSIVITY_WINDOW_KEY),
MIN_POST_INSTALL_EXCLUSIVITY_WINDOW,
MAX_POST_INSTALL_EXCLUSIVITY_WINDOW);
- result.setInstallCooldownWindow(installCooldownWindow);
+ result.setInstallCooldownWindow(TimeUnit.SECONDS.toMillis(installCooldownWindow));
+ } else {
+ result.setInstallCooldownWindow(
+ TimeUnit.SECONDS.toMillis(MIN_POST_INSTALL_EXCLUSIVITY_WINDOW));
}
-
// This "filter_data" field is used to generate reports.
if (!json.isNull(SourceHeaderContract.FILTER_DATA)) {
if (!FetcherUtil.areValidAttributionFilters(
@@ -158,43 +163,36 @@ public class SourceFetcher {
LogUtil.d("Source filter-data is invalid.");
return false;
}
- result.setAggregateFilterData(
+ result.setFilterData(
json.getJSONObject(SourceHeaderContract.FILTER_DATA).toString());
}
-
if (!json.isNull(SourceHeaderContract.DESTINATION)) {
Uri appUri = Uri.parse(json.getString(SourceHeaderContract.DESTINATION));
if (appUri.getScheme() == null) {
LogUtil.d("App destination is missing app scheme, adding.");
appUri = Uri.parse(mDefaultAndroidAppUriPrefix + appUri);
}
-
if (!mDefaultAndroidAppScheme.equals(appUri.getScheme())) {
LogUtil.e(
"Invalid scheme for app destination: %s; dropping the source.",
appUri.getScheme());
return false;
}
-
if (appDestinationFromRequest != null && !appDestinationFromRequest.equals(appUri)) {
LogUtil.d("Expected destination to match with the supplied one!");
return false;
}
-
result.setAppDestination(getBaseUri(appUri));
}
-
if (shouldValidateDestination
&& !doUriFieldsMatch(
json, SourceHeaderContract.WEB_DESTINATION, webDestinationFromRequest)) {
LogUtil.d("Expected web_destination to match with ths supplied one!");
return false;
}
-
if (!json.isNull(SourceHeaderContract.WEB_DESTINATION)) {
Uri webDestination = Uri.parse(json.getString(SourceHeaderContract.WEB_DESTINATION));
- Optional<Uri> topPrivateDomainAndScheme =
- Web.topPrivateDomainAndScheme(webDestination);
+ Optional<Uri> topPrivateDomainAndScheme = Web.topPrivateDomainAndScheme(webDestination);
if (!topPrivateDomainAndScheme.isPresent()) {
LogUtil.d("Unable to extract top private domain and scheme from web destination.");
return false;
@@ -202,62 +200,36 @@ public class SourceFetcher {
result.setWebDestination(topPrivateDomainAndScheme.get());
}
}
-
return true;
}
- private boolean hasRequiredParams(JSONObject json, boolean shouldValidateDestinations) {
- boolean isDestinationAvailable;
- if (shouldValidateDestinations) {
- // This is multiple-destinations case (web or app). At least one of them should be
- // available.
- isDestinationAvailable =
- !json.isNull(SourceHeaderContract.DESTINATION)
- || !json.isNull(SourceHeaderContract.WEB_DESTINATION);
- } else {
- isDestinationAvailable = !json.isNull(SourceHeaderContract.DESTINATION);
- }
-
- return !json.isNull(SourceHeaderContract.SOURCE_EVENT_ID) && isDestinationAvailable;
- }
-
- private boolean doUriFieldsMatch(JSONObject json, String fieldName, Uri expectedValue)
- throws JSONException {
- if (json.isNull(fieldName) && expectedValue == null) {
- return true;
- }
-
- return !json.isNull(fieldName)
- && Objects.equals(expectedValue, Uri.parse(json.getString(fieldName)));
- }
-
- private long extractValidNumberInRange(long value, long lowerLimit, long upperLimit) {
- if (value < lowerLimit) {
- return lowerLimit;
- } else if (value > upperLimit) {
- return upperLimit;
- }
-
- return value;
- }
-
private boolean parseSource(
- @NonNull Uri topOrigin,
+ @NonNull Uri publisher,
@NonNull String enrollmentId,
@Nullable Uri appDestination,
@Nullable Uri webDestination,
+ @Nullable Uri registrant,
+ long eventTime,
+ @Nullable Source.SourceType sourceType,
boolean shouldValidateDestination,
@NonNull Map<String, List<String>> headers,
- @NonNull List<SourceRegistration> addToResults,
+ @NonNull List<Source> sources,
boolean isWebSource,
boolean isAllowDebugKey,
boolean isAdIdPermissionGranted) {
- SourceRegistration.Builder result = new SourceRegistration.Builder();
- result.setTopOrigin(topOrigin);
+ Source.Builder result = new Source.Builder();
+ result.setPublisher(publisher);
result.setEnrollmentId(enrollmentId);
- List<String> field;
- field = headers.get("Attribution-Reporting-Register-Source");
+ result.setRegistrant(registrant);
+ result.setSourceType(sourceType);
+ result.setAttributionMode(Source.AttributionMode.TRUTHFULLY);
+ result.setEventTime(eventTime);
+ result.setPublisherType(isWebSource ? EventSurfaceType.WEB : EventSurfaceType.APP);
+ List<String> field = headers.get("Attribution-Reporting-Register-Source");
if (field == null || field.size() != 1) {
+ LogUtil.d(
+ "AsyncSourceFetcher: "
+ + "Invalid Attribution-Reporting-Register-Source header.");
return false;
}
try {
@@ -267,6 +239,7 @@ public class SourceFetcher {
json,
appDestination,
webDestination,
+ eventTime,
shouldValidateDestination,
result,
isWebSource,
@@ -277,21 +250,45 @@ public class SourceFetcher {
}
if (!json.isNull(SourceHeaderContract.AGGREGATION_KEYS)) {
if (!areValidAggregationKeys(
- json.getJSONArray(SourceHeaderContract.AGGREGATION_KEYS))) {
+ json.getJSONObject(SourceHeaderContract.AGGREGATION_KEYS))) {
return false;
}
result.setAggregateSource(json.getString(SourceHeaderContract.AGGREGATION_KEYS));
}
- synchronized (addToResults) {
- addToResults.add(result.build());
- }
+ sources.add(result.build());
return true;
- } catch (JSONException e) {
- LogUtil.d(e, "Invalid JSON");
+ } catch (JSONException | NumberFormatException e) {
+ LogUtil.d(e, "AsyncSourceFetcher: Invalid JSON");
return false;
}
}
+ private static boolean hasRequiredParams(JSONObject json, boolean shouldValidateDestinations) {
+ boolean isDestinationAvailable = !json.isNull(SourceHeaderContract.DESTINATION);
+ if (shouldValidateDestinations) {
+ isDestinationAvailable |= !json.isNull(SourceHeaderContract.WEB_DESTINATION);
+ }
+ return !json.isNull(SourceHeaderContract.SOURCE_EVENT_ID) && isDestinationAvailable;
+ }
+
+ private static boolean doUriFieldsMatch(JSONObject json, String fieldName, Uri expectedValue)
+ throws JSONException {
+ if (json.isNull(fieldName) && expectedValue == null) {
+ return true;
+ }
+ return !json.isNull(fieldName)
+ && Objects.equals(expectedValue, Uri.parse(json.getString(fieldName)));
+ }
+
+ private static long extractValidNumberInRange(long value, long lowerLimit, long upperLimit) {
+ if (value < lowerLimit) {
+ return lowerLimit;
+ } else if (value > upperLimit) {
+ return upperLimit;
+ }
+ return value;
+ }
+
/** Provided a testing hook. */
@NonNull
@VisibleForTesting
@@ -299,20 +296,61 @@ public class SourceFetcher {
return mNetworkConnection.setup(url);
}
+ /**
+ * Fetch a source type registration.
+ *
+ * @param asyncRegistration a {@link AsyncRegistration}, a request the record.
+ * @param asyncFetchStatus a {@link AsyncFetchStatus}, stores Ad Tech server status.
+ */
+ public Optional<Source> fetchSource(
+ @NonNull AsyncRegistration asyncRegistration,
+ @NonNull AsyncFetchStatus asyncFetchStatus,
+ AsyncRedirect asyncRedirect) {
+ List<Source> out = new ArrayList<>();
+ fetchSource(
+ asyncRegistration.getTopOrigin(),
+ asyncRegistration.getRegistrationUri(),
+ asyncRegistration.getOsDestination(),
+ asyncRegistration.getWebDestination(),
+ asyncRegistration.getRegistrant(),
+ asyncRegistration.getRequestTime(),
+ asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistration.getSourceType(),
+ out,
+ asyncRegistration.shouldProcessRedirects(),
+ asyncRegistration.getRedirectType(),
+ asyncRedirect,
+ asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistration.getDebugKeyAllowed(),
+ asyncFetchStatus,
+ asyncRegistration.getDebugKeyAllowed());
+ if (out.isEmpty()) {
+ return Optional.empty();
+ } else {
+ return Optional.of(out.get(0));
+ }
+ }
+
private void fetchSource(
- @NonNull Uri topOrigin,
+ @NonNull Uri publisher,
@NonNull Uri registrationUri,
@Nullable Uri appDestination,
@Nullable Uri webDestination,
+ @Nullable Uri registrant,
+ long eventTime,
boolean shouldValidateDestination,
- @NonNull String sourceType,
+ @NonNull Source.SourceType sourceType,
+ @NonNull List<Source> sourceOut,
boolean shouldProcessRedirects,
- @NonNull List<SourceRegistration> registrationsOut,
+ @AsyncRegistration.RedirectType int redirectType,
+ @NonNull AsyncRedirect asyncRedirect,
boolean isWebSource,
boolean isAllowDebugKey,
+ @Nullable AsyncFetchStatus asyncFetchStatus,
boolean isAdIdPermissionGranted) {
// Require https.
if (!registrationUri.getScheme().equals("https")) {
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
return;
}
URL url;
@@ -325,7 +363,9 @@ public class SourceFetcher {
Optional<String> enrollmentId =
Enrollment.maybeGetEnrollmentId(registrationUri, mEnrollmentDao);
if (!enrollmentId.isPresent()) {
- LogUtil.d("fetchSource: unable to find enrollment ID. Registration URI: %s",
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT);
+ LogUtil.d(
+ "fetchSource: unable to find enrollment ID. Registration URI: %s",
registrationUri);
return;
}
@@ -333,217 +373,76 @@ public class SourceFetcher {
try {
urlConnection = (HttpURLConnection) openUrl(url);
} catch (IOException e) {
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
LogUtil.e(e, "Failed to open registration target URL");
return;
}
try {
urlConnection.setRequestMethod("POST");
- urlConnection.setRequestProperty("Attribution-Reporting-Source-Info", sourceType);
+ urlConnection.setRequestProperty(
+ "Attribution-Reporting-Source-Info", sourceType.toString());
urlConnection.setInstanceFollowRedirects(false);
Map<String, List<String>> headers = urlConnection.getHeaderFields();
-
FetcherUtil.emitHeaderMetrics(
mFlags,
mLogger,
AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
headers,
registrationUri);
-
int responseCode = urlConnection.getResponseCode();
- if (!FetcherUtil.isRedirect(responseCode)
- && !FetcherUtil.isSuccess(responseCode)) {
+ if (!FetcherUtil.isRedirect(responseCode) && !FetcherUtil.isSuccess(responseCode)) {
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
return;
}
-
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
final boolean parsed =
parseSource(
- topOrigin,
+ publisher,
enrollmentId.get(),
appDestination,
webDestination,
+ registrant,
+ eventTime,
+ sourceType,
shouldValidateDestination,
headers,
- registrationsOut,
+ sourceOut,
isWebSource,
isAllowDebugKey,
isAdIdPermissionGranted);
if (!parsed) {
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
LogUtil.d("Failed to parse");
return;
}
-
if (shouldProcessRedirects) {
- List<Uri> redirects = FetcherUtil.parseRedirects(headers);
- if (!redirects.isEmpty()) {
- processAsyncRedirects(
- redirects,
- topOrigin,
- sourceType,
- registrationsOut,
- isWebSource,
- isAllowDebugKey,
- isAdIdPermissionGranted);
- }
+ AsyncRedirect redirectsAndType = FetcherUtil.parseRedirects(headers, redirectType);
+ asyncRedirect.addToRedirects(redirectsAndType.getRedirects());
+ asyncRedirect.setRedirectType(redirectsAndType.getRedirectType());
+ } else {
+ asyncRedirect.setRedirectType(redirectType);
}
} catch (IOException e) {
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
LogUtil.e(e, "Failed to get registration response");
} finally {
urlConnection.disconnect();
}
}
- private void processAsyncRedirects(
- List<Uri> redirects,
- Uri topOrigin,
- String sourceInfo,
- List<SourceRegistration> registrationsOut,
- boolean isWebSource,
- boolean isAllowDebugKey,
- boolean isAdIdPermissionGranted) {
- try {
- Function<Uri, CompletableFuture<Void>> fetchSourceFromRedirectFuture =
- redirect ->
- CompletableFuture.runAsync(
- () ->
- fetchSource(
- topOrigin,
- redirect,
- /* appDestination */ null,
- /* webDestination */ null,
- /* shouldValidateDestination*/ false,
- sourceInfo,
- /*shouldProcessRedirects*/ false,
- registrationsOut,
- isWebSource,
- isAllowDebugKey,
- isAdIdPermissionGranted),
- mIoExecutor);
- CompletableFuture.allOf(
- redirects.stream()
- .map(fetchSourceFromRedirectFuture)
- .toArray(CompletableFuture<?>[]::new))
- .get();
- } catch (InterruptedException | ExecutionException e) {
- LogUtil.e(e, "Failed to process source redirection");
- }
- }
-
- /** Fetch an attribution source type registration. */
- public Optional<List<SourceRegistration>> fetchSource(@NonNull RegistrationRequest request) {
- if (request.getRegistrationType()
- != RegistrationRequest.REGISTER_SOURCE) {
- throw new IllegalArgumentException("Expected source registration");
- }
- List<SourceRegistration> out = new ArrayList<>();
- fetchSource(
- request.getTopOriginUri(),
- request.getRegistrationUri(),
- null,
- null,
- false,
- request.getInputEvent() == null ? "event" : "navigation",
- true,
- out,
- false,
- false,
- request.isAdIdPermissionGranted());
- if (out.isEmpty()) {
- return Optional.empty();
- } else {
- return Optional.of(out);
- }
- }
-
- /** Fetch an attribution source type registration. */
- public Optional<List<SourceRegistration>> fetchWebSources(
- @NonNull WebSourceRegistrationRequest request, boolean isAdIdPermissionGranted) {
- List<SourceRegistration> out = new ArrayList<>();
- processWebSourcesFetch(
- request.getTopOriginUri(),
- request.getSourceParams(),
- request.getAppDestination(),
- request.getWebDestination(),
- request.getInputEvent() == null ? "event" : "navigation",
- out,
- isAdIdPermissionGranted);
- if (out.isEmpty()) {
- return Optional.empty();
- } else {
- return Optional.of(out);
- }
- }
-
- private void processWebSourcesFetch(
- Uri topOrigin,
- List<WebSourceParams> sourceParamsList,
- Uri appDestination,
- Uri webDestination,
- String sourceType,
- List<SourceRegistration> registrationsOut,
- boolean isAdIdPermissionGranted) {
- try {
- CompletableFuture.allOf(
- sourceParamsList.stream()
- .map(
- sourceParams ->
- createFutureToFetchWebSource(
- topOrigin,
- appDestination,
- webDestination,
- sourceType,
- registrationsOut,
- sourceParams,
- isAdIdPermissionGranted))
- .toArray(CompletableFuture<?>[]::new))
- .get();
- } catch (InterruptedException | ExecutionException e) {
- LogUtil.e(e, "Failed to process source redirection");
- }
- }
-
- private CompletableFuture<Void> createFutureToFetchWebSource(
- Uri topOrigin,
- Uri appDestination,
- Uri webDestination,
- String sourceType,
- List<SourceRegistration> registrationsOut,
- WebSourceParams sourceParams,
- boolean isAdIdPermissionGranted) {
- return CompletableFuture.runAsync(
- () ->
- fetchSource(
- topOrigin,
- sourceParams.getRegistrationUri(),
- appDestination,
- webDestination,
- /* shouldValidateDestination */ true,
- sourceType,
- /* shouldProcessRedirects*/ false,
- registrationsOut,
- true,
- sourceParams.isDebugKeyAllowed(),
- isAdIdPermissionGranted),
- mIoExecutor);
- }
-
- private static boolean areValidAggregationKeys(JSONArray aggregationKeys) {
+ private boolean areValidAggregationKeys(JSONObject aggregationKeys) {
if (aggregationKeys.length() > MAX_AGGREGATE_KEYS_PER_REGISTRATION) {
- LogUtil.d("Aggregation-keys have more entries than permitted. %s",
+ LogUtil.d(
+ "Aggregation-keys have more entries than permitted. %s",
aggregationKeys.length());
return false;
}
- for (int i = 0; i < aggregationKeys.length(); i++) {
- JSONObject keyObj = aggregationKeys.optJSONObject(i);
- if (keyObj == null) {
- LogUtil.d("SourceFetcher: aggregation key failed to parse.");
- return false;
- }
- String id = keyObj.optString("id");
+ for (String id : aggregationKeys.keySet()) {
if (!FetcherUtil.isValidAggregateKeyId(id)) {
LogUtil.d("SourceFetcher: aggregation key ID is invalid. %s", id);
return false;
}
- String keyPiece = keyObj.optString("key_piece");
+ String keyPiece = aggregationKeys.optString(id);
if (!FetcherUtil.isValidAggregateKeyPiece(keyPiece)) {
LogUtil.d("SourceFetcher: aggregation key-piece is invalid. %s", keyPiece);
return false;
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/TriggerFetcher.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncTriggerFetcher.java
index 737dc2450..9871357e3 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/TriggerFetcher.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncTriggerFetcher.java
@@ -20,20 +20,23 @@ import static com.android.adservices.service.measurement.SystemHealthParams.MAX_
import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_EVENT_TRIGGER_DATA;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER;
-import android.adservices.measurement.RegistrationRequest;
-import android.adservices.measurement.WebTriggerParams;
-import android.adservices.measurement.WebTriggerRegistrationRequest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.Uri;
import com.android.adservices.LogUtil;
-import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.EventSurfaceType;
import com.android.adservices.service.measurement.MeasurementHttpClient;
+import com.android.adservices.service.measurement.Trigger;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
import com.android.adservices.service.measurement.util.Enrollment;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.internal.annotations.VisibleForTesting;
@@ -52,34 +55,26 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
/**
* Download and decode Trigger registration.
*
* @hide
*/
-public class TriggerFetcher {
- private final ExecutorService mIoExecutor = AdServicesExecutors.getBlockingExecutor();
+public class AsyncTriggerFetcher {
+
private final MeasurementHttpClient mNetworkConnection = new MeasurementHttpClient();
private final EnrollmentDao mEnrollmentDao;
private final Flags mFlags;
private final AdServicesLogger mLogger;
-
- public TriggerFetcher(Context context) {
+ public AsyncTriggerFetcher(Context context) {
this(
EnrollmentDao.getInstance(context),
FlagsFactory.getFlags(),
AdServicesLoggerImpl.getInstance());
}
-
@VisibleForTesting
- public TriggerFetcher(
- EnrollmentDao enrollmentDao,
- Flags flags,
- AdServicesLogger logger) {
+ public AsyncTriggerFetcher(EnrollmentDao enrollmentDao, Flags flags, AdServicesLogger logger) {
mEnrollmentDao = enrollmentDao;
mFlags = flags;
mLogger = logger;
@@ -87,28 +82,41 @@ public class TriggerFetcher {
private boolean parseTrigger(
@NonNull Uri topOrigin,
+ @NonNull Uri registrant,
@NonNull String enrollmentId,
+ long triggerTime,
@NonNull Map<String, List<String>> headers,
- @NonNull List<TriggerRegistration> addToResults,
+ @NonNull List<Trigger> triggers,
boolean isWebSource,
boolean isAllowDebugKey,
- boolean isAdIdPermissionGranted) {
- TriggerRegistration.Builder result = new TriggerRegistration.Builder();
- result.setTopOrigin(topOrigin);
+ boolean isAdIdPermissionGranted,
+ AsyncRegistration.RegistrationType registrationType) {
+ Trigger.Builder result = new Trigger.Builder();
result.setEnrollmentId(enrollmentId);
- List<String> field;
- field = headers.get("Attribution-Reporting-Register-Trigger");
+ result.setAttributionDestination(topOrigin);
+ result.setRegistrant(registrant);
+ result.setDestinationType(
+ registrationType == AsyncRegistration.RegistrationType.WEB_TRIGGER
+ ? EventSurfaceType.WEB
+ : EventSurfaceType.APP);
+ result.setTriggerTime(triggerTime);
+ List<String> field = headers.get("Attribution-Reporting-Register-Trigger");
if (field == null || field.size() != 1) {
+ LogUtil.d(
+ "AsyncSourceFetcher: "
+ + "Invalid Attribution-Reporting-Register-Source header.");
return false;
}
try {
JSONObject json = new JSONObject(field.get(0));
if (!json.isNull(TriggerHeaderContract.EVENT_TRIGGER_DATA)) {
- if (!isValidEventTriggerData(
- json.getJSONArray(TriggerHeaderContract.EVENT_TRIGGER_DATA))) {
+ Optional<String> validEventTriggerData =
+ getValidEventTriggerData(
+ json.getJSONArray(TriggerHeaderContract.EVENT_TRIGGER_DATA));
+ if (!validEventTriggerData.isPresent()) {
return false;
}
- result.setEventTriggers(json.getString(TriggerHeaderContract.EVENT_TRIGGER_DATA));
+ result.setEventTriggers(validEventTriggerData.get());
}
if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)) {
if (!isValidAggregateTriggerData(
@@ -127,23 +135,35 @@ public class TriggerFetcher {
json.getString(TriggerHeaderContract.AGGREGATABLE_VALUES));
}
if (!json.isNull(TriggerHeaderContract.FILTERS)) {
- result.setFilters(json.getString(TriggerHeaderContract.FILTERS));
+ JSONObject filters = json.optJSONObject(TriggerHeaderContract.FILTERS);
+ if (!FetcherUtil.areValidAttributionFilters(filters)) {
+ LogUtil.d("parseTrigger: filters are invalid.");
+ return false;
+ }
+ result.setFilters(filters.toString());
+ }
+ if (!json.isNull(TriggerHeaderContract.NOT_FILTERS)) {
+ JSONObject notFilters = json.optJSONObject(TriggerHeaderContract.NOT_FILTERS);
+ if (!FetcherUtil.areValidAttributionFilters(notFilters)) {
+ LogUtil.d("parseTrigger: not-filters are invalid.");
+ return false;
+ }
+ result.setNotFilters(notFilters.toString());
}
boolean isWebAllow = isWebSource && isAllowDebugKey && isAdIdPermissionGranted;
boolean isAppAllow = !isWebSource && isAdIdPermissionGranted;
if (!json.isNull(TriggerHeaderContract.DEBUG_KEY) && (isWebAllow || isAppAllow)) {
try {
result.setDebugKey(
- Long.parseUnsignedLong(
- json.getString(TriggerHeaderContract.DEBUG_KEY)));
+ new UnsignedLong(json.getString(TriggerHeaderContract.DEBUG_KEY)));
} catch (NumberFormatException e) {
LogUtil.e(e, "Parsing trigger debug key failed");
}
}
- addToResults.add(result.build());
+ triggers.add(result.build());
return true;
} catch (JSONException e) {
- LogUtil.e(e, "Trigger Parsing failed");
+ LogUtil.e("Trigger Parsing failed", e);
return false;
}
}
@@ -157,26 +177,35 @@ public class TriggerFetcher {
private void fetchTrigger(
@NonNull Uri topOrigin,
@NonNull Uri registrationUri,
+ Uri registrant,
+ long triggerTime,
boolean shouldProcessRedirects,
- @NonNull List<TriggerRegistration> registrationsOut,
+ @AsyncRegistration.RedirectType int redirectType,
+ @NonNull AsyncRedirect asyncRedirect,
+ @NonNull List<Trigger> triggerOut,
boolean isWebSource,
boolean isAllowDebugKey,
- boolean isAdIdPermissionGranted) {
+ @Nullable AsyncFetchStatus asyncFetchStatus,
+ boolean isAdIdPermissionGranted,
+ AsyncRegistration.RegistrationType registrationType) {
// Require https.
if (!registrationUri.getScheme().equals("https")) {
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
return;
}
URL url;
try {
url = new URL(registrationUri.toString());
} catch (MalformedURLException e) {
- LogUtil.d(e, "Malformed registration registrationUri URL");
+ LogUtil.d(e, "Malformed registration target URL");
return;
}
Optional<String> enrollmentId =
Enrollment.maybeGetEnrollmentId(registrationUri, mEnrollmentDao);
if (!enrollmentId.isPresent()) {
- LogUtil.d("fetchTrigger: unable to find enrollment ID. Registration URI: %s",
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT);
+ LogUtil.d(
+ "fetchTrigger: unable to find enrollment ID. Registration URI: %s",
registrationUri);
return;
}
@@ -184,55 +213,50 @@ public class TriggerFetcher {
try {
urlConnection = (HttpURLConnection) openUrl(url);
} catch (IOException e) {
- LogUtil.d(e, "Failed to open registration registrationUri URL");
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ LogUtil.d(e, "Failed to open registration target URL");
return;
}
try {
urlConnection.setRequestMethod("POST");
urlConnection.setInstanceFollowRedirects(false);
Map<String, List<String>> headers = urlConnection.getHeaderFields();
-
FetcherUtil.emitHeaderMetrics(
mFlags,
mLogger,
AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
headers,
registrationUri);
-
int responseCode = urlConnection.getResponseCode();
LogUtil.d("Response code = " + responseCode);
-
- if (!FetcherUtil.isRedirect(responseCode)
- && !FetcherUtil.isSuccess(responseCode)) {
+ if (!FetcherUtil.isRedirect(responseCode) && !FetcherUtil.isSuccess(responseCode)) {
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
return;
}
-
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
final boolean parsed =
parseTrigger(
topOrigin,
+ registrant,
enrollmentId.get(),
+ triggerTime,
headers,
- registrationsOut,
+ triggerOut,
isWebSource,
isAllowDebugKey,
- isAdIdPermissionGranted);
+ isAdIdPermissionGranted,
+ registrationType);
if (!parsed) {
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
LogUtil.d("Failed to parse.");
return;
}
-
if (shouldProcessRedirects) {
- List<Uri> redirects = FetcherUtil.parseRedirects(headers);
- for (Uri redirect : redirects) {
- fetchTrigger(
- topOrigin,
- redirect,
- false,
- registrationsOut,
- isWebSource,
- isAllowDebugKey,
- isAdIdPermissionGranted);
- }
+ AsyncRedirect redirectsAndType = FetcherUtil.parseRedirects(headers, redirectType);
+ asyncRedirect.addToRedirects(redirectsAndType.getRedirects());
+ asyncRedirect.setRedirectType(redirectsAndType.getRedirectType());
+ } else {
+ asyncRedirect.setRedirectType(redirectType);
}
} catch (IOException e) {
LogUtil.d(e, "Failed to get registration response");
@@ -242,92 +266,110 @@ public class TriggerFetcher {
}
}
}
-
/**
* Fetch a trigger type registration.
+ *
+ * @param asyncRegistration a {@link AsyncRegistration}, a request the record.
+ * @param asyncFetchStatus a {@link AsyncFetchStatus}, stores Ad Tech server status.
*/
- public Optional<List<TriggerRegistration>> fetchTrigger(@NonNull RegistrationRequest request) {
- if (request.getRegistrationType() != RegistrationRequest.REGISTER_TRIGGER) {
- throw new IllegalArgumentException("Expected trigger registration");
- }
- List<TriggerRegistration> out = new ArrayList<>();
+ public Optional<Trigger> fetchTrigger(
+ @NonNull AsyncRegistration asyncRegistration,
+ @NonNull AsyncFetchStatus asyncFetchStatus,
+ AsyncRedirect asyncRedirect) {
+ List<Trigger> out = new ArrayList<>();
fetchTrigger(
- request.getTopOriginUri(),
- request.getRegistrationUri(),
- true,
+ asyncRegistration.getTopOrigin(),
+ asyncRegistration.getRegistrationUri(),
+ asyncRegistration.getRegistrant(),
+ asyncRegistration.getRequestTime(),
+ asyncRegistration.shouldProcessRedirects(),
+ asyncRegistration.getRedirectType(),
+ asyncRedirect,
out,
- false,
- false,
- request.isAdIdPermissionGranted());
+ asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistration.getDebugKeyAllowed(),
+ asyncFetchStatus,
+ asyncRegistration.getDebugKeyAllowed(),
+ asyncRegistration.getType());
if (out.isEmpty()) {
return Optional.empty();
} else {
- return Optional.of(out);
+ return Optional.of(out.get(0));
}
}
- /** Fetch a trigger type registration without redirects. */
- public Optional<List<TriggerRegistration>> fetchWebTriggers(
- WebTriggerRegistrationRequest request, boolean isAdIdPermissionGranted) {
- List<TriggerRegistration> out = new ArrayList<>();
- processWebTriggersFetch(
- request.getDestination(), request.getTriggerParams(), out, isAdIdPermissionGranted);
+ private Optional<String> getValidEventTriggerData(JSONArray eventTriggerDataArr) {
- if (out.isEmpty()) {
+ if (eventTriggerDataArr.length() > MAX_ATTRIBUTION_EVENT_TRIGGER_DATA) {
+ LogUtil.d(
+ "Event trigger data list has more entries than permitted. %s",
+ eventTriggerDataArr.length());
return Optional.empty();
- } else {
- return Optional.of(out);
}
- }
-
- private void processWebTriggersFetch(
- Uri topOrigin,
- List<WebTriggerParams> triggerParamsList,
- List<TriggerRegistration> registrationsOut,
- boolean isAdIdPermissionGranted) {
- try {
- CompletableFuture.allOf(
- triggerParamsList.stream()
- .map(
- triggerParams ->
- createFutureToFetchWebTrigger(
- topOrigin,
- registrationsOut,
- triggerParams,
- isAdIdPermissionGranted))
- .toArray(CompletableFuture<?>[]::new))
- .get();
- } catch (InterruptedException | ExecutionException e) {
- LogUtil.e(e, "Failed to process source redirection");
+ JSONArray validEventTriggerData = new JSONArray();
+ for (int i = 0; i < eventTriggerDataArr.length(); i++) {
+ JSONObject validEventTriggerDatum = new JSONObject();
+ try {
+ JSONObject eventTriggerDatum = eventTriggerDataArr.getJSONObject(i);
+ // Treat invalid trigger data, priority and deduplication key as if they were not
+ // set.
+ if (!eventTriggerDatum.isNull("trigger_data")) {
+ try {
+ validEventTriggerDatum.put(
+ "trigger_data",
+ new UnsignedLong(eventTriggerDatum.getString("trigger_data")));
+ } catch (NumberFormatException e) {
+ LogUtil.d(e, "getValidEventTriggerData: parsing trigger_data failed.");
+ }
+ }
+ if (!eventTriggerDatum.isNull("priority")) {
+ try {
+ validEventTriggerDatum.put(
+ "priority",
+ String.valueOf(
+ Long.parseLong(eventTriggerDatum.getString("priority"))));
+ } catch (NumberFormatException e) {
+ LogUtil.d(e, "getValidEventTriggerData: parsing priority failed.");
+ }
+ }
+ if (!eventTriggerDatum.isNull("deduplication_key")) {
+ try {
+ validEventTriggerDatum.put(
+ "deduplication_key",
+ new UnsignedLong(eventTriggerDatum.getString("deduplication_key")));
+ } catch (NumberFormatException e) {
+ LogUtil.d(e, "getValidEventTriggerData: parsing deduplication_key failed.");
+ }
+ }
+ if (!eventTriggerDatum.isNull("filters")) {
+ JSONObject filters = eventTriggerDatum.optJSONObject("filters");
+ if (!FetcherUtil.areValidAttributionFilters(filters)) {
+ LogUtil.d("getValidEventTriggerData: filters are invalid.");
+ return Optional.empty();
+ }
+ validEventTriggerDatum.put("filters", filters);
+ }
+ if (!eventTriggerDatum.isNull("not_filters")) {
+ JSONObject notFilters = eventTriggerDatum.optJSONObject("not_filters");
+ if (!FetcherUtil.areValidAttributionFilters(notFilters)) {
+ LogUtil.d("getValidEventTriggerData: not-filters are invalid.");
+ return Optional.empty();
+ }
+ validEventTriggerDatum.put("not_filters", notFilters);
+ }
+ validEventTriggerData.put(validEventTriggerDatum);
+ } catch (JSONException e) {
+ LogUtil.d(e, "AsyncTriggerFetcher: " + "Parsing event trigger datum JSON failed.");
+ }
}
- }
-
- private CompletableFuture<Void> createFutureToFetchWebTrigger(
- Uri topOrigin,
- List<TriggerRegistration> registrationsOut,
- WebTriggerParams triggerParams,
- boolean isAdIdPermissionGranted) {
- return CompletableFuture.runAsync(
- () ->
- fetchTrigger(
- topOrigin,
- triggerParams.getRegistrationUri(),
- /* should process redirects*/ false,
- registrationsOut,
- true,
- triggerParams.isDebugKeyAllowed(),
- isAdIdPermissionGranted),
- mIoExecutor);
- }
-
- private boolean isValidEventTriggerData(JSONArray eventTriggerDataArr) {
- return eventTriggerDataArr.length() <= MAX_ATTRIBUTION_EVENT_TRIGGER_DATA;
+ return Optional.of(validEventTriggerData.toString());
}
private boolean isValidAggregateTriggerData(JSONArray aggregateTriggerDataArr)
throws JSONException {
if (aggregateTriggerDataArr.length() > MAX_AGGREGATABLE_TRIGGER_DATA) {
- LogUtil.d("Aggregate trigger data list has more entries than permitted. %s",
+ LogUtil.d(
+ "Aggregate trigger data list has more entries than permitted. %s",
aggregateTriggerDataArr.length());
return false;
}
@@ -340,8 +382,9 @@ public class TriggerFetcher {
}
JSONArray sourceKeys = aggregateTriggerData.optJSONArray("source_keys");
if (sourceKeys == null || sourceKeys.length() > MAX_AGGREGATE_KEYS_PER_REGISTRATION) {
- LogUtil.d("Aggregate trigger data source-keys list failed to parse or has more"
- + " entries than permitted.");
+ LogUtil.d(
+ "Aggregate trigger data source-keys list failed to parse or has more"
+ + " entries than permitted.");
return false;
}
for (int j = 0; j < sourceKeys.length(); j++) {
@@ -351,8 +394,9 @@ public class TriggerFetcher {
return false;
}
}
- if (!aggregateTriggerData.isNull("filters") && !FetcherUtil.areValidAttributionFilters(
- aggregateTriggerData.optJSONObject("filters"))) {
+ if (!aggregateTriggerData.isNull("filters")
+ && !FetcherUtil.areValidAttributionFilters(
+ aggregateTriggerData.optJSONObject("filters"))) {
LogUtil.d("Aggregate trigger data filters are invalid.");
return false;
}
@@ -368,8 +412,8 @@ public class TriggerFetcher {
private boolean isValidAggregateValues(JSONObject aggregateValues) {
if (aggregateValues.length() > MAX_AGGREGATE_KEYS_PER_REGISTRATION) {
- LogUtil.d("Aggregate values have more keys than permitted. %s",
- aggregateValues.length());
+ LogUtil.d(
+ "Aggregate values have more keys than permitted. %s", aggregateValues.length());
return false;
}
Iterator<String> ids = aggregateValues.keys();
@@ -386,6 +430,7 @@ public class TriggerFetcher {
private interface TriggerHeaderContract {
String EVENT_TRIGGER_DATA = "event_trigger_data";
String FILTERS = "filters";
+ String NOT_FILTERS = "not_filters";
String AGGREGATABLE_TRIGGER_DATA = "aggregatable_trigger_data";
String AGGREGATABLE_VALUES = "aggregatable_values";
String DEBUG_KEY = "debug_key";
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/EnqueueAsyncRegistration.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/EnqueueAsyncRegistration.java
new file mode 100644
index 000000000..ddbcbe469
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/EnqueueAsyncRegistration.java
@@ -0,0 +1,231 @@
+/*
+ * 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.measurement.registration;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.adservices.measurement.WebSourceParams;
+import android.adservices.measurement.WebSourceRegistrationRequest;
+import android.adservices.measurement.WebTriggerParams;
+import android.adservices.measurement.WebTriggerRegistrationRequest;
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.view.InputEvent;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.IMeasurementDao;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.util.Enrollment;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+
+/** Class containing static functions for enqueueing AsyncRegistrations */
+public class EnqueueAsyncRegistration {
+ private static final String ANDROID_APP_SCHEME = "android-app";
+
+ /**
+ * Inserts an App Source or Trigger Registration request into the Async Registration Queue
+ * table.
+ *
+ * @param registrationRequest a {@link RegistrationRequest} to be queued.
+ */
+ public static boolean appSourceOrTriggerRegistrationRequest(
+ RegistrationRequest registrationRequest,
+ Uri registrant,
+ long requestTime,
+ @NonNull EnrollmentDao enrollmentDao,
+ @NonNull DatastoreManager datastoreManager) {
+ Objects.requireNonNull(enrollmentDao);
+ Objects.requireNonNull(datastoreManager);
+ return datastoreManager.runInTransaction(
+ (dao) -> {
+ Optional<String> enrollmentData =
+ Enrollment.maybeGetEnrollmentId(
+ registrationRequest.getRegistrationUri(), enrollmentDao);
+ if (enrollmentData == null || enrollmentData.isEmpty()) {
+ return;
+ }
+ String enrollmentId = enrollmentData.get();
+ insertAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ registrationRequest.getRegistrationUri(),
+ /* mWebDestination */ null,
+ /* mOsDestination */ null,
+ registrant,
+ /* verifiedDestination */ null,
+ registrant,
+ registrationRequest.getRegistrationType()
+ == RegistrationRequest.REGISTER_SOURCE
+ ? AsyncRegistration.RegistrationType.APP_SOURCE
+ : AsyncRegistration.RegistrationType.APP_TRIGGER,
+ registrationRequest.getRegistrationType()
+ == RegistrationRequest.REGISTER_TRIGGER
+ ? null
+ : getSourceType(registrationRequest.getInputEvent()),
+ requestTime,
+ /* mRetryCount */ 0,
+ System.currentTimeMillis(),
+ AsyncRegistration.RedirectType.ANY,
+ registrationRequest.isAdIdPermissionGranted(),
+ dao);
+ });
+ }
+
+ /**
+ * Inserts a Web Source Registration request into the Async Registration Queue table.
+ *
+ * @param webSourceRegistrationRequest a {@link WebSourceRegistrationRequest} to be queued.
+ */
+ public static boolean webSourceRegistrationRequest(
+ WebSourceRegistrationRequest webSourceRegistrationRequest,
+ Uri registrant,
+ long requestTime,
+ @NonNull EnrollmentDao enrollmentDao,
+ @NonNull DatastoreManager datastoreManager) {
+ Objects.requireNonNull(enrollmentDao);
+ Objects.requireNonNull(datastoreManager);
+ return datastoreManager.runInTransaction(
+ (dao) -> {
+ for (WebSourceParams webSourceParams :
+ webSourceRegistrationRequest.getSourceParams()) {
+ Optional<String> enrollmentData =
+ Enrollment.maybeGetEnrollmentId(
+ webSourceParams.getRegistrationUri(), enrollmentDao);
+ if (enrollmentData == null || enrollmentData.isEmpty()) {
+ return;
+ }
+ String enrollmentId = enrollmentData.get();
+
+ insertAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ webSourceParams.getRegistrationUri(),
+ webSourceRegistrationRequest.getWebDestination(),
+ webSourceRegistrationRequest.getAppDestination(),
+ registrant,
+ webSourceRegistrationRequest.getVerifiedDestination(),
+ webSourceRegistrationRequest.getTopOriginUri(),
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ getSourceType(webSourceRegistrationRequest.getInputEvent()),
+ requestTime,
+ /* mRetryCount */ 0,
+ System.currentTimeMillis(),
+ AsyncRegistration.RedirectType.NONE,
+ webSourceParams.isDebugKeyAllowed(),
+ dao);
+ }
+ });
+ }
+
+ /**
+ * Inserts a Web Trigger Registration request into the Async Registration Queue table.
+ *
+ * @param webTriggerRegistrationRequest a {@link WebTriggerRegistrationRequest} to be queued.
+ */
+ public static boolean webTriggerRegistrationRequest(
+ WebTriggerRegistrationRequest webTriggerRegistrationRequest,
+ Uri registrant,
+ long requestTime,
+ @NonNull EnrollmentDao enrollmentDao,
+ @NonNull DatastoreManager datastoreManager) {
+ Objects.requireNonNull(enrollmentDao);
+ Objects.requireNonNull(datastoreManager);
+ return datastoreManager.runInTransaction(
+ (dao) -> {
+ for (WebTriggerParams webTriggerParams :
+ webTriggerRegistrationRequest.getTriggerParams()) {
+ Optional<String> enrollmentData =
+ Enrollment.maybeGetEnrollmentId(
+ webTriggerParams.getRegistrationUri(), enrollmentDao);
+ if (enrollmentData == null || enrollmentData.isEmpty()) {
+ return;
+ }
+ String enrollmentId = enrollmentData.get();
+
+ insertAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ webTriggerParams.getRegistrationUri(),
+ /* mWebDestination */ null,
+ /* mOsDestination */ null,
+ registrant,
+ /* mVerifiedDestination */ null,
+ webTriggerRegistrationRequest.getDestination(),
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ /* mSourceType */ null,
+ requestTime,
+ /* mRetryCount */ 0,
+ System.currentTimeMillis(),
+ AsyncRegistration.RedirectType.NONE,
+ webTriggerParams.isDebugKeyAllowed(),
+ dao);
+ }
+ });
+ }
+
+ private static void insertAsyncRegistration(
+ String iD,
+ String enrollmentId,
+ Uri registrationUri,
+ Uri webDestination,
+ Uri osDestination,
+ Uri registrant,
+ Uri verifiedDestination,
+ Uri topOrigin,
+ AsyncRegistration.RegistrationType registrationType,
+ Source.SourceType sourceType,
+ long mRequestTime,
+ long mRetryCount,
+ long mLastProcessingTime,
+ @AsyncRegistration.RedirectType int redirectType,
+ boolean debugKeyAllowed,
+ @NonNull IMeasurementDao dao)
+ throws DatastoreException {
+ AsyncRegistration asyncRegistration =
+ new AsyncRegistration.Builder()
+ .setId(iD)
+ .setEnrollmentId(enrollmentId)
+ .setRegistrationUri(registrationUri)
+ .setWebDestination(webDestination)
+ .setOsDestination(osDestination)
+ .setRegistrant(registrant)
+ .setVerifiedDestination(verifiedDestination)
+ .setTopOrigin(topOrigin)
+ .setType(registrationType.ordinal())
+ .setSourceType(sourceType)
+ .setRequestTime(mRequestTime)
+ .setRetryCount(mRetryCount)
+ .setLastProcessingTime(mLastProcessingTime)
+ .setRedirectType(redirectType)
+ .setDebugKeyAllowed(debugKeyAllowed)
+ .build();
+
+ dao.insertAsyncRegistration(asyncRegistration);
+ }
+
+ @VisibleForTesting
+ static Source.SourceType getSourceType(InputEvent inputEvent) {
+ return inputEvent == null ? Source.SourceType.EVENT : Source.SourceType.NAVIGATION;
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/FetcherUtil.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/FetcherUtil.java
index 981fe90bc..143ad7a9c 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/FetcherUtil.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/FetcherUtil.java
@@ -18,13 +18,17 @@ package com.android.adservices.service.measurement.registration;
import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
import static com.android.adservices.service.measurement.SystemHealthParams.MAX_BYTES_PER_ATTRIBUTION_AGGREGATE_KEY_ID;
import static com.android.adservices.service.measurement.SystemHealthParams.MAX_BYTES_PER_ATTRIBUTION_FILTER_STRING;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_REDIRECTS_PER_REGISTRATION;
import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
import android.annotation.NonNull;
import android.net.Uri;
+import com.android.adservices.LogUtil;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
import com.android.adservices.service.measurement.util.Web;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
@@ -44,10 +48,8 @@ import java.util.Map;
* @hide
*/
class FetcherUtil {
- /**
- * Limit recursion.
- */
- static final int REDIRECT_LIMIT = 5;
+ static final String REDIRECT_LIST_HEADER_KEY = "Attribution-Reporting-Redirect";
+ static final String REDIRECT_DAISY_CHAIN_HEADER_KEY = "Location";
/**
* Determine all redirects.
@@ -55,16 +57,23 @@ class FetcherUtil {
* <p>Generates a list of: (url, allows_regular_redirects) tuples. Returns true if all steps
* succeed. Returns false if there are any failures.
*/
- static List<Uri> parseRedirects(@NonNull Map<String, List<String>> headers) {
+ static AsyncRedirect parseRedirects(@NonNull Map<String, List<String>> headers,
+ @AsyncRegistration.RedirectType int redirectType) {
List<Uri> redirects = new ArrayList<>();
- List<String> field = headers.get("Attribution-Reporting-Redirect");
- if (field != null) {
- for (int i = 0; i < Math.min(field.size(), REDIRECT_LIMIT); i++) {
- redirects.add(Uri.parse(field.get(i)));
- }
+ switch (redirectType) {
+ case AsyncRegistration.RedirectType.ANY:
+ if (headers.containsKey(REDIRECT_LIST_HEADER_KEY)) {
+ return parseListRedirects(headers, redirects);
+ } else if (headers.containsKey(REDIRECT_DAISY_CHAIN_HEADER_KEY)) {
+ return parseDaisyChainRedirects(headers, redirects);
+ }
+ return new AsyncRedirect(redirects, AsyncRegistration.RedirectType.NONE);
+ case AsyncRegistration.RedirectType.DAISY_CHAIN:
+ return parseDaisyChainRedirects(headers, redirects);
+ case AsyncRegistration.RedirectType.NONE:
+ default:
+ return new AsyncRedirect(redirects, AsyncRegistration.RedirectType.NONE);
}
-
- return redirects;
}
/**
@@ -157,6 +166,29 @@ class FetcherUtil {
.build());
}
+ private static AsyncRedirect parseListRedirects(Map<String, List<String>> headers,
+ List<Uri> redirects) {
+ List<String> field = headers.get("Attribution-Reporting-Redirect");
+ if (field != null) {
+ for (int i = 0; i < Math.min(field.size(), MAX_REDIRECTS_PER_REGISTRATION); i++) {
+ redirects.add(Uri.parse(field.get(i)));
+ }
+ }
+ return new AsyncRedirect(redirects, AsyncRegistration.RedirectType.NONE);
+ }
+
+ private static AsyncRedirect parseDaisyChainRedirects(
+ Map<String, List<String>> headers, List<Uri> redirects) {
+ List<String> field = headers.get("Location");
+ if (field != null) {
+ redirects.add(Uri.parse(field.get(0)));
+ if (field.size() > 1) {
+ LogUtil.d("Expected one Location redirect only, others ignored!");
+ }
+ }
+ return new AsyncRedirect(redirects, AsyncRegistration.RedirectType.DAISY_CHAIN);
+ }
+
private static long calculateHeadersCharactersLength(Map<String, List<String>> headers) {
long size = 0;
for (String headerKey : headers.keySet()) {
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/SourceRegistration.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/SourceRegistration.java
deleted file mode 100644
index 2d0028d10..000000000
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/SourceRegistration.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static com.android.adservices.service.measurement.PrivacyParams.MAX_INSTALL_ATTRIBUTION_WINDOW;
-import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
-import static com.android.adservices.service.measurement.PrivacyParams.MIN_POST_INSTALL_EXCLUSIVITY_WINDOW;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.Uri;
-
-import com.android.adservices.service.measurement.util.Validation;
-
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * A registration for an attribution source.
- */
-public final class SourceRegistration {
- private final Uri mTopOrigin;
- private final String mEnrollmentId;
- private final Uri mAppDestination;
- private final Uri mWebDestination;
- private final long mSourceEventId;
- private final long mExpiry;
- private final long mSourcePriority;
- private final long mInstallAttributionWindow;
- private final long mInstallCooldownWindow;
- @Nullable private final Long mDebugKey;
- private final String mAggregateSource;
- private final String mAggregateFilterData;
-
- /** Create a new source registration. */
- private SourceRegistration(
- @NonNull Uri topOrigin,
- @NonNull String enrollmentId,
- @Nullable Uri appDestination,
- @Nullable Uri webDestination,
- long sourceEventId,
- long expiry,
- long sourcePriority,
- long installAttributionWindow,
- long installCooldownWindow,
- @Nullable Long debugKey,
- @Nullable String aggregateSource,
- @Nullable String aggregateFilterData) {
- mTopOrigin = topOrigin;
- mEnrollmentId = enrollmentId;
- mAppDestination = appDestination;
- mWebDestination = webDestination;
- mSourceEventId = sourceEventId;
- mExpiry = expiry;
- mSourcePriority = sourcePriority;
- mInstallAttributionWindow = installAttributionWindow;
- mInstallCooldownWindow = installCooldownWindow;
- mAggregateSource = aggregateSource;
- mAggregateFilterData = aggregateFilterData;
- mDebugKey = debugKey;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SourceRegistration)) return false;
- SourceRegistration that = (SourceRegistration) o;
- return mSourceEventId == that.mSourceEventId
- && mExpiry == that.mExpiry
- && mSourcePriority == that.mSourcePriority
- && mInstallAttributionWindow == that.mInstallAttributionWindow
- && mInstallCooldownWindow == that.mInstallCooldownWindow
- && Objects.equals(mTopOrigin, that.mTopOrigin)
- && Objects.equals(mEnrollmentId, that.mEnrollmentId)
- && Objects.equals(mAppDestination, that.mAppDestination)
- && Objects.equals(mWebDestination, that.mWebDestination)
- && Objects.equals(mAggregateSource, that.mAggregateSource)
- && Objects.equals(mAggregateFilterData, that.mAggregateFilterData)
- && Objects.equals(mDebugKey, that.mDebugKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- mTopOrigin,
- mEnrollmentId,
- mAppDestination,
- mWebDestination,
- mSourceEventId,
- mExpiry,
- mSourcePriority,
- mInstallAttributionWindow,
- mInstallCooldownWindow,
- mAggregateSource,
- mAggregateFilterData,
- mDebugKey);
- }
-
- /** Top level origin. */
- @NonNull
- public Uri getTopOrigin() {
- return mTopOrigin;
- }
-
- /** Enrollment ID associated with this registration. */
- @NonNull
- public String getEnrollmentId() {
- return mEnrollmentId;
- }
-
- /** OS (app) destination Uri. */
- @Nullable
- public Uri getAppDestination() {
- return mAppDestination;
- }
-
- /** Web destination Uri. */
- @Nullable
- public Uri getWebDestination() {
- return mWebDestination;
- }
-
- /** Source event id. */
- @NonNull
- public long getSourceEventId() {
- return mSourceEventId;
- }
-
- /** Source debug key. */
- public @Nullable Long getDebugKey() {
- return mDebugKey;
- }
-
- /** Expiration. */
- @NonNull
- public long getExpiry() {
- return mExpiry;
- }
-
- /** Source priority. */
- @NonNull
- public long getSourcePriority() {
- return mSourcePriority;
- }
-
- /**
- * Install attribution window.
- */
- public long getInstallAttributionWindow() {
- return mInstallAttributionWindow;
- }
-
- /**
- * Install cooldown window.
- */
- public long getInstallCooldownWindow() {
- return mInstallCooldownWindow;
- }
-
- /**
- * Aggregate source used to generate aggregate report.
- */
- public String getAggregateSource() {
- return mAggregateSource;
- }
-
- /**
- * Aggregate filter data used to generate aggregate report.
- */
- public String getAggregateFilterData() {
- return mAggregateFilterData;
- }
-
- /**
- * A builder for {@link SourceRegistration}.
- */
- public static final class Builder {
- private Uri mTopOrigin;
- private String mEnrollmentId;
- private Uri mAppDestination;
- private Uri mWebDestination;
- private long mSourceEventId;
- private long mExpiry;
- private long mSourcePriority;
- private long mInstallAttributionWindow;
- private long mInstallCooldownWindow;
- private @Nullable Long mDebugKey;
- private String mAggregateSource;
- private String mAggregateFilterData;
-
- public Builder() {
- mExpiry = MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
- mInstallAttributionWindow = MAX_INSTALL_ATTRIBUTION_WINDOW;
- mInstallCooldownWindow = MIN_POST_INSTALL_EXCLUSIVITY_WINDOW;
- }
-
- /** See {@link SourceRegistration#getTopOrigin}. */
- @NonNull
- public Builder setTopOrigin(@NonNull Uri origin) {
- Validation.validateUri(origin);
- mTopOrigin = origin;
- return this;
- }
-
- /** See {@link SourceRegistration#getEnrollmentId}. */
- @NonNull
- public Builder setEnrollmentId(@NonNull String enrollmentId) {
- mEnrollmentId = enrollmentId;
- return this;
- }
-
- /**
- * See {@link SourceRegistration#getAppDestination}. At least one of destination or web
- * destination is required.
- */
- @NonNull
- public Builder setAppDestination(@Nullable Uri appDestination) {
- Optional.ofNullable(appDestination).ifPresent(Validation::validateUri);
- mAppDestination = appDestination;
- return this;
- }
-
- /**
- * See {@link SourceRegistration#getWebDestination()}. At least one of destination or web
- * destination is required.
- */
- @NonNull
- public Builder setWebDestination(@Nullable Uri webDestination) {
- Optional.ofNullable(webDestination).ifPresent(Validation::validateUri);
- mWebDestination = webDestination;
- return this;
- }
-
- /** See {@link SourceRegistration#getSourceEventId}. */
- @NonNull
- public Builder setSourceEventId(long sourceEventId) {
- mSourceEventId = sourceEventId;
- return this;
- }
-
- /** See {@link SourceRegistration#getDebugKey()}. */
- public @NonNull Builder setDebugKey(@Nullable Long debugKey) {
- mDebugKey = debugKey;
- return this;
- }
-
- /** See {@link SourceRegistration#getExpiry}. */
- @NonNull
- public Builder setExpiry(long expiry) {
- mExpiry = expiry;
- return this;
- }
-
- /** See {@link SourceRegistration#getSourcePriority}. */
- @NonNull
- public Builder setSourcePriority(long priority) {
- mSourcePriority = priority;
- return this;
- }
-
- /** See {@link SourceRegistration#getInstallAttributionWindow()}. */
- @NonNull
- public Builder setInstallAttributionWindow(long installAttributionWindow) {
- mInstallAttributionWindow = installAttributionWindow;
- return this;
- }
-
- /** See {@link SourceRegistration#getInstallCooldownWindow()}. */
- @NonNull
- public Builder setInstallCooldownWindow(long installCooldownWindow) {
- mInstallCooldownWindow = installCooldownWindow;
- return this;
- }
-
- /** See {@link SourceRegistration#getAggregateSource()}. */
- @NonNull
- public Builder setAggregateSource(@Nullable String aggregateSource) {
- mAggregateSource = aggregateSource;
- return this;
- }
-
- /** See {@link SourceRegistration#getAggregateFilterData()}. */
- @NonNull
- public Builder setAggregateFilterData(@Nullable String aggregateFilterData) {
- mAggregateFilterData = aggregateFilterData;
- return this;
- }
-
- /** Build the SourceRegistration. */
- @NonNull
- public SourceRegistration build() {
- Validation.validateNonNull(mTopOrigin, mEnrollmentId);
-
- if (mAppDestination == null && mWebDestination == null) {
- throw new IllegalArgumentException(
- "At least one of destination or web destination is required.");
- }
-
- return new SourceRegistration(
- mTopOrigin,
- mEnrollmentId,
- mAppDestination,
- mWebDestination,
- mSourceEventId,
- mExpiry,
- mSourcePriority,
- mInstallAttributionWindow,
- mInstallCooldownWindow,
- mDebugKey,
- mAggregateSource,
- mAggregateFilterData);
- }
- }
-}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/TriggerRegistration.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/TriggerRegistration.java
deleted file mode 100644
index 3f2585609..000000000
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/TriggerRegistration.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.Uri;
-
-import com.android.adservices.service.measurement.util.Validation;
-
-import java.util.Objects;
-
-/**
- * A registration for a trigger of attribution.
- */
-public final class TriggerRegistration {
- private final Uri mTopOrigin;
- private final String mEnrollmentId;
- private final String mAggregateTriggerData;
- private final String mAggregateValues;
- private final String mFilters;
- private final String mEventTriggers;
- @Nullable private final Long mDebugKey;
-
- /** Create a trigger registration. */
- private TriggerRegistration(
- @NonNull Uri topOrigin,
- @NonNull String enrollmentId,
- @NonNull String eventTriggers,
- @Nullable String aggregateTriggerData,
- @Nullable String aggregateValues,
- @Nullable String filters,
- @Nullable Long debugKey) {
- mTopOrigin = topOrigin;
- mEnrollmentId = enrollmentId;
- mAggregateTriggerData = aggregateTriggerData;
- mAggregateValues = aggregateValues;
- mFilters = filters;
- mEventTriggers = eventTriggers;
- mDebugKey = debugKey;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof TriggerRegistration)) return false;
- TriggerRegistration that = (TriggerRegistration) o;
- return Objects.equals(mTopOrigin, that.mTopOrigin)
- && Objects.equals(mEnrollmentId, that.mEnrollmentId)
- && Objects.equals(mAggregateTriggerData, that.mAggregateTriggerData)
- && Objects.equals(mAggregateValues, that.mAggregateValues)
- && Objects.equals(mFilters, that.mFilters)
- && Objects.equals(mEventTriggers, that.mEventTriggers)
- && Objects.equals(mDebugKey, that.mDebugKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- mTopOrigin,
- mEnrollmentId,
- mAggregateTriggerData,
- mAggregateValues,
- mFilters,
- mEventTriggers,
- mDebugKey);
- }
-
- /** Top level origin. */
- @NonNull
- public Uri getTopOrigin() {
- return mTopOrigin;
- }
-
- /** Enrollment ID associated with this registration. */
- @NonNull
- public String getEnrollmentId() {
- return mEnrollmentId;
- }
-
- /** Event triggers - contains trigger data, priority, de-dup key and event-level filters. */
- @NonNull
- public String getEventTriggers() {
- return mEventTriggers;
- }
-
- /**
- * Aggregate trigger data is used to generate aggregate report.
- */
- public String getAggregateTriggerData() {
- return mAggregateTriggerData;
- }
-
- /**
- * Aggregate value is used to generate aggregate report.
- */
- public String getAggregateValues() {
- return mAggregateValues;
- }
-
- /** Top level filters. */
- public String getFilters() {
- return mFilters;
- }
- /** Trigger Debug Key. */
- public @Nullable Long getDebugKey() {
- return mDebugKey;
- }
-
- /**
- * A builder for {@link TriggerRegistration}.
- */
- public static final class Builder {
- private Uri mTopOrigin;
- private String mEnrollmentId;
- private String mEventTriggers;
- private String mAggregateTriggerData;
- private String mAggregateValues;
- private String mFilters;
- private @Nullable Long mDebugKey;
-
- /** See {@link TriggerRegistration#getTopOrigin}. */
- @NonNull
- public Builder setTopOrigin(@NonNull Uri origin) {
- Validation.validateUri(origin);
- mTopOrigin = origin;
- return this;
- }
-
- /** See {@link TriggerRegistration#getEnrollmentId}. */
- @NonNull
- public Builder setEnrollmentId(@NonNull String enrollmentId) {
- mEnrollmentId = enrollmentId;
- return this;
- }
-
- /** See {@link TriggerRegistration#getEventTriggers()}. */
- @NonNull
- public Builder setEventTriggers(@NonNull String eventTriggers) {
- Validation.validateNonNull(eventTriggers);
- mEventTriggers = eventTriggers;
- return this;
- }
-
- /** See {@link TriggerRegistration#getAggregateTriggerData()}. */
- @NonNull
- public Builder setAggregateTriggerData(@Nullable String aggregateTriggerData) {
- mAggregateTriggerData = aggregateTriggerData;
- return this;
- }
-
- /** See {@link TriggerRegistration#getAggregateValues()}. */
- @NonNull
- public Builder setAggregateValues(@Nullable String aggregateValues) {
- mAggregateValues = aggregateValues;
- return this;
- }
-
- /** See {@link TriggerRegistration#getFilters()}. */
- @NonNull
- public Builder setFilters(@Nullable String filters) {
- mFilters = filters;
- return this;
- }
-
- /** See {@link TriggerRegistration#getDebugKey()}. */
- public Builder setDebugKey(@Nullable Long debugKey) {
- mDebugKey = debugKey;
- return this;
- }
-
- /** Build the TriggerRegistration. */
- @NonNull
- public TriggerRegistration build() {
- Validation.validateNonNull(mTopOrigin, mEnrollmentId);
-
- return new TriggerRegistration(
- mTopOrigin,
- mEnrollmentId,
- mEventTriggers,
- mAggregateTriggerData,
- mAggregateValues,
- mFilters,
- mDebugKey);
- }
- }
-}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobService.java
index 48d71d7b6..a0114abaf 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobService.java
@@ -57,19 +57,25 @@ public final class AggregateFallbackReportingJobService extends JobService {
}
LogUtil.d("AggregateFallbackReportingJobService.onStartJob");
- sBlockingExecutor.execute(() -> {
- boolean success = new AggregateReportingJobHandler(
- EnrollmentDao.getInstance(getApplicationContext()),
- DatastoreManagerFactory.getDatastoreManager(
- getApplicationContext()))
- .performScheduledPendingReportsInWindow(
- System.currentTimeMillis() - SystemHealthParams
- .MAX_AGGREGATE_REPORT_UPLOAD_RETRY_WINDOW_MS,
+ sBlockingExecutor.execute(
+ () -> {
+ final long windowStartTime =
+ System.currentTimeMillis()
+ - SystemHealthParams
+ .MAX_AGGREGATE_REPORT_UPLOAD_RETRY_WINDOW_MS;
+ final long windowEndTime =
System.currentTimeMillis()
- AdServicesConfig
- .getMeasurementAggregateFallbackReportingJobPeriodMs());
- jobFinished(params, !success);
- });
+ .getMeasurementAggregateMainReportingJobPeriodMs();
+ final boolean success =
+ new AggregateReportingJobHandler(
+ EnrollmentDao.getInstance(getApplicationContext()),
+ DatastoreManagerFactory.getDatastoreManager(
+ getApplicationContext()))
+ .performScheduledPendingReportsInWindow(
+ windowStartTime, windowEndTime);
+ jobFinished(params, !success);
+ });
return true;
}
@@ -82,13 +88,18 @@ public final class AggregateFallbackReportingJobService extends JobService {
/** Schedules {@link AggregateFallbackReportingJobService} */
@VisibleForTesting
static void schedule(Context context, JobScheduler jobScheduler) {
- final JobInfo job = new JobInfo.Builder(
- AdServicesConfig.MEASUREMENT_AGGREGATE_FALLBACK_REPORTING_JOB_ID,
- new ComponentName(context, AggregateFallbackReportingJobService.class))
- .setRequiresDeviceIdle(true)
- .setRequiresBatteryNotLow(true)
- .setPeriodic(AdServicesConfig.getMeasurementAggregateFallbackReportingJobPeriodMs())
- .build();
+ final JobInfo job =
+ new JobInfo.Builder(
+ AdServicesConfig.MEASUREMENT_AGGREGATE_FALLBACK_REPORTING_JOB_ID,
+ new ComponentName(
+ context, AggregateFallbackReportingJobService.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setPeriodic(
+ AdServicesConfig
+ .getMeasurementAggregateFallbackReportingJobPeriodMs())
+ .setPersisted(true)
+ .build();
jobScheduler.schedule(job);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportBody.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportBody.java
index a8b7eb3be..b0b570db1 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportBody.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportBody.java
@@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
import com.android.adservices.service.measurement.aggregation.AggregateCryptoConverter;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import com.google.common.annotations.VisibleForTesting;
@@ -40,8 +41,8 @@ public class AggregateReportBody {
private String mReportId;
private String mReportingOrigin;
private String mDebugCleartextPayload;
- @Nullable private Long mSourceDebugKey;
- @Nullable private Long mTriggerDebugKey;
+ @Nullable private UnsignedLong mSourceDebugKey;
+ @Nullable private UnsignedLong mTriggerDebugKey;
private static final String API_NAME = "attribution-reporting";
@@ -93,12 +94,10 @@ public class AggregateReportBody {
aggregationServicePayloadsToJson(sharedInfo, key));
if (mSourceDebugKey != null) {
- aggregateBodyJson.put(
- PayloadBodyKeys.SOURCE_DEBUG_KEY, Long.toUnsignedString(mSourceDebugKey));
+ aggregateBodyJson.put(PayloadBodyKeys.SOURCE_DEBUG_KEY, mSourceDebugKey.toString());
}
if (mTriggerDebugKey != null) {
- aggregateBodyJson.put(
- PayloadBodyKeys.TRIGGER_DEBUG_KEY, Long.toUnsignedString(mTriggerDebugKey));
+ aggregateBodyJson.put(PayloadBodyKeys.TRIGGER_DEBUG_KEY, mTriggerDebugKey.toString());
}
return aggregateBodyJson;
@@ -136,10 +135,11 @@ public class AggregateReportBody {
aggregationServicePayload.put(AggregationServicePayloadKeys.PAYLOAD, encryptedPayload);
aggregationServicePayload.put(AggregationServicePayloadKeys.KEY_ID, key.getKeyId());
- aggregationServicePayload.put(
- AggregationServicePayloadKeys.DEBUG_CLEARTEXT_PAYLOAD,
- AggregateCryptoConverter.encode(mDebugCleartextPayload));
-
+ if (mSourceDebugKey != null || mTriggerDebugKey != null) {
+ aggregationServicePayload.put(
+ AggregationServicePayloadKeys.DEBUG_CLEARTEXT_PAYLOAD,
+ AggregateCryptoConverter.encode(mDebugCleartextPayload));
+ }
aggregationServicePayloadsJson.put(aggregationServicePayload);
return aggregationServicePayloadsJson;
@@ -212,13 +212,13 @@ public class AggregateReportBody {
}
/** Source debug key */
- public @NonNull Builder setSourceDebugKey(@Nullable Long sourceDebugKey) {
+ public @NonNull Builder setSourceDebugKey(@Nullable UnsignedLong sourceDebugKey) {
mBuilding.mSourceDebugKey = sourceDebugKey;
return this;
}
/** Trigger debug key */
- public @NonNull Builder setTriggerDebugKey(@Nullable Long triggerDebugKey) {
+ public @NonNull Builder setTriggerDebugKey(@Nullable UnsignedLong triggerDebugKey) {
mBuilding.mTriggerDebugKey = triggerDebugKey;
return this;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportSender.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportSender.java
index dfb3f1dec..778b3fa7d 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportSender.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportSender.java
@@ -18,6 +18,8 @@ package com.android.adservices.service.measurement.reporting;
import android.net.Uri;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.MalformedURLException;
import java.net.URL;
@@ -26,17 +28,36 @@ import java.net.URL;
*/
public class AggregateReportSender extends MeasurementReportSender {
- private static final String AGGREGATE_ATTRIBUTION_REPORT_URI_PATH =
+ @VisibleForTesting
+ public static final String AGGREGATE_ATTRIBUTION_REPORT_URI_PATH =
".well-known/attribution-reporting/report-aggregate-attribution";
+ @VisibleForTesting
+ public static final String DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH =
+ ".well-known/attribution-reporting/debug/report-aggregate-attribution";
+
+ private String mReportUriPath;
+
+ public AggregateReportSender(boolean isDebugReport) {
+ this.mReportUriPath = AGGREGATE_ATTRIBUTION_REPORT_URI_PATH;
+ if (isDebugReport) {
+ this.mReportUriPath = DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH;
+ }
+ }
+
+ /** The report uri path. */
+ @VisibleForTesting
+ public String getReportUriPath() {
+ return mReportUriPath;
+ }
+
/**
* Given a String reportingOrigin, returns the URL Object
* of the URL to send the POST request to.
*/
URL createReportingFullUrl(Uri adTechDomain)
throws MalformedURLException {
- Uri reportingFullUrl = Uri.withAppendedPath(adTechDomain,
- AGGREGATE_ATTRIBUTION_REPORT_URI_PATH);
+ Uri reportingFullUrl = Uri.withAppendedPath(adTechDomain, mReportUriPath);
return new URL(reportingFullUrl.toString());
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandler.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandler.java
index 4920f6da5..6ba2a87ff 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandler.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandler.java
@@ -51,6 +51,7 @@ public class AggregateReportingJobHandler {
private final EnrollmentDao mEnrollmentDao;
private final DatastoreManager mDatastoreManager;
private final AggregateEncryptionKeyManager mAggregateEncryptionKeyManager;
+ private boolean mIsDebugReport;
AggregateReportingJobHandler(EnrollmentDao enrollmentDao, DatastoreManager datastoreManager) {
mEnrollmentDao = enrollmentDao;
@@ -69,8 +70,20 @@ public class AggregateReportingJobHandler {
}
/**
+ * Set Debug Report
+ *
+ * @param isDebugReport
+ * @return the instance of AggregateReportingJobHandler
+ */
+ public AggregateReportingJobHandler setDebugReport(boolean isDebugReport) {
+ mIsDebugReport = isDebugReport;
+ return this;
+ }
+
+ /**
* Finds all aggregate reports within the given window that have a status {@link
- * AggregateReport.Status#PENDING} and attempts to upload them individually.
+ * AggregateReport.Status#PENDING} or {@link AggregateReport.DebugReportStatus#PENDING} based on
+ * mIsDebugReport and attempts to upload them individually.
*
* @param windowStartTime Start time of the search window
* @param windowEndTime End time of the search window
@@ -78,9 +91,16 @@ public class AggregateReportingJobHandler {
*/
synchronized boolean performScheduledPendingReportsInWindow(
long windowStartTime, long windowEndTime) {
- Optional<List<String>> pendingAggregateReportsInWindowOpt = mDatastoreManager
- .runInTransactionWithResult((dao) ->
- dao.getPendingAggregateReportIdsInWindow(windowStartTime, windowEndTime));
+ Optional<List<String>> pendingAggregateReportsInWindowOpt =
+ mDatastoreManager.runInTransactionWithResult(
+ (dao) -> {
+ if (mIsDebugReport) {
+ return dao.getPendingAggregateDebugReportIds();
+ } else {
+ return dao.getPendingAggregateReportIdsInWindow(
+ windowStartTime, windowEndTime);
+ }
+ });
if (!pendingAggregateReportsInWindowOpt.isPresent()) {
// Failure during event report retrieval
return true;
@@ -121,7 +141,13 @@ public class AggregateReportingJobHandler {
return AdServicesStatusUtils.STATUS_IO_ERROR;
}
AggregateReport aggregateReport = aggregateReportOpt.get();
- if (aggregateReport.getStatus() != AggregateReport.Status.PENDING) {
+
+ if (mIsDebugReport
+ && aggregateReport.getDebugReportStatus()
+ != AggregateReport.DebugReportStatus.PENDING) {
+ return AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
+ }
+ if (!mIsDebugReport && aggregateReport.getStatus() != AggregateReport.Status.PENDING) {
return AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
}
try {
@@ -138,8 +164,17 @@ public class AggregateReportingJobHandler {
if (returnCode >= HttpURLConnection.HTTP_OK
&& returnCode <= 299) {
- boolean success = mDatastoreManager.runInTransaction((dao) ->
- dao.markAggregateReportDelivered(aggregateReportId));
+ boolean success =
+ mDatastoreManager.runInTransaction(
+ (dao) -> {
+ if (mIsDebugReport) {
+ dao.markAggregateDebugReportDelivered(aggregateReportId);
+ } else {
+ dao.markAggregateReportStatus(
+ aggregateReportId,
+ AggregateReport.Status.DELIVERED);
+ }
+ });
return success
? AdServicesStatusUtils.STATUS_SUCCESS
@@ -183,7 +218,7 @@ public class AggregateReportingJobHandler {
@VisibleForTesting
public int makeHttpPostRequest(Uri adTechDomain, JSONObject aggregateReportBody)
throws IOException {
- AggregateReportSender aggregateReportSender = new AggregateReportSender();
+ AggregateReportSender aggregateReportSender = new AggregateReportSender(mIsDebugReport);
return aggregateReportSender.sendReport(adTechDomain, aggregateReportBody);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobService.java
index b42ff315c..22ce16ae4 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobService.java
@@ -81,14 +81,17 @@ public final class AggregateReportingJobService extends JobService {
/** Schedules {@link AggregateReportingJobService} */
@VisibleForTesting
static void schedule(Context context, JobScheduler jobScheduler) {
- final JobInfo job = new JobInfo.Builder(
- AdServicesConfig.MEASUREMENT_AGGREGATE_MAIN_REPORTING_JOB_ID,
- new ComponentName(context, AggregateReportingJobService.class))
- .setRequiresDeviceIdle(true)
- .setRequiresBatteryNotLow(true)
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
- .setPeriodic(AdServicesConfig.getMeasurementAggregateMainReportingJobPeriodMs())
- .build();
+ final JobInfo job =
+ new JobInfo.Builder(
+ AdServicesConfig.MEASUREMENT_AGGREGATE_MAIN_REPORTING_JOB_ID,
+ new ComponentName(context, AggregateReportingJobService.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPeriodic(
+ AdServicesConfig.getMeasurementAggregateMainReportingJobPeriodMs())
+ .setPersisted(true)
+ .build();
jobScheduler.schedule(job);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobService.java
new file mode 100644
index 000000000..fe6b3071f
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobService.java
@@ -0,0 +1,136 @@
+/*
+ * 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.measurement.reporting;
+
+import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DEBUG_REPORT_JOB_ID;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.FlagsFactory;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Main service for scheduling debug reporting jobs. The actual job execution logic is part of
+ * {@link EventReportingJobHandler } and {@link AggregateReportingJobHandler}
+ */
+public final class DebugReportingJobService extends JobService {
+
+ private static final Executor sBlockingExecutor = AdServicesExecutors.getBlockingExecutor();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (FlagsFactory.getFlags().getMeasurementJobDebugReportingKillSwitch()) {
+ LogUtil.e("DebugReportingJobService is disabled");
+ return skipAndCancelBackgroundJob(params);
+ }
+
+ LogUtil.d("DebugReportingJobService.onStartJob: ");
+ sBlockingExecutor.execute(
+ () -> {
+ new EventReportingJobHandler(
+ EnrollmentDao.getInstance(getApplicationContext()),
+ DatastoreManagerFactory.getDatastoreManager(
+ getApplicationContext()))
+ .setDebugReport(true)
+ .performScheduledPendingReportsInWindow(0, 0);
+ new AggregateReportingJobHandler(
+ EnrollmentDao.getInstance(getApplicationContext()),
+ DatastoreManagerFactory.getDatastoreManager(
+ getApplicationContext()))
+ .setDebugReport(true)
+ .performScheduledPendingReportsInWindow(0, 0);
+ jobFinished(params, false);
+ });
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ LogUtil.d("DebugReportingJobService.onStopJob");
+ return true;
+ }
+
+ /** Schedules {@link DebugReportingJobService} */
+ @VisibleForTesting
+ static void schedule(Context context, JobScheduler jobScheduler) {
+ final JobInfo job =
+ new JobInfo.Builder(
+ MEASUREMENT_DEBUG_REPORT_JOB_ID,
+ new ComponentName(context, DebugReportingJobService.class))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setOverrideDeadline(1)
+ .build();
+ jobScheduler.schedule(job);
+ }
+
+ /**
+ * Schedule Debug Reporting Job if it is not already scheduled
+ *
+ * @param context the context
+ * @param forceSchedule flag to indicate whether to force rescheduling the job.
+ */
+ public static void scheduleIfNeeded(Context context, boolean forceSchedule) {
+ if (FlagsFactory.getFlags().getMeasurementJobDebugReportingKillSwitch()) {
+ LogUtil.d("DebugReportingJobService is disabled, skip scheduling");
+ return;
+ }
+
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ LogUtil.e("JobScheduler not found");
+ return;
+ }
+
+ final JobInfo job = jobScheduler.getPendingJob(MEASUREMENT_DEBUG_REPORT_JOB_ID);
+ // Schedule if it hasn't been scheduled already or force rescheduling
+ if (job == null || forceSchedule) {
+ schedule(context, jobScheduler);
+ LogUtil.d("Scheduled AggregateReportingJobService");
+ } else {
+ LogUtil.d("AggregateReportingJobService already scheduled, skipping reschedule");
+ }
+ }
+
+ private boolean skipAndCancelBackgroundJob(final JobParameters params) {
+ final JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
+ if (jobScheduler != null) {
+ jobScheduler.cancel(MEASUREMENT_DEBUG_REPORT_JOB_ID);
+ }
+
+ // Tell the JobScheduler that the job has completed and does not need to be rescheduled.
+ jobFinished(params, false);
+
+ // Returning false means that this job has completed its work.
+ return false;
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobService.java
index ec8ffc312..e1ff8cb5c 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobService.java
@@ -83,13 +83,16 @@ public final class EventFallbackReportingJobService extends JobService {
/** Schedules {@link EventFallbackReportingJobService} */
@VisibleForTesting
static void schedule(Context context, JobScheduler jobScheduler) {
- final JobInfo job = new JobInfo.Builder(AdServicesConfig
- .MEASUREMENT_EVENT_FALLBACK_REPORTING_JOB_ID,
- new ComponentName(context, EventFallbackReportingJobService.class))
- .setRequiresDeviceIdle(true)
- .setRequiresBatteryNotLow(true)
- .setPeriodic(AdServicesConfig.getMeasurementEventFallbackReportingJobPeriodMs())
- .build();
+ final JobInfo job =
+ new JobInfo.Builder(
+ AdServicesConfig.MEASUREMENT_EVENT_FALLBACK_REPORTING_JOB_ID,
+ new ComponentName(context, EventFallbackReportingJobService.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setPeriodic(
+ AdServicesConfig.getMeasurementEventFallbackReportingJobPeriodMs())
+ .setPersisted(true)
+ .build();
jobScheduler.schedule(job);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportPayload.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportPayload.java
index 25fe0c8d6..aa48c6cc2 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportPayload.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportPayload.java
@@ -20,6 +20,8 @@ import android.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.json.JSONException;
import org.json.JSONObject;
@@ -29,25 +31,25 @@ import org.json.JSONObject;
public final class EventReportPayload {
private String mAttributionDestination;
- private String mSourceEventId;
- private String mTriggerData;
+ private UnsignedLong mSourceEventId;
+ @NonNull private UnsignedLong mTriggerData;
private String mReportId;
private String mSourceType;
private double mRandomizedTriggerRate;
- @Nullable private Long mSourceDebugKey;
- @Nullable private Long mTriggerDebugKey;
+ @Nullable private UnsignedLong mSourceDebugKey;
+ @Nullable private UnsignedLong mTriggerDebugKey;
private EventReportPayload() {};
private EventReportPayload(EventReportPayload other) {
- this.mAttributionDestination = other.mAttributionDestination;
- this.mSourceEventId = other.mSourceEventId;
- this.mTriggerData = other.mTriggerData;
- this.mReportId = other.mReportId;
- this.mSourceType = other.mSourceType;
- this.mRandomizedTriggerRate = other.mRandomizedTriggerRate;
- this.mSourceDebugKey = other.mSourceDebugKey;
- this.mTriggerDebugKey = other.mTriggerDebugKey;
+ mAttributionDestination = other.mAttributionDestination;
+ mSourceEventId = other.mSourceEventId;
+ mTriggerData = other.mTriggerData;
+ mReportId = other.mReportId;
+ mSourceType = other.mSourceType;
+ mRandomizedTriggerRate = other.mRandomizedTriggerRate;
+ mSourceDebugKey = other.mSourceDebugKey;
+ mTriggerDebugKey = other.mTriggerDebugKey;
}
/**
@@ -56,17 +58,17 @@ public final class EventReportPayload {
public JSONObject toJson() throws JSONException {
JSONObject eventPayloadJson = new JSONObject();
- eventPayloadJson.put("attribution_destination", this.mAttributionDestination);
- eventPayloadJson.put("source_event_id", this.mSourceEventId);
- eventPayloadJson.put("trigger_data", this.mTriggerData);
- eventPayloadJson.put("report_id", this.mReportId);
- eventPayloadJson.put("source_type", this.mSourceType);
- eventPayloadJson.put("randomized_trigger_rate", this.mRandomizedTriggerRate);
+ eventPayloadJson.put("attribution_destination", mAttributionDestination);
+ eventPayloadJson.put("source_event_id", mSourceEventId.toString());
+ eventPayloadJson.put("trigger_data", mTriggerData.toString());
+ eventPayloadJson.put("report_id", mReportId);
+ eventPayloadJson.put("source_type", mSourceType);
+ eventPayloadJson.put("randomized_trigger_rate", mRandomizedTriggerRate);
if (mSourceDebugKey != null) {
- eventPayloadJson.put("source_debug_key", Long.toUnsignedString(this.mSourceDebugKey));
+ eventPayloadJson.put("source_debug_key", mSourceDebugKey.toString());
}
if (mTriggerDebugKey != null) {
- eventPayloadJson.put("trigger_debug_key", Long.toUnsignedString(this.mTriggerDebugKey));
+ eventPayloadJson.put("trigger_debug_key", mTriggerDebugKey.toString());
}
return eventPayloadJson;
@@ -93,7 +95,7 @@ public final class EventReportPayload {
/**
* 64-bit event id set on the attribution source.
*/
- public @NonNull Builder setSourceEventId(@NonNull String sourceEventId) {
+ public @NonNull Builder setSourceEventId(@NonNull UnsignedLong sourceEventId) {
mBuilding.mSourceEventId = sourceEventId;
return this;
}
@@ -101,7 +103,7 @@ public final class EventReportPayload {
/**
* Course data set in the attribution trigger registration.
*/
- public @NonNull Builder setTriggerData(@NonNull String triggerData) {
+ public @NonNull Builder setTriggerData(@NonNull UnsignedLong triggerData) {
mBuilding.mTriggerData = triggerData;
return this;
}
@@ -132,13 +134,13 @@ public final class EventReportPayload {
}
/** Source debug key */
- public @NonNull Builder setSourceDebugKey(@Nullable Long sourceDebugKey) {
+ public @NonNull Builder setSourceDebugKey(@Nullable UnsignedLong sourceDebugKey) {
mBuilding.mSourceDebugKey = sourceDebugKey;
return this;
}
/** Trigger debug key */
- public @NonNull Builder setTriggerDebugKey(@Nullable Long triggerDebugKey) {
+ public @NonNull Builder setTriggerDebugKey(@Nullable UnsignedLong triggerDebugKey) {
mBuilding.mTriggerDebugKey = triggerDebugKey;
return this;
}
@@ -147,6 +149,9 @@ public final class EventReportPayload {
* Build the EventReportPayload.
*/
public @NonNull EventReportPayload build() {
+ if (mBuilding.mTriggerData == null) {
+ mBuilding.mTriggerData = new UnsignedLong(0L);
+ }
return new EventReportPayload(mBuilding);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportSender.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportSender.java
index b01204cf6..507c7e427 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportSender.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportSender.java
@@ -18,6 +18,8 @@ package com.android.adservices.service.measurement.reporting;
import android.net.Uri;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.MalformedURLException;
import java.net.URL;
@@ -26,9 +28,29 @@ import java.net.URL;
*/
public class EventReportSender extends MeasurementReportSender {
- private static final String EVENT_ATTRIBUTION_REPORT_URI_PATH =
+ @VisibleForTesting
+ public static final String EVENT_ATTRIBUTION_REPORT_URI_PATH =
".well-known/attribution-reporting/report-event-attribution";
+ @VisibleForTesting
+ public static final String DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH =
+ ".well-known/attribution-reporting/debug/report-event-attribution";
+
+ private String mReportUriPath;
+
+ public EventReportSender(boolean isDebugReport) {
+ this.mReportUriPath = EVENT_ATTRIBUTION_REPORT_URI_PATH;
+ if (isDebugReport) {
+ this.mReportUriPath = DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH;
+ }
+ }
+
+ /** The report uri path. */
+ @VisibleForTesting
+ public String getReportUriPath() {
+ return mReportUriPath;
+ }
+
/**
* Creates URL to send the POST request to.
*/
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobHandler.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobHandler.java
index d833b7873..653bf7d51 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobHandler.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobHandler.java
@@ -46,6 +46,7 @@ public class EventReportingJobHandler {
private final EnrollmentDao mEnrollmentDao;
private final DatastoreManager mDatastoreManager;
+ private boolean mIsDebugReport;
EventReportingJobHandler(EnrollmentDao enrollmentDao, DatastoreManager datastoreManager) {
mEnrollmentDao = enrollmentDao;
@@ -53,8 +54,20 @@ public class EventReportingJobHandler {
}
/**
+ * Set Debug Report
+ *
+ * @param isDebugReport
+ * @return the instance of EventReportingJobHandler
+ */
+ public EventReportingJobHandler setDebugReport(boolean isDebugReport) {
+ mIsDebugReport = isDebugReport;
+ return this;
+ }
+
+ /**
* Finds all reports within the given window that have a status {@link
- * EventReport.Status#PENDING} and attempts to upload them individually.
+ * EventReport.Status#PENDING} or {@link EventReport.DebugReportStatus#PENDING} based on
+ * mIsDebugReport and attempts to upload them individually.
*
* @param windowStartTime Start time of the search window
* @param windowEndTime End time of the search window
@@ -62,9 +75,16 @@ public class EventReportingJobHandler {
*/
synchronized boolean performScheduledPendingReportsInWindow(
long windowStartTime, long windowEndTime) {
- Optional<List<String>> pendingEventReportsInWindowOpt = mDatastoreManager
- .runInTransactionWithResult((dao) ->
- dao.getPendingEventReportIdsInWindow(windowStartTime, windowEndTime));
+ Optional<List<String>> pendingEventReportsInWindowOpt =
+ mDatastoreManager.runInTransactionWithResult(
+ (dao) -> {
+ if (mIsDebugReport) {
+ return dao.getPendingDebugEventReportIds();
+ } else {
+ return dao.getPendingEventReportIdsInWindow(
+ windowStartTime, windowEndTime);
+ }
+ });
if (!pendingEventReportsInWindowOpt.isPresent()) {
// Failure during event report retrieval
return true;
@@ -94,7 +114,12 @@ public class EventReportingJobHandler {
return AdServicesStatusUtils.STATUS_IO_ERROR;
}
EventReport eventReport = eventReportOpt.get();
- if (eventReport.getStatus() != EventReport.Status.PENDING) {
+
+ if (mIsDebugReport
+ && eventReport.getDebugReportStatus() != EventReport.DebugReportStatus.PENDING) {
+ return AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
+ }
+ if (!mIsDebugReport && eventReport.getStatus() != EventReport.Status.PENDING) {
return AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
}
try {
@@ -110,8 +135,16 @@ public class EventReportingJobHandler {
if (returnCode >= HttpURLConnection.HTTP_OK
&& returnCode <= 299) {
- boolean success = mDatastoreManager.runInTransaction((dao) ->
- dao.markEventReportDelivered(eventReportId));
+ boolean success =
+ mDatastoreManager.runInTransaction(
+ (dao) -> {
+ if (mIsDebugReport) {
+ dao.markEventDebugReportDelivered(eventReportId);
+ } else {
+ dao.markEventReportStatus(
+ eventReportId, EventReport.Status.DELIVERED);
+ }
+ });
return success
? AdServicesStatusUtils.STATUS_SUCCESS
@@ -133,9 +166,9 @@ public class EventReportingJobHandler {
JSONObject createReportJsonPayload(EventReport eventReport) throws JSONException {
return new EventReportPayload.Builder()
.setReportId(eventReport.getId())
- .setSourceEventId(String.valueOf(eventReport.getSourceId()))
+ .setSourceEventId(eventReport.getSourceEventId())
.setAttributionDestination(eventReport.getAttributionDestination().toString())
- .setTriggerData(String.valueOf(eventReport.getTriggerData()))
+ .setTriggerData(eventReport.getTriggerData())
.setSourceType(eventReport.getSourceType().getValue())
.setRandomizedTriggerRate(eventReport.getRandomizedTriggerRate())
.setSourceDebugKey(eventReport.getSourceDebugKey())
@@ -150,7 +183,7 @@ public class EventReportingJobHandler {
@VisibleForTesting
public int makeHttpPostRequest(Uri adTechDomain, JSONObject eventReportPayload)
throws IOException {
- EventReportSender eventReportSender = new EventReportSender();
+ EventReportSender eventReportSender = new EventReportSender(mIsDebugReport);
return eventReportSender.sendReport(adTechDomain, eventReportPayload);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobService.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobService.java
index 5cdce1af6..8c4842cc3 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobService.java
@@ -80,14 +80,16 @@ public final class EventReportingJobService extends JobService {
/** Schedules {@link EventReportingJobService} */
@VisibleForTesting
static void schedule(Context context, JobScheduler jobScheduler) {
- final JobInfo job = new JobInfo.Builder(
- AdServicesConfig.MEASUREMENT_EVENT_MAIN_REPORTING_JOB_ID,
- new ComponentName(context, EventReportingJobService.class))
- .setRequiresDeviceIdle(true)
- .setRequiresBatteryNotLow(true)
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
- .setPeriodic(AdServicesConfig.getMeasurementEventMainReportingJobPeriodMs())
- .build();
+ final JobInfo job =
+ new JobInfo.Builder(
+ AdServicesConfig.MEASUREMENT_EVENT_MAIN_REPORTING_JOB_ID,
+ new ComponentName(context, EventReportingJobService.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPeriodic(AdServicesConfig.getMeasurementEventMainReportingJobPeriodMs())
+ .setPersisted(true)
+ .build();
jobScheduler.schedule(job);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/util/AsyncFetchStatus.java b/adservices/service-core/java/com/android/adservices/service/measurement/util/AsyncFetchStatus.java
new file mode 100644
index 000000000..5daa1990f
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/util/AsyncFetchStatus.java
@@ -0,0 +1,49 @@
+/*
+ * 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.measurement.util;
+
+/** POJO for storing source and trigger fetcher status */
+public class AsyncFetchStatus {
+ public enum ResponseStatus {
+ SUCCESS,
+ SERVER_UNAVAILABLE,
+ NETWORK_ERROR,
+ PARSING_ERROR,
+ INVALID_ENROLLMENT
+ }
+
+ private ResponseStatus mStatus;
+
+ public AsyncFetchStatus() {
+ mStatus = ResponseStatus.SERVER_UNAVAILABLE;
+ }
+
+ /** Get the status of a communication with an Ad Tech server. */
+ public ResponseStatus getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Set the status of a communication with an Ad Tech server.
+ *
+ * @param status a {@link ResponseStatus} that is used up the call stack to determine errors in
+ * the Ad tech server during source and trigger fetching.
+ */
+ public void setStatus(ResponseStatus status) {
+ mStatus = status;
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/util/AsyncRedirect.java b/adservices/service-core/java/com/android/adservices/service/measurement/util/AsyncRedirect.java
new file mode 100644
index 000000000..f5295ae56
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/util/AsyncRedirect.java
@@ -0,0 +1,59 @@
+/*
+ * 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.measurement.util;
+
+import android.net.Uri;
+
+import com.android.adservices.service.measurement.AsyncRegistration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Wrapper for a list of redirect Uris and a redirect type */
+public class AsyncRedirect {
+ private final List<Uri> mRedirects;
+ private @AsyncRegistration.RedirectType int mRedirectType;
+
+ public AsyncRedirect() {
+ mRedirects = new ArrayList<>();
+ mRedirectType = AsyncRegistration.RedirectType.ANY;
+ }
+
+ public AsyncRedirect(List<Uri> redirects, @AsyncRegistration.RedirectType int redirectType) {
+ mRedirects = redirects;
+ mRedirectType = redirectType;
+ }
+
+ /** The list the redirect Uris */
+ public List<Uri> getRedirects() {
+ return new ArrayList<>(mRedirects);
+ }
+
+ /** The redirect type */
+ public @AsyncRegistration.RedirectType int getRedirectType() {
+ return mRedirectType;
+ }
+
+ /** Add to the list the redirect Uris */
+ public void addToRedirects(List<Uri> uris) {
+ mRedirects.addAll(uris);
+ }
+
+ public void setRedirectType(@AsyncRegistration.RedirectType int redirectType) {
+ mRedirectType = redirectType;
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/util/Enrollment.java b/adservices/service-core/java/com/android/adservices/service/measurement/util/Enrollment.java
index e0c736458..9432ccf7e 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/util/Enrollment.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/util/Enrollment.java
@@ -37,8 +37,9 @@ public final class Enrollment {
public static Optional<String> maybeGetEnrollmentId(Uri registrationUri,
EnrollmentDao enrollmentDao) {
Uri uriWithoutParams = registrationUri.buildUpon().clearQuery().fragment(null).build();
- EnrollmentData enrollmentData = enrollmentDao.getEnrollmentDataFromMeasurementUrl(
- uriWithoutParams.toString());
+
+ EnrollmentData enrollmentData =
+ enrollmentDao.getEnrollmentDataFromMeasurementUrl(uriWithoutParams);
if (enrollmentData != null && enrollmentData.getEnrollmentId() != null) {
return Optional.of(enrollmentData.getEnrollmentId());
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/util/Filter.java b/adservices/service-core/java/com/android/adservices/service/measurement/util/Filter.java
index 56b31b0cf..f2fe7339f 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/util/Filter.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/util/Filter.java
@@ -16,7 +16,7 @@
package com.android.adservices.service.measurement.util;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
+import com.android.adservices.service.measurement.FilterData;
import java.util.HashSet;
import java.util.List;
@@ -37,7 +37,7 @@ public final class Filter {
* @return return true when all keys in source filter and trigger filter are matched.
*/
public static boolean isFilterMatch(
- AggregateFilterData sourceFilter, AggregateFilterData triggerFilter, boolean isFilter) {
+ FilterData sourceFilter, FilterData triggerFilter, boolean isFilter) {
for (String key : triggerFilter.getAttributionFilterMap().keySet()) {
if (!sourceFilter.getAttributionFilterMap().containsKey(key)) {
continue;
@@ -45,17 +45,20 @@ public final class Filter {
// Finds the intersection of two value lists.
List<String> sourceValues = sourceFilter.getAttributionFilterMap().get(key);
List<String> triggerValues = triggerFilter.getAttributionFilterMap().get(key);
- Set<String> common = new HashSet<>(sourceValues);
- common.retainAll(triggerValues);
- // For filters, return false when one key doesn't have intersection.
- if (isFilter && common.size() == 0) {
- return false;
- }
- // For not_filters, return false when one key has intersection.
- if (!isFilter && common.size() != 0) {
+ if (!matchFilterValues(sourceValues, triggerValues, isFilter)) {
return false;
}
}
return true;
}
+
+ private static boolean matchFilterValues(List<String> sourceValues, List<String> triggerValues,
+ boolean isFilter) {
+ if (triggerValues.isEmpty()) {
+ return isFilter ? sourceValues.isEmpty() : !sourceValues.isEmpty();
+ }
+ Set<String> intersection = new HashSet<>(sourceValues);
+ intersection.retainAll(triggerValues);
+ return isFilter ? !intersection.isEmpty() : intersection.isEmpty();
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/util/UnsignedLong.java b/adservices/service-core/java/com/android/adservices/service/measurement/util/UnsignedLong.java
new file mode 100644
index 000000000..650c36dc4
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/util/UnsignedLong.java
@@ -0,0 +1,89 @@
+/*
+ * 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.measurement.util;
+
+import android.annotation.NonNull;
+
+import java.math.BigInteger;
+import java.util.Objects;
+
+/**
+ * A type that stores a numeric value as a Long, but is meant for representation in strings and
+ * math operations as an unsigned 64 bit integer, using the 64th bit as part of its magnitude.
+ */
+public class UnsignedLong implements Comparable<UnsignedLong> {
+ private final Long mValue;
+
+ public UnsignedLong(@NonNull Long value) {
+ Validation.validateNonNull(value);
+ mValue = value;
+ }
+
+ public UnsignedLong(@NonNull String value) {
+ Validation.validateNonNull(value);
+ mValue = Long.parseUnsignedLong(value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mValue);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof UnsignedLong)) {
+ return false;
+ }
+ UnsignedLong other = (UnsignedLong) obj;
+ return Objects.equals(mValue, other.getValue());
+ }
+
+ @Override
+ public int compareTo(UnsignedLong other) {
+ if (mValue >= 0L) {
+ if (other.getValue() >= 0) {
+ return Long.compare(mValue, other.getValue());
+ } else {
+ return -1;
+ }
+ } else {
+ if (other.getValue() >= 0) {
+ return 1;
+ } else {
+ return Long.compare(mValue, other.getValue());
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return Long.toUnsignedString(mValue);
+ }
+
+ public Long getValue() {
+ return mValue;
+ }
+
+ /**
+ * Returns the {@code BigInteger} mod operation of the unsigned value and modulus as an
+ * {@code UnsignedLong}.
+ */
+ public UnsignedLong mod(int modulus) {
+ BigInteger truncated = new BigInteger(Long.toUnsignedString(mValue))
+ .mod(BigInteger.valueOf(modulus));
+ return new UnsignedLong(truncated.toString());
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/util/Web.java b/adservices/service-core/java/com/android/adservices/service/measurement/util/Web.java
index cf6f04a99..5b07f9de1 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/util/Web.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/util/Web.java
@@ -59,4 +59,19 @@ public final class Web {
public static Optional<Uri> topPrivateDomainAndScheme(String uri) {
return topPrivateDomainAndScheme(Uri.parse(uri));
}
+
+ /**
+ * Returns a {@code Uri} of the scheme concatenated with the first subdomain + path of the
+ * provided URL that is beneath the public suffix.
+ *
+ * @param uri the URL string to parse.
+ */
+ public static Optional<Uri> topPrivateDomainSchemeAndPath(Uri uri) {
+ Optional<Uri> topDomain = topPrivateDomainAndScheme(uri);
+ if (!topDomain.isPresent()) {
+ return Optional.empty();
+ }
+ String url = topDomain.get() + uri.getPath();
+ return Optional.of(Uri.parse(url));
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/AdServicesLogger.java b/adservices/service-core/java/com/android/adservices/service/stats/AdServicesLogger.java
index d66e730ae..9723f4643 100644
--- a/adservices/service-core/java/com/android/adservices/service/stats/AdServicesLogger.java
+++ b/adservices/service-core/java/com/android/adservices/service/stats/AdServicesLogger.java
@@ -28,8 +28,61 @@ public interface AdServicesLogger {
void logUIStats(UIStats uiStats);
/** Logs API call stats specific to the FLEDGE APIs as an {@link ApiCallStats} object. */
- void logFledgeApiCallStats(int apiName, int resultCode);
+ void logFledgeApiCallStats(int apiName, int resultCode, int latencyMs);
/** Logs measurement registrations response size. */
void logMeasurementRegistrationsResponseSize(MeasurementRegistrationResponseStats stats);
+
+ /**
+ * Logs the runAdSelection process stats as an {@link RunAdSelectionProcessReportedStats}
+ * object.
+ */
+ void logRunAdSelectionProcessReportedStats(RunAdSelectionProcessReportedStats stats);
+
+ /**
+ * Logs the runAdBidding process stats as an {@link RunAdBiddingProcessReportedStats} object.
+ */
+ void logRunAdBiddingProcessReportedStats(RunAdBiddingProcessReportedStats stats);
+
+ /**
+ * Logs the runAdScoring process stats as an {@link RunAdScoringProcessReportedStats} object.
+ */
+ void logRunAdScoringProcessReportedStats(RunAdScoringProcessReportedStats stats);
+
+ /**
+ * Logs the runAdBiddingPerCA process stats as an {@link RunAdBiddingPerCAProcessReportedStats}
+ * object.
+ */
+ void logRunAdBiddingPerCAProcessReportedStats(RunAdBiddingPerCAProcessReportedStats stats);
+
+ /**
+ * Logs the backgroundFetch process stats as an {@link BackgroundFetchProcessReportedStats}
+ * object.
+ */
+ void logBackgroundFetchProcessReportedStats(BackgroundFetchProcessReportedStats stats);
+
+ /**
+ * Logs the updateCustomAudience process stats as an {@link
+ * com.android.adservices.service.stats.UpdateCustomAudienceProcessReportedStats} objects.
+ */
+ void logUpdateCustomAudienceProcessReportedStats(
+ UpdateCustomAudienceProcessReportedStats stats);
+
+ /**
+ * Logs GetTopics API call stats as an {@link
+ * com.android.adservices.service.stats.GetTopicsReportedStats} object.
+ */
+ void logGetTopicsReportedStats(GetTopicsReportedStats stats);
+
+ /**
+ * Logs stats for getTopTopics as an {@link
+ * com.android.adservices.service.stats.EpochComputationGetTopTopicsStats} object.
+ */
+ void logEpochComputationGetTopTopicsStats(EpochComputationGetTopTopicsStats stats);
+
+ /**
+ * Logs classifier stats during epoch computation as an {@link
+ * com.android.adservices.service.stats.EpochComputationClassifierStats} object.
+ */
+ void logEpochComputationClassifierStats(EpochComputationClassifierStats stats);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/AdServicesLoggerImpl.java b/adservices/service-core/java/com/android/adservices/service/stats/AdServicesLoggerImpl.java
index 742230ef3..dff2ab7f2 100644
--- a/adservices/service-core/java/com/android/adservices/service/stats/AdServicesLoggerImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/stats/AdServicesLoggerImpl.java
@@ -16,8 +16,7 @@
package com.android.adservices.service.stats;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__FLEDGE;
+import com.android.internal.annotations.VisibleForTesting;
import javax.annotation.concurrent.ThreadSafe;
@@ -25,8 +24,16 @@ import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
public class AdServicesLoggerImpl implements AdServicesLogger {
private static volatile AdServicesLoggerImpl sAdServicesLogger;
+ private final StatsdAdServicesLogger mStatsdAdServicesLogger;
- private AdServicesLoggerImpl() {}
+ private AdServicesLoggerImpl() {
+ mStatsdAdServicesLogger = StatsdAdServicesLogger.getInstance();
+ }
+
+ @VisibleForTesting
+ AdServicesLoggerImpl(StatsdAdServicesLogger statsdAdServicesLogger) {
+ mStatsdAdServicesLogger = statsdAdServicesLogger;
+ }
/** Returns an instance of AdServicesLogger. */
public static AdServicesLoggerImpl getInstance() {
@@ -42,37 +49,74 @@ public class AdServicesLoggerImpl implements AdServicesLogger {
@Override
public void logMeasurementReports(MeasurementReportsStats measurementReportsStats) {
- StatsdAdServicesLogger.getInstance().logMeasurementReports(measurementReportsStats);
+ mStatsdAdServicesLogger.logMeasurementReports(measurementReportsStats);
}
@Override
public void logApiCallStats(ApiCallStats apiCallStats) {
- StatsdAdServicesLogger.getInstance().logApiCallStats(apiCallStats);
+ mStatsdAdServicesLogger.logApiCallStats(apiCallStats);
}
@Override
public void logUIStats(UIStats uiStats) {
- StatsdAdServicesLogger.getInstance().logUIStats(uiStats);
+ mStatsdAdServicesLogger.logUIStats(uiStats);
}
@Override
- public void logFledgeApiCallStats(int apiName, int resultCode) {
- // TODO(b/233628316): Implement latency measurement
- logApiCallStats(
- new ApiCallStats.Builder()
- .setCode(AD_SERVICES_API_CALLED)
- .setApiClass(AD_SERVICES_API_CALLED__API_CLASS__FLEDGE)
- .setApiName(apiName)
- .setResultCode(resultCode)
- // TODO(b/233629557): Implement app/SDK reporting
- .setSdkPackageName("")
- .setAppPackageName("")
- .build());
+ public void logFledgeApiCallStats(int apiName, int resultCode, int latencyMs) {
+ mStatsdAdServicesLogger.logFledgeApiCallStats(apiName, resultCode, latencyMs);
}
@Override
public void logMeasurementRegistrationsResponseSize(
MeasurementRegistrationResponseStats stats) {
- StatsdAdServicesLogger.getInstance().logMeasurementRegistrationsResponseSize(stats);
+ mStatsdAdServicesLogger.logMeasurementRegistrationsResponseSize(stats);
+ }
+
+ @Override
+ public void logRunAdSelectionProcessReportedStats(RunAdSelectionProcessReportedStats stats) {
+ mStatsdAdServicesLogger.logRunAdSelectionProcessReportedStats(stats);
+ }
+
+ @Override
+ public void logRunAdBiddingProcessReportedStats(RunAdBiddingProcessReportedStats stats) {
+ mStatsdAdServicesLogger.logRunAdBiddingProcessReportedStats(stats);
+ }
+
+ @Override
+ public void logRunAdScoringProcessReportedStats(RunAdScoringProcessReportedStats stats) {
+ mStatsdAdServicesLogger.logRunAdScoringProcessReportedStats(stats);
+ }
+
+ @Override
+ public void logRunAdBiddingPerCAProcessReportedStats(
+ RunAdBiddingPerCAProcessReportedStats stats) {
+ mStatsdAdServicesLogger.logRunAdBiddingPerCAProcessReportedStats(stats);
+ }
+
+ @Override
+ public void logBackgroundFetchProcessReportedStats(BackgroundFetchProcessReportedStats stats) {
+ mStatsdAdServicesLogger.logBackgroundFetchProcessReportedStats(stats);
+ }
+
+ @Override
+ public void logUpdateCustomAudienceProcessReportedStats(
+ UpdateCustomAudienceProcessReportedStats stats) {
+ mStatsdAdServicesLogger.logUpdateCustomAudienceProcessReportedStats(stats);
+ }
+
+ @Override
+ public void logGetTopicsReportedStats(GetTopicsReportedStats stats) {
+ mStatsdAdServicesLogger.logGetTopicsReportedStats(stats);
+ }
+
+ @Override
+ public void logEpochComputationGetTopTopicsStats(EpochComputationGetTopTopicsStats stats) {
+ mStatsdAdServicesLogger.logEpochComputationGetTopTopicsStats(stats);
+ }
+
+ @Override
+ public void logEpochComputationClassifierStats(EpochComputationClassifierStats stats) {
+ mStatsdAdServicesLogger.logEpochComputationClassifierStats(stats);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/ApiServiceLatencyCalculator.java b/adservices/service-core/java/com/android/adservices/service/stats/ApiServiceLatencyCalculator.java
new file mode 100644
index 000000000..e6986827d
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/ApiServiceLatencyCalculator.java
@@ -0,0 +1,107 @@
+/*
+ * 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.stats;
+
+import android.adservices.common.CallerMetadata;
+import android.annotation.NonNull;
+
+import com.android.adservices.LogUtil;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/** Class for Api Service Latency Calculator. */
+@ThreadSafe
+public class ApiServiceLatencyCalculator {
+ private final long mBinderElapsedTimestamp;
+ private final long mStartElapsedTimestamp;
+ private volatile long mStopElapsedTimestamp;
+ private volatile boolean mRunning;
+ private final Clock mClock;
+
+ public ApiServiceLatencyCalculator(
+ @NonNull CallerMetadata callerMetadata, @NonNull Clock clock) {
+ mBinderElapsedTimestamp = callerMetadata.getBinderElapsedTimestamp();
+ mClock = clock;
+ mStartElapsedTimestamp = mClock.elapsedRealtime();
+ mRunning = true;
+ LogUtil.v("ApiServiceLatencyCalculator started.");
+ }
+
+ /**
+ * Stops a {@link ApiServiceLatencyCalculator} instance from time calculation. If an instance is
+ * not running, calling this method will do nothing.
+ */
+ private void stop() {
+ if (!mRunning) {
+ return;
+ }
+ synchronized (this) {
+ if (!mRunning) {
+ return;
+ }
+ mStopElapsedTimestamp = mClock.elapsedRealtime();
+ mRunning = false;
+ LogUtil.v("ApiServiceLatencyCalculator stopped.");
+ }
+ }
+
+ /**
+ * @return the elapsed timestamp since the system boots if the {@link
+ * ApiServiceLatencyCalculator} instance is still running, otherwise the timestamp when it
+ * was stopped.
+ */
+ private long getServiceElapsedTimestamp() {
+ if (mRunning) {
+ return mClock.elapsedRealtime();
+ }
+ return mStopElapsedTimestamp;
+ }
+
+ /**
+ * @return the api service elapsed time latency since {@link ApiServiceLatencyCalculator} starts
+ * in milliseconds on the service side. This method will not stop the {@link
+ * ApiServiceLatencyCalculator} and should be used for getting intermediate stage latency of
+ * a API process.
+ */
+ public int getApiServiceElapsedLatencyMs() {
+ return (int) (getServiceElapsedTimestamp() - mStartElapsedTimestamp);
+ }
+
+ /**
+ * @return the api service overall latency since the {@link ApiServiceLatencyCalculator} starts
+ * in milliseconds without binder latency, on the server side. This method will stop the
+ * calculator if still running and the returned latency value will no longer change once the
+ * calculator is stopped. It should be used to get the complete process latency of an API
+ * within the server side.
+ */
+ public int getApiServiceInternalFinalLatencyMs() {
+ stop();
+ return getApiServiceElapsedLatencyMs();
+ }
+
+ /**
+ * @return the approximate api service overall latency since the api is called at the client
+ * interface. This method will stop the {@link ApiServiceLatencyCalculator} if still running
+ * and the returned latency value will no longer change once the calculator is stopped. It
+ * should be used to get the complete process latency of an API.
+ */
+ public int getApiServiceOverallLatencyMs() {
+ return (int)
+ ((mStartElapsedTimestamp - mBinderElapsedTimestamp) * 2
+ + getApiServiceInternalFinalLatencyMs());
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/BackgroundFetchProcessReportedStats.java b/adservices/service-core/java/com/android/adservices/service/stats/BackgroundFetchProcessReportedStats.java
new file mode 100644
index 000000000..ced8ab4e3
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/BackgroundFetchProcessReportedStats.java
@@ -0,0 +1,50 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+
+/** Class for backgroundFetch process reported stats. */
+@AutoValue
+public abstract class BackgroundFetchProcessReportedStats {
+ /** @return latency in milliseconds. */
+ public abstract int getLatencyInMillis();
+
+ /** @return num of eligible-to-update CAs. */
+ public abstract int getNumOfEligibleToUpdateCas();
+
+ /** @return background fetch process result code. */
+ public abstract int getResultCode();
+
+ /** @return generic builder. */
+ static Builder builder() {
+ return new AutoValue_BackgroundFetchProcessReportedStats.Builder();
+ }
+
+ /** Builder class for {@link BackgroundFetchProcessReportedStats} object. */
+ @AutoValue.Builder
+ abstract static class Builder {
+
+ abstract Builder setLatencyInMillis(int value);
+
+ abstract Builder setNumOfEligibleToUpdateCas(int value);
+
+ abstract Builder setResultCode(int value);
+
+ abstract BackgroundFetchProcessReportedStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/EpochComputationClassifierStats.java b/adservices/service-core/java/com/android/adservices/service/stats/EpochComputationClassifierStats.java
new file mode 100644
index 000000000..a096723db
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/EpochComputationClassifierStats.java
@@ -0,0 +1,76 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+/** Class for AdServicesEpochComputationClassifierReported atom. */
+@AutoValue
+public abstract class EpochComputationClassifierStats {
+
+ /** @return list of topics returned by the classifier for each app. */
+ public abstract ImmutableList<Integer> getTopicIds();
+
+ /** @return build id of the assets. */
+ public abstract int getBuildId();
+
+ /** @return version of the assets used. */
+ public abstract String getAssetVersion();
+
+ /** @return type of the classifier used for classification. */
+ public abstract int getClassifierType();
+
+ /** @return on-device classifier status. */
+ public abstract int getOnDeviceClassifierStatus();
+
+ /** @return pre-computed classifier status. */
+ public abstract int getPrecomputedClassifierStatus();
+
+ /** @return generic builder. */
+ public static EpochComputationClassifierStats.Builder builder() {
+ return new AutoValue_EpochComputationClassifierStats.Builder();
+ }
+
+ /** Builder class for {@link EpochComputationClassifierStats}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /** Set list of topics returned by the classifier for each app */
+ public abstract EpochComputationClassifierStats.Builder setTopicIds(
+ ImmutableList<Integer> value);
+
+ /** Set duplicate topic count. */
+ public abstract EpochComputationClassifierStats.Builder setBuildId(int value);
+
+ /** Set version of the assets used. */
+ public abstract EpochComputationClassifierStats.Builder setAssetVersion(String value);
+
+ /** Set type of the classifier used for classification. */
+ public abstract EpochComputationClassifierStats.Builder setClassifierType(int value);
+
+ /** Set on-device classifier status. */
+ public abstract EpochComputationClassifierStats.Builder setOnDeviceClassifierStatus(
+ int value);
+
+ /** Set pre-computed classifier status. */
+ public abstract EpochComputationClassifierStats.Builder setPrecomputedClassifierStatus(
+ int value);
+
+ /** build for {@link EpochComputationClassifierStats}. */
+ public abstract EpochComputationClassifierStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/EpochComputationGetTopTopicsStats.java b/adservices/service-core/java/com/android/adservices/service/stats/EpochComputationGetTopTopicsStats.java
new file mode 100644
index 000000000..6f510a0ba
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/EpochComputationGetTopTopicsStats.java
@@ -0,0 +1,61 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+
+/** Class for AdServicesEpochComputationGetTopTopicsReported atom. */
+@AutoValue
+public abstract class EpochComputationGetTopTopicsStats {
+
+ /** @return number of top topics generated. */
+ public abstract int getTopTopicCount();
+
+ /** @return number of padded random topics generated. */
+ public abstract int getPaddedRandomTopicsCount();
+
+ /** @return number of apps considered for calculating top topics. */
+ public abstract int getAppsConsideredCount();
+
+ /** @return number of sdks that called Topics API in the epoch. */
+ public abstract int getSdksConsideredCount();
+
+ /** @return generic builder. */
+ public static EpochComputationGetTopTopicsStats.Builder builder() {
+ return new AutoValue_EpochComputationGetTopTopicsStats.Builder();
+ }
+
+ /** Builder class for {@link EpochComputationGetTopTopicsStats}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /** Set number of top topics generated. */
+ public abstract EpochComputationGetTopTopicsStats.Builder setTopTopicCount(int value);
+
+ /** Set number of padded random topics generated. */
+ public abstract EpochComputationGetTopTopicsStats.Builder setPaddedRandomTopicsCount(
+ int value);
+
+ /** Set number of apps considered for calculating top topics. */
+ public abstract EpochComputationGetTopTopicsStats.Builder setAppsConsideredCount(int value);
+
+ /** Set number of sdks that called Topics API in the epoch. */
+ public abstract EpochComputationGetTopTopicsStats.Builder setSdksConsideredCount(int value);
+
+ /** build for {@link EpochComputationGetTopTopicsStats}. */
+ public abstract EpochComputationGetTopTopicsStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/GetTopicsReportedStats.java b/adservices/service-core/java/com/android/adservices/service/stats/GetTopicsReportedStats.java
new file mode 100644
index 000000000..06b4c2c14
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/GetTopicsReportedStats.java
@@ -0,0 +1,53 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+
+/** Class for AdServicesGetTopicsReported atom. */
+@AutoValue
+public abstract class GetTopicsReportedStats {
+ /** @return number of topic ids filtered due to duplication. */
+ public abstract int getDuplicateTopicCount();
+
+ /** @return number of topic ids filtered due to being blocked. */
+ public abstract int getFilteredBlockedTopicCount();
+
+ /** @return number of topic ids returned. */
+ public abstract int getTopicIdsCount();
+
+ /** @return generic builder. */
+ public static GetTopicsReportedStats.Builder builder() {
+ return new AutoValue_GetTopicsReportedStats.Builder();
+ }
+
+ /** Builder class for {@link GetTopicsReportedStats}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /** Set duplicate topic count. */
+ public abstract GetTopicsReportedStats.Builder setDuplicateTopicCount(int value);
+
+ /** Set filtered blocked topic count. */
+ public abstract GetTopicsReportedStats.Builder setFilteredBlockedTopicCount(int value);
+
+ /** Set number of topic ids returned. */
+ public abstract GetTopicsReportedStats.Builder setTopicIdsCount(int value);
+
+ /** build for {@link GetTopicsReportedStats}. */
+ public abstract GetTopicsReportedStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/RunAdBiddingPerCAProcessReportedStats.java b/adservices/service-core/java/com/android/adservices/service/stats/RunAdBiddingPerCAProcessReportedStats.java
new file mode 100644
index 000000000..c73677895
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/RunAdBiddingPerCAProcessReportedStats.java
@@ -0,0 +1,104 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+
+/** Class for runAdBiddingPerCA process reported stats. */
+@AutoValue
+public abstract class RunAdBiddingPerCAProcessReportedStats {
+ /** @return num of ads for bidding. */
+ public abstract int getNumOfAdsForBidding();
+
+ /** @return runAdBiddingPerCA latency in milliseconds. */
+ public abstract int getRunAdBiddingPerCaLatencyInMillis();
+
+ /** @return runAdBiddingPerCA result code. */
+ public abstract int getRunAdBiddingPerCaResultCode();
+
+ /** @return getBuyerDecisionLogic script latency in milliseconds. */
+ public abstract int getGetBuyerDecisionLogicLatencyInMillis();
+
+ /** @return getBuyerDecisionLogic result code. */
+ public abstract int getGetBuyerDecisionLogicResultCode();
+
+ /** @return getBuyerDecisionLogic script type. */
+ public abstract int getBuyerDecisionLogicScriptType();
+
+ /** @return fetched buyer decision logic script size in bytes. */
+ public abstract int getFetchedBuyerDecisionLogicScriptSizeInBytes();
+
+ /** @return num of keys of trusted bidding signals. */
+ public abstract int getNumOfKeysOfTrustedBiddingSignals();
+
+ /** @return fetched trusted bidding signals data size in bytes. */
+ public abstract int getFetchedTrustedBiddingSignalsDataSizeInBytes();
+
+ /** @return getTrustedBiddingSignals latency in milliseconds. */
+ public abstract int getGetTrustedBiddingSignalsLatencyInMillis();
+
+ /** @return getTrustedBiddingSignals result code. */
+ public abstract int getGetTrustedBiddingSignalsResultCode();
+
+ /** @return the total generateBids script execution time when runBidding() is called. */
+ public abstract int getGenerateBidsLatencyInMillis();
+
+ /** @return the overall latency of runBidding(). */
+ public abstract int getRunBiddingLatencyInMillis();
+
+ /** @return the runBidding() result code. */
+ public abstract int getRunBiddingResultCode();
+
+ /** @return generic builder. */
+ static Builder builder() {
+ return new AutoValue_RunAdBiddingPerCAProcessReportedStats.Builder();
+ }
+
+ /** Builder class for {@link RunAdBiddingPerCAProcessReportedStats}. */
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setNumOfAdsForBidding(int value);
+
+ abstract Builder setRunAdBiddingPerCaLatencyInMillis(int value);
+
+ abstract Builder setRunAdBiddingPerCaResultCode(int value);
+
+ abstract Builder setGetBuyerDecisionLogicLatencyInMillis(int value);
+
+ abstract Builder setGetBuyerDecisionLogicResultCode(int value);
+
+ abstract Builder setBuyerDecisionLogicScriptType(int value);
+
+ abstract Builder setFetchedBuyerDecisionLogicScriptSizeInBytes(int value);
+
+ abstract Builder setNumOfKeysOfTrustedBiddingSignals(int value);
+
+ abstract Builder setFetchedTrustedBiddingSignalsDataSizeInBytes(int value);
+
+ abstract Builder setGetTrustedBiddingSignalsLatencyInMillis(int value);
+
+ abstract Builder setGetTrustedBiddingSignalsResultCode(int value);
+
+ abstract Builder setGenerateBidsLatencyInMillis(int value);
+
+ abstract Builder setRunBiddingLatencyInMillis(int value);
+
+ abstract Builder setRunBiddingResultCode(int value);
+
+ abstract RunAdBiddingPerCAProcessReportedStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/RunAdBiddingProcessReportedStats.java b/adservices/service-core/java/com/android/adservices/service/stats/RunAdBiddingProcessReportedStats.java
new file mode 100644
index 000000000..36e4cddc9
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/RunAdBiddingProcessReportedStats.java
@@ -0,0 +1,88 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+
+/** Class for runAdBidding process reported stats. */
+@AutoValue
+public abstract class RunAdBiddingProcessReportedStats {
+ /** @return getBuyersCustomAudienceLatencyInMills. */
+ public abstract int getGetBuyersCustomAudienceLatencyInMills();
+
+ /** @return getBuyersCustomAudience result code. */
+ public abstract int getGetBuyersCustomAudienceResultCode();
+
+ /** @return num of buyers requests. */
+ public abstract int getNumBuyersRequested();
+
+ /** @return num of buyers fetched. */
+ public abstract int getNumBuyersFetched();
+
+ /** @return num of ads entered bidding. */
+ public abstract int getNumOfAdsEnteringBidding();
+
+ /** @return num of CAs entered bidding. */
+ public abstract int getNumOfCasEnteringBidding();
+
+ /** @return num of CAs post bidding. */
+ public abstract int getNumOfCasPostBidding();
+
+ /** @return ratio of CAs selected rmkt ads. */
+ public abstract float getRatioOfCasSelectingRmktAds();
+
+ /** @return runAdBidding latency in milliseconds. */
+ public abstract int getRunAdBiddingLatencyInMillis();
+
+ /** @return runAdBidding result code. */
+ public abstract int getRunAdBiddingResultCode();
+
+ /** @return total ad bidding stage latency in milliseconds. */
+ public abstract int getTotalAdBiddingStageLatencyInMillis();
+
+ static Builder builder() {
+ return new AutoValue_RunAdBiddingProcessReportedStats.Builder();
+ }
+
+ /** Builder class for RunAdBiddingProcessReportedStats. */
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setGetBuyersCustomAudienceLatencyInMills(int value);
+
+ abstract Builder setGetBuyersCustomAudienceResultCode(int value);
+
+ abstract Builder setNumBuyersRequested(int value);
+
+ abstract Builder setNumBuyersFetched(int value);
+
+ abstract Builder setNumOfAdsEnteringBidding(int value);
+
+ abstract Builder setNumOfCasEnteringBidding(int value);
+
+ abstract Builder setNumOfCasPostBidding(int value);
+
+ abstract Builder setRatioOfCasSelectingRmktAds(float value);
+
+ abstract Builder setRunAdBiddingLatencyInMillis(int value);
+
+ abstract Builder setRunAdBiddingResultCode(int value);
+
+ abstract Builder setTotalAdBiddingStageLatencyInMillis(int value);
+
+ abstract RunAdBiddingProcessReportedStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/RunAdScoringProcessReportedStats.java b/adservices/service-core/java/com/android/adservices/service/stats/RunAdScoringProcessReportedStats.java
new file mode 100644
index 000000000..5755cc0e4
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/RunAdScoringProcessReportedStats.java
@@ -0,0 +1,109 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+
+/** Class for runAdScoring process reported stats. */
+@AutoValue
+public abstract class RunAdScoringProcessReportedStats {
+ /** @return getAdSelectionLogic latency in milliseconds. */
+ public abstract int getGetAdSelectionLogicLatencyInMillis();
+
+ /** @return getAdSelectionLogic result code. */
+ public abstract int getGetAdSelectionLogicResultCode();
+
+ /** @return getAdSelectionLogic script type. */
+ public abstract int getGetAdSelectionLogicScriptType();
+
+ /** @return fetched ad selection logic script size in bytes. */
+ public abstract int getFetchedAdSelectionLogicScriptSizeInBytes();
+
+ /** @return getTrustedScoringSignals latency in milliseconds. */
+ public abstract int getGetTrustedScoringSignalsLatencyInMillis();
+
+ /** @return getTrustedScoringSignals result code. */
+ public abstract int getGetTrustedScoringSignalsResultCode();
+
+ /** @return fetched trusted scoring signals data size in bytes. */
+ public abstract int getFetchedTrustedScoringSignalsDataSizeInBytes();
+
+ /** @return the total scoreAds script execution time when getAdScores() is called. */
+ public abstract int getScoreAdsLatencyInMillis();
+
+ /** @return the overall latency of the getAdScores(). */
+ public abstract int getGetAdScoresLatencyInMillis();
+
+ /** @return the getAdScores result code. */
+ public abstract int getGetAdScoresResultCode();
+
+ /** @return the num of CAs entered scoring. */
+ public abstract int getNumOfCasEnteringScoring();
+
+ /** @return the num of remarketing ads entered scoring. */
+ public abstract int getNumOfRemarketingAdsEnteringScoring();
+
+ /** @return the num of contextual ads entered scoring. */
+ public abstract int getNumOfContextualAdsEnteringScoring();
+
+ /** @return the overall latency of the runAdScoring process. */
+ public abstract int getRunAdScoringLatencyInMillis();
+
+ /** @return the runAdScoring result code. */
+ public abstract int getRunAdScoringResultCode();
+
+ /** @return generic builder. */
+ static Builder builder() {
+ return new AutoValue_RunAdScoringProcessReportedStats.Builder();
+ }
+
+ /** Builder class for RunAdScoringProcessReportedStats. */
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setGetAdSelectionLogicLatencyInMillis(int value);
+
+ abstract Builder setGetAdSelectionLogicResultCode(int value);
+
+ abstract Builder setGetAdSelectionLogicScriptType(int value);
+
+ abstract Builder setFetchedAdSelectionLogicScriptSizeInBytes(int value);
+
+ abstract Builder setGetTrustedScoringSignalsLatencyInMillis(int value);
+
+ abstract Builder setGetTrustedScoringSignalsResultCode(int value);
+
+ abstract Builder setFetchedTrustedScoringSignalsDataSizeInBytes(int value);
+
+ abstract Builder setScoreAdsLatencyInMillis(int value);
+
+ abstract Builder setGetAdScoresLatencyInMillis(int value);
+
+ abstract Builder setGetAdScoresResultCode(int value);
+
+ abstract Builder setNumOfCasEnteringScoring(int value);
+
+ abstract Builder setNumOfRemarketingAdsEnteringScoring(int value);
+
+ abstract Builder setNumOfContextualAdsEnteringScoring(int value);
+
+ abstract Builder setRunAdScoringLatencyInMillis(int value);
+
+ abstract Builder setRunAdScoringResultCode(int value);
+
+ abstract RunAdScoringProcessReportedStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/RunAdSelectionProcessReportedStats.java b/adservices/service-core/java/com/android/adservices/service/stats/RunAdSelectionProcessReportedStats.java
new file mode 100644
index 000000000..32ed79083
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/RunAdSelectionProcessReportedStats.java
@@ -0,0 +1,64 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+
+/** Class for runAdSelection process reported stats. */
+@AutoValue
+public abstract class RunAdSelectionProcessReportedStats {
+ /** @return isRemarketingAdsWon. */
+ public abstract boolean getIsRemarketingAdsWon();
+
+ /** @return DBAdSelectionSizeInBytes. */
+ public abstract int getDBAdSelectionSizeInBytes();
+
+ /** @return persistAdSelectionLatencyInMills. */
+ public abstract int getPersistAdSelectionLatencyInMillis();
+
+ /** @return persistAdSelectionResultCode. */
+ public abstract int getPersistAdSelectionResultCode();
+
+ /** @return runAdSelectionLatencyInMillis. */
+ public abstract int getRunAdSelectionLatencyInMillis();
+
+ /** @return runAdSelectionResultCode. */
+ public abstract int getRunAdSelectionResultCode();
+
+ /** @return generic builder. */
+ static Builder builder() {
+ return new AutoValue_RunAdSelectionProcessReportedStats.Builder();
+ }
+
+ /** Builder class for {@link RunAdSelectionProcessReportedStats}. */
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setIsRemarketingAdsWon(boolean value);
+
+ abstract Builder setDBAdSelectionSizeInBytes(int value);
+
+ abstract Builder setPersistAdSelectionLatencyInMillis(int value);
+
+ abstract Builder setPersistAdSelectionResultCode(int value);
+
+ abstract Builder setRunAdSelectionLatencyInMillis(int value);
+
+ abstract Builder setRunAdSelectionResultCode(int value);
+
+ abstract RunAdSelectionProcessReportedStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/StatsdAdServicesLogger.java b/adservices/service-core/java/com/android/adservices/service/stats/StatsdAdServicesLogger.java
index 937ed6ba2..2454878e5 100644
--- a/adservices/service-core/java/com/android/adservices/service/stats/StatsdAdServicesLogger.java
+++ b/adservices/service-core/java/com/android/adservices/service/stats/StatsdAdServicesLogger.java
@@ -17,7 +17,16 @@
package com.android.adservices.service.stats;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__FLEDGE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__UNKNOWN;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_GET_TOPICS_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.BACKGROUND_FETCH_PROCESS_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.RUN_AD_BIDDING_PROCESS_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.RUN_AD_SCORING_PROCESS_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.RUN_AD_SELECTION_PROCESS_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED;
import javax.annotation.concurrent.ThreadSafe;
@@ -39,6 +48,7 @@ public class StatsdAdServicesLogger implements AdServicesLogger {
}
return sStatsdAdServicesLogger;
}
+
/** log method for measurement reporting. */
public void logMeasurementReports(MeasurementReportsStats measurementReportsStats) {
AdServicesStatsLog.write(
@@ -61,25 +71,19 @@ public class StatsdAdServicesLogger implements AdServicesLogger {
/** log method for UI stats. */
public void logUIStats(UIStats uiStats) {
- AdServicesStatsLog.write(
- uiStats.getCode(),
- uiStats.getRegion(),
- uiStats.getAction());
+ AdServicesStatsLog.write(uiStats.getCode(), uiStats.getRegion(), uiStats.getAction());
}
@Override
- public void logFledgeApiCallStats(int apiName, int resultCode) {
- // TODO(b/233628316): Implement latency measurement
- logApiCallStats(
- new ApiCallStats.Builder()
- .setCode(AD_SERVICES_API_CALLED)
- .setApiClass(AD_SERVICES_API_CALLED__API_CLASS__FLEDGE)
- .setApiName(apiName)
- .setResultCode(resultCode)
- // TODO(b/233629557): Implement app/SDK reporting
- .setSdkPackageName("")
- .setAppPackageName("")
- .build());
+ public void logFledgeApiCallStats(int apiName, int resultCode, int latencyMs) {
+ AdServicesStatsLog.write(
+ AD_SERVICES_API_CALLED,
+ AD_SERVICES_API_CALLED__API_CLASS__UNKNOWN,
+ apiName,
+ "",
+ "",
+ latencyMs,
+ resultCode);
}
@Override
@@ -91,4 +95,128 @@ public class StatsdAdServicesLogger implements AdServicesLogger {
stats.getResponseSize(),
stats.getAdTechDomain());
}
+
+ @Override
+ public void logRunAdSelectionProcessReportedStats(RunAdSelectionProcessReportedStats stats) {
+ AdServicesStatsLog.write(
+ RUN_AD_SELECTION_PROCESS_REPORTED,
+ stats.getIsRemarketingAdsWon(),
+ stats.getDBAdSelectionSizeInBytes(),
+ stats.getPersistAdSelectionLatencyInMillis(),
+ stats.getPersistAdSelectionResultCode(),
+ stats.getRunAdSelectionLatencyInMillis(),
+ stats.getRunAdSelectionResultCode());
+ }
+
+ @Override
+ public void logRunAdBiddingProcessReportedStats(RunAdBiddingProcessReportedStats stats) {
+ AdServicesStatsLog.write(
+ RUN_AD_BIDDING_PROCESS_REPORTED,
+ stats.getGetBuyersCustomAudienceLatencyInMills(),
+ stats.getGetBuyersCustomAudienceResultCode(),
+ stats.getNumBuyersRequested(),
+ stats.getNumBuyersFetched(),
+ stats.getNumOfAdsEnteringBidding(),
+ stats.getNumOfCasEnteringBidding(),
+ stats.getNumOfCasPostBidding(),
+ stats.getRatioOfCasSelectingRmktAds(),
+ stats.getRunAdBiddingLatencyInMillis(),
+ stats.getRunAdBiddingResultCode(),
+ stats.getTotalAdBiddingStageLatencyInMillis());
+ }
+
+ @Override
+ public void logRunAdScoringProcessReportedStats(RunAdScoringProcessReportedStats stats) {
+ AdServicesStatsLog.write(
+ RUN_AD_SCORING_PROCESS_REPORTED,
+ stats.getGetAdSelectionLogicLatencyInMillis(),
+ stats.getGetAdSelectionLogicResultCode(),
+ stats.getGetAdSelectionLogicScriptType(),
+ stats.getFetchedAdSelectionLogicScriptSizeInBytes(),
+ stats.getGetTrustedScoringSignalsLatencyInMillis(),
+ stats.getGetTrustedScoringSignalsResultCode(),
+ stats.getFetchedTrustedScoringSignalsDataSizeInBytes(),
+ stats.getScoreAdsLatencyInMillis(),
+ stats.getGetAdScoresLatencyInMillis(),
+ stats.getGetAdScoresResultCode(),
+ stats.getNumOfCasEnteringScoring(),
+ stats.getNumOfRemarketingAdsEnteringScoring(),
+ stats.getNumOfContextualAdsEnteringScoring(),
+ stats.getRunAdScoringLatencyInMillis(),
+ stats.getRunAdScoringResultCode());
+ }
+
+ @Override
+ public void logRunAdBiddingPerCAProcessReportedStats(
+ RunAdBiddingPerCAProcessReportedStats stats) {
+ AdServicesStatsLog.write(
+ RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED,
+ stats.getNumOfAdsForBidding(),
+ stats.getRunAdBiddingPerCaLatencyInMillis(),
+ stats.getRunAdBiddingPerCaResultCode(),
+ stats.getGetBuyerDecisionLogicLatencyInMillis(),
+ stats.getGetBuyerDecisionLogicResultCode(),
+ stats.getBuyerDecisionLogicScriptType(),
+ stats.getFetchedBuyerDecisionLogicScriptSizeInBytes(),
+ stats.getNumOfKeysOfTrustedBiddingSignals(),
+ stats.getFetchedTrustedBiddingSignalsDataSizeInBytes(),
+ stats.getGetTrustedBiddingSignalsLatencyInMillis(),
+ stats.getGetTrustedBiddingSignalsResultCode(),
+ stats.getGenerateBidsLatencyInMillis(),
+ stats.getRunBiddingLatencyInMillis(),
+ stats.getRunBiddingResultCode());
+ }
+
+ @Override
+ public void logBackgroundFetchProcessReportedStats(BackgroundFetchProcessReportedStats stats) {
+ AdServicesStatsLog.write(
+ BACKGROUND_FETCH_PROCESS_REPORTED,
+ stats.getLatencyInMillis(),
+ stats.getNumOfEligibleToUpdateCas(),
+ stats.getResultCode());
+ }
+
+ @Override
+ public void logUpdateCustomAudienceProcessReportedStats(
+ UpdateCustomAudienceProcessReportedStats stats) {
+ AdServicesStatsLog.write(
+ UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED,
+ stats.getLatencyInMills(),
+ stats.getResultCode(),
+ stats.getDataSizeOfAdsInBytes(),
+ stats.getNumOfAds());
+ }
+
+ @Override
+ public void logGetTopicsReportedStats(GetTopicsReportedStats stats) {
+ AdServicesStatsLog.write(
+ AD_SERVICES_GET_TOPICS_REPORTED,
+ new int[] {}, // TODO(b/256649873): Log empty topic ids until the long term
+ // solution.
+ stats.getDuplicateTopicCount(),
+ stats.getFilteredBlockedTopicCount(),
+ stats.getTopicIdsCount());
+ }
+
+ @Override
+ public void logEpochComputationGetTopTopicsStats(EpochComputationGetTopTopicsStats stats) {
+ AdServicesStatsLog.write(
+ AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED,
+ stats.getTopTopicCount(),
+ stats.getPaddedRandomTopicsCount(),
+ stats.getAppsConsideredCount(),
+ stats.getSdksConsideredCount());
+ }
+
+ @Override
+ public void logEpochComputationClassifierStats(EpochComputationClassifierStats stats) {
+ AdServicesStatsLog.write(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED,
+ stats.getTopicIds().stream().mapToInt(Integer::intValue).toArray(),
+ stats.getBuildId(),
+ stats.getAssetVersion(),
+ stats.getClassifierType(),
+ stats.getOnDeviceClassifierStatus(),
+ stats.getPrecomputedClassifierStatus());
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/UpdateCustomAudienceProcessReportedStats.java b/adservices/service-core/java/com/android/adservices/service/stats/UpdateCustomAudienceProcessReportedStats.java
new file mode 100644
index 000000000..4cacc3d3e
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/stats/UpdateCustomAudienceProcessReportedStats.java
@@ -0,0 +1,54 @@
+/*
+ * 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.stats;
+
+import com.google.auto.value.AutoValue;
+
+/** Class for updateCustomAudience process reported stats. */
+@AutoValue
+public abstract class UpdateCustomAudienceProcessReportedStats {
+ /** @return latency in milliseconds. */
+ public abstract int getLatencyInMills();
+
+ /** @return update custom audience result code. */
+ public abstract int getResultCode();
+
+ /** @return data size of ads in bytes. */
+ public abstract int getDataSizeOfAdsInBytes();
+
+ /** @return num of ads. */
+ public abstract int getNumOfAds();
+
+ /** @return generic builder. */
+ static Builder builder() {
+ return new AutoValue_UpdateCustomAudienceProcessReportedStats.Builder();
+ }
+
+ /** Builder class for {@link UpdateCustomAudienceProcessReportedStats} object. */
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setLatencyInMills(int value);
+
+ abstract Builder setResultCode(int value);
+
+ abstract Builder setDataSizeOfAdsInBytes(int value);
+
+ abstract Builder setNumOfAds(int value);
+
+ abstract UpdateCustomAudienceProcessReportedStats build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java b/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java
index 22eb558da..2fc29f291 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java
@@ -20,10 +20,12 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Pair;
import com.android.adservices.LogUtil;
+import com.android.adservices.data.DbHelper;
import com.android.adservices.data.topics.Topic;
import com.android.adservices.data.topics.TopicsDao;
import com.android.adservices.data.topics.TopicsTables;
@@ -31,9 +33,8 @@ import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.internal.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -51,8 +52,8 @@ import java.util.stream.Collectors;
*
* <p>See go/rb-topics-app-update for details.
*/
-// TODO(b/239553255): Use transaction for methods have both read and write to the database.
public class AppUpdateManager {
+ private static final String EMPTY_SDK = "";
private static AppUpdateManager sSingleton;
// Tables that needs to be wiped out for application data
@@ -77,11 +78,17 @@ public class AppUpdateManager {
TopicsTables.AppUsageHistoryContract.APP)
};
+ private final DbHelper mDbHelper;
private final TopicsDao mTopicsDao;
private final Random mRandom;
private final Flags mFlags;
- AppUpdateManager(@NonNull TopicsDao topicsDao, @NonNull Random random, @NonNull Flags flags) {
+ AppUpdateManager(
+ @NonNull DbHelper dbHelper,
+ @NonNull TopicsDao topicsDao,
+ @NonNull Random random,
+ @NonNull Flags flags) {
+ mDbHelper = dbHelper;
mTopicsDao = topicsDao;
mRandom = random;
mFlags = flags;
@@ -99,6 +106,7 @@ public class AppUpdateManager {
if (sSingleton == null) {
sSingleton =
new AppUpdateManager(
+ DbHelper.getInstance(context),
TopicsDao.getInstance(context),
new Random(),
FlagsFactory.getFlags());
@@ -109,30 +117,77 @@ public class AppUpdateManager {
}
/**
- * Delete application data for a specific application.
+ * Handle application uninstallation for Topics API.
*
- * <p>This method allows other usages besides daily maintenance job, such as real-time data
- * wiping for an app uninstallation.
+ * <ul>
+ * <li>Delete all derived data for an uninstalled app.
+ * <li>When the feature is enabled, remove a topic if it has the uninstalled app as the only
+ * contributor in an epoch.
+ * </ul>
*
- * @param apps a {@link List} of applications to wipe data for
+ * @param packageUri The {@link Uri} got from Broadcast Intent
+ * @param currentEpochId the epoch id of current Epoch
*/
- public void deleteAppDataFromTableByApps(@NonNull List<String> apps) {
- for (Pair<String, String> tableColumnNamePair : TABLE_INFO_TO_ERASE_APP_DATA) {
- mTopicsDao.deleteAppFromTable(
- tableColumnNamePair.first, tableColumnNamePair.second, apps);
+ public void handleAppUninstallationInRealTime(@NonNull Uri packageUri, long currentEpochId) {
+ String packageName = convertUriToAppName(packageUri);
+
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ LogUtil.e(
+ "Database is not available, Stop processing app uninstallation for %s!",
+ packageName);
+ return;
}
- LogUtil.v("Have deleted data for application " + apps);
+ // This cross db and java boundaries multiple times, so we need to have a db transaction.
+ db.beginTransaction();
+
+ try {
+ if (supportsTopicContributorFeature()) {
+ handleTopTopicsWithoutContributors(currentEpochId, packageName);
+ }
+
+ deleteAppDataFromTableByApps(List.of(packageName));
+
+ // Mark the transaction successful.
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ LogUtil.d("End of processing app uninstallation for %s", packageName);
+ }
}
/**
- * Delete application data for a specific application.
+ * Handle application installation for Topics API.
+ *
+ * <p>Assign topics to past epochs for the installed app.
*
* @param packageUri The {@link Uri} got from Broadcast Intent
+ * @param currentEpochId the epoch id of current Epoch
*/
- public void deleteAppDataByUri(@NonNull Uri packageUri) {
- String appName = convertUriToAppName(packageUri);
- deleteAppDataFromTableByApps(List.of(appName));
+ public void handleAppInstallationInRealTime(@NonNull Uri packageUri, long currentEpochId) {
+ String packageName = convertUriToAppName(packageUri);
+
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ LogUtil.e(
+ "Database is not available, Stop processing app installation for %s",
+ packageName);
+ return;
+ }
+
+ // This cross db and java boundaries multiple times, so we need to have a db transaction.
+ db.beginTransaction();
+
+ try {
+ assignTopicsToNewlyInstalledApps(packageName, currentEpochId);
+
+ // Mark the transaction successful.
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ LogUtil.d("End of processing app installation for %s", packageName);
+ }
}
/**
@@ -149,8 +204,9 @@ public class AppUpdateManager {
* </ul>
*
* @param context the context
+ * @param currentEpochId epoch ID of current epoch
*/
- public void reconcileUninstalledApps(@NonNull Context context) {
+ public void reconcileUninstalledApps(@NonNull Context context, long currentEpochId) {
Set<String> currentInstalledApps = getCurrentInstalledApps(context);
Set<String> unhandledUninstalledApps = getUnhandledUninstalledApps(currentInstalledApps);
if (unhandledUninstalledApps.isEmpty()) {
@@ -160,8 +216,25 @@ public class AppUpdateManager {
LogUtil.v(
"Detect below unhandled mismatched applications: %s",
unhandledUninstalledApps.toString());
- handleUninstalledApps(unhandledUninstalledApps);
- LogUtil.v("App uninstallation reconciliation is finished!");
+
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ LogUtil.e("Database is not available, Stop reconciling app uninstallation in Topics!");
+ return;
+ }
+
+ // This cross db and java boundaries multiple times, so we need to have a db transaction.
+ db.beginTransaction();
+
+ try {
+ handleUninstalledAppsInReconciliation(unhandledUninstalledApps, currentEpochId);
+
+ // Mark the transaction successful.
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ LogUtil.v("App uninstallation reconciliation in Topics is finished!");
+ }
}
/**
@@ -191,25 +264,30 @@ public class AppUpdateManager {
LogUtil.v(
"Detect below unhandled installed applications: %s",
unhandledInstalledApps.toString());
- handleInstalledApps(unhandledInstalledApps, currentEpochId);
- LogUtil.v("App installation reconciliation is finished!");
- }
- /**
- * An overloading method to allow passing in Uri instead of app name in string format.
- *
- * <p>For newly installed app, to allow it get topics in current epoch, one of top topics in
- * past epochs will be assigned to this app.
- *
- * <p>See more details in go/rb-topics-app-update
- *
- * @param packageUri the Uri of newly installed application
- * @param currentEpochId current epoch id
- */
- public void assignTopicsToNewlyInstalledApps(@NonNull Uri packageUri, long currentEpochId) {
- assignTopicsToNewlyInstalledApps(convertUriToAppName(packageUri), currentEpochId);
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ LogUtil.e("Database is not available, Stop reconciling app installation in Topics!");
+ return;
+ }
+
+ // This cross db and java boundaries multiple times, so we need to have a db transaction.
+ db.beginTransaction();
+
+ try {
+ handleInstalledAppsInReconciliation(unhandledInstalledApps, currentEpochId);
+
+ // Mark the transaction successful.
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ LogUtil.v("App installation reconciliation in Topics is finished!");
+ }
}
+ // TODO(b/256703300): Currently we handled app-sdk topic assignments in serving flow. Move the
+ // logic back to app installation after we can get all SDKs when an app is
+ // installed.
/**
* For a newly installed app, in case SDKs that this app uses are not known when the app is
* installed, the returned topic for an SDK can only be assigned when user calls getTopic().
@@ -231,7 +309,7 @@ public class AppUpdateManager {
}
int numberOfLookBackEpochs = mFlags.getTopicsNumberOfLookBackEpochs();
- Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
+ Pair<String, String> appOnlyCaller = Pair.create(app, EMPTY_SDK);
Pair<String, String> appSdkCaller = Pair.create(app, sdk);
// Get ReturnedTopics and CallerCanLearnTopics for past epochs in
@@ -286,32 +364,220 @@ public class AppUpdateManager {
/**
* Generating a random topic from given top topic list
*
- * @param topTopics a {@link List} of top topics in current epoch
- * @param numberOfTopTopics the number of regular top topics
- * @param numberOfRandomTopics the number of random top topics
+ * @param regularTopics a {@link List} of non-random topics in current epoch, excluding those
+ * which have no contributors
+ * @param randomTopics a {@link List} of random top topics
* @param percentageForRandomTopic the probability to select random object
* @return a selected {@link Topic} to be assigned to newly installed app
*/
+ @VisibleForTesting
@NonNull
- public Topic selectAssignedTopicFromTopTopics(
- @NonNull List<Topic> topTopics,
- int numberOfTopTopics,
- int numberOfRandomTopics,
+ Topic selectAssignedTopicFromTopTopics(
+ @NonNull List<Topic> regularTopics,
+ @NonNull List<Topic> randomTopics,
int percentageForRandomTopic) {
- // Validate the Top Topics are combined with correct number of topics and random topics
- Preconditions.checkArgument(numberOfTopTopics + numberOfRandomTopics == topTopics.size());
-
// If random number is in [0, randomPercentage - 1], a random topic will be selected.
boolean shouldSelectRandomTopic = mRandom.nextInt(100) < percentageForRandomTopic;
- if (shouldSelectRandomTopic) {
- // Generate a random number to pick one of random topics.
- // Random topics' index starts from numberOfTopTopics
- return topTopics.get(numberOfTopTopics + mRandom.nextInt(numberOfRandomTopics));
+ return shouldSelectRandomTopic
+ ? randomTopics.get(mRandom.nextInt(randomTopics.size()))
+ : regularTopics.get(mRandom.nextInt(regularTopics.size()));
+ }
+
+ /**
+ * Delete application data for a specific application.
+ *
+ * <p>This method allows other usages besides daily maintenance job, such as real-time data
+ * wiping for an app uninstallation.
+ *
+ * @param apps a {@link List} of applications to wipe data for
+ */
+ @VisibleForTesting
+ void deleteAppDataFromTableByApps(@NonNull List<String> apps) {
+ List<Pair<String, String>> tableToEraseData =
+ Arrays.stream(TABLE_INFO_TO_ERASE_APP_DATA).collect(Collectors.toList());
+ if (supportsTopicContributorFeature()) {
+ tableToEraseData.add(
+ Pair.create(
+ TopicsTables.TopicContributorsContract.TABLE,
+ TopicsTables.TopicContributorsContract.APP));
+ }
+
+ mTopicsDao.deleteFromTableByColumn(
+ /* tableNamesAndColumnNamePairs */ tableToEraseData, /* valuesToDelete */ apps);
+
+ LogUtil.v("Have deleted data for application " + apps);
+ }
+
+ /**
+ * Assign a top Topic for the newly installed app. This allows SDKs in the newly installed app
+ * to get the past 3 epochs' topics if they did observe the topic in the past.
+ *
+ * <p>See more details in go/rb-topics-app-update
+ *
+ * @param app the app package name of newly installed application
+ * @param currentEpochId current epoch id
+ */
+ @VisibleForTesting
+ void assignTopicsToNewlyInstalledApps(@NonNull String app, long currentEpochId) {
+ Objects.requireNonNull(app);
+
+ final int numberOfEpochsToAssignTopics = mFlags.getTopicsNumberOfLookBackEpochs();
+ final int numberOfTopTopics = mFlags.getTopicsNumberOfTopTopics();
+ final int topicsPercentageForRandomTopic = mFlags.getTopicsPercentageForRandomTopic();
+
+ Pair<String, String> appOnlyCaller = Pair.create(app, EMPTY_SDK);
+
+ // For each past epoch, assign a random topic to this newly installed app.
+ // The assigned topic should align the probability with rule to generate top topics.
+ for (long epochId = currentEpochId - 1;
+ epochId >= currentEpochId - numberOfEpochsToAssignTopics && epochId >= 0;
+ epochId--) {
+ List<Topic> topTopics = mTopicsDao.retrieveTopTopics(epochId);
+
+ if (topTopics.isEmpty()) {
+ LogUtil.v(
+ "Empty top topic list in Epoch %d, do not assign topic to App %s.",
+ epochId, app);
+ continue;
+ }
+
+ // Regular Topics are placed at the beginning of top topic list.
+ List<Topic> regularTopics = topTopics.subList(0, numberOfTopTopics);
+ // If enabled, filter out topics without contributors.
+ if (supportsTopicContributorFeature()) {
+ regularTopics = filterRegularTopicsWithoutContributors(regularTopics, epochId);
+ }
+ List<Topic> randomTopics = topTopics.subList(numberOfTopTopics, topTopics.size());
+
+ if (regularTopics.isEmpty() && randomTopics.isEmpty()) {
+ LogUtil.v(
+ "No topic is available to assign in Epoch %d, do not assign topic to App"
+ + " %s.",
+ epochId, app);
+ continue;
+ }
+
+ Topic assignedTopic =
+ selectAssignedTopicFromTopTopics(
+ regularTopics, randomTopics, topicsPercentageForRandomTopic);
+
+ // Persist this topic to database as returned topic in this epoch
+ mTopicsDao.persistReturnedAppTopicsMap(epochId, Map.of(appOnlyCaller, assignedTopic));
+
+ LogUtil.v(
+ "Topic %s has been assigned to newly installed App %s in Epoch %d",
+ assignedTopic.getTopic(), app, epochId);
}
+ }
- // Regular top topics start from index 0
- return topTopics.get(mRandom.nextInt(numberOfTopTopics));
+ /**
+ * When an app is uninstalled, we need to check whether any of its classified topics has no
+ * contributors on epoch basis for past epochs to look back. Note in an epoch, an app is a
+ * contributor to a topic if the app has called Topics API in this epoch and is classified to
+ * the topic.
+ *
+ * <p>If such topic exists, remove this topic from ReturnedTopicsTable in the epoch. This method
+ * is invoked before {@code deleteAppDataFromTableByApps}, so the uninstalled app will be
+ * cleared in TopicContributors Table there.
+ *
+ * <p>NOTE: We are only interested in the epochs which will be used for getTopics(), i.e. past
+ * numberOfLookBackEpochs epochs.
+ *
+ * @param currentEpochId the id of epoch when the method gets invoked
+ * @param uninstalledApp the newly uninstalled app
+ */
+ @VisibleForTesting
+ void handleTopTopicsWithoutContributors(long currentEpochId, @NonNull String uninstalledApp) {
+ // This check is on epoch basis for past epochs to look back
+ for (long epochId = currentEpochId - 1;
+ epochId >= currentEpochId - mFlags.getTopicsNumberOfLookBackEpochs()
+ && epochId >= 0;
+ epochId--) {
+ Map<String, List<Topic>> appClassificationTopics =
+ mTopicsDao.retrieveAppClassificationTopics(epochId);
+ List<Topic> topTopics = mTopicsDao.retrieveTopTopics(epochId);
+ Map<Integer, Set<String>> topTopicsToContributorsMap =
+ mTopicsDao.retrieveTopicToContributorsMap(epochId);
+
+ List<Topic> classifiedTopics =
+ appClassificationTopics.getOrDefault(uninstalledApp, new ArrayList<>());
+ // Collect all top topics to delete to make only one Db Update
+ List<String> topTopicsToDelete =
+ classifiedTopics.stream()
+ .filter(
+ classifiedTopic ->
+ topTopics.contains(classifiedTopic)
+ && topTopicsToContributorsMap.containsKey(
+ classifiedTopic.getTopic())
+ // Filter out the topic that has ONLY
+ // the uninstalled app as a contributor
+ && topTopicsToContributorsMap
+ .get(classifiedTopic.getTopic())
+ .size()
+ == 1
+ && topTopicsToContributorsMap
+ .get(classifiedTopic.getTopic())
+ .contains(uninstalledApp))
+ .map(Topic::getTopic)
+ .map(String::valueOf)
+ .collect(Collectors.toList());
+
+ if (!topTopicsToDelete.isEmpty()) {
+ LogUtil.v(
+ "Topics %s will not have contributors at epoch %d. Delete them in"
+ + " epoch %d",
+ topTopicsToDelete, epochId, epochId);
+ }
+
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.TOPIC)),
+ topTopicsToDelete,
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ String.valueOf(epochId),
+ /* isStringEqualConditionColumnValue */ false);
+ }
+ }
+
+ /**
+ * Filter out regular topics without any contributors. Note in an epoch, an app is a contributor
+ * to a topic if the app has called Topics API in this epoch and is classified to the topic.
+ *
+ * <p>For padded Topics (Classifier randomly pads top topics if they are not enough), as we put
+ * {@link EpochManager#PADDED_TOP_TOPICS_STRING} into TopicContributors Map, padded topics
+ * actually have "contributor" PADDED_TOP_TOPICS_STRING. Therefore, they won't be filtered out.
+ *
+ * @param regularTopics non-random top topics
+ * @param epochId epochId of current epoch
+ * @return the filtered regular topics
+ */
+ @NonNull
+ @VisibleForTesting
+ List<Topic> filterRegularTopicsWithoutContributors(
+ @NonNull List<Topic> regularTopics, long epochId) {
+ Map<Integer, Set<String>> topicToContributorMap =
+ mTopicsDao.retrieveTopicToContributorsMap(epochId);
+ return regularTopics.stream()
+ .filter(
+ regularTopic ->
+ topicToContributorMap.containsKey(regularTopic.getTopic())
+ && !topicToContributorMap
+ .get(regularTopic.getTopic())
+ .isEmpty())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Check whether TopContributors Feature is enabled. It's enabled only when TopicContributors
+ * table is supported and the feature flag is on.
+ */
+ @VisibleForTesting
+ boolean supportsTopicContributorFeature() {
+ return mFlags.getEnableTopicContributorsCheck()
+ && mDbHelper.supportsTopicContributorsTable();
}
// An app will be regarded as an unhandled uninstalled app if it has an entry in any epoch of
@@ -368,7 +634,8 @@ public class AppUpdateManager {
// Get current installed applications from package manager
@NonNull
- private Set<String> getCurrentInstalledApps(Context context) {
+ @VisibleForTesting
+ Set<String> getCurrentInstalledApps(Context context) {
PackageManager packageManager = context.getPackageManager();
List<ApplicationInfo> appInfoList =
packageManager.getInstalledApplications(
@@ -377,73 +644,46 @@ public class AppUpdateManager {
return appInfoList.stream().map(appInfo -> appInfo.packageName).collect(Collectors.toSet());
}
+ /**
+ * Get App Package Name from a Uri.
+ *
+ * <p>Across PPAPI, package Uri is in the form of "package:com.example.adservices.sampleapp".
+ * "package" is a scheme of Uri and "com.example.adservices.sampleapp" is the app package name.
+ * Topics API persists app package name into database so this method extracts it from a Uri.
+ *
+ * @param packageUri the {@link Uri} of a package
+ * @return the app package name
+ */
+ @VisibleForTesting
+ @NonNull
+ String convertUriToAppName(@NonNull Uri packageUri) {
+ return packageUri.getSchemeSpecificPart();
+ }
+
// Handle Uninstalled applications that still have derived data in database
//
- // Currently, simply wipe out these data in the database for an app. i.e. Deleting all
- // derived data from all tables that are related to app (has app column)
- private void handleUninstalledApps(@NonNull Set<String> newlyUninstalledApps) {
- deleteAppDataFromTableByApps(new ArrayList<>(newlyUninstalledApps));
+ // 1) Delete all derived data for an uninstalled app.
+ // 2) Remove a topic if it has the uninstalled app as the only contributor in an epoch. In an
+ // epoch, an app is a contributor to a topic if the app has called Topics API in this epoch and
+ // is classified to the topic.
+ private void handleUninstalledAppsInReconciliation(
+ @NonNull Set<String> newlyUninstalledApps, long currentEpochId) {
+ for (String app : newlyUninstalledApps) {
+ if (supportsTopicContributorFeature()) {
+ handleTopTopicsWithoutContributors(currentEpochId, app);
+ }
+
+ deleteAppDataFromTableByApps(List.of(app));
+ }
}
// Handle newly installed applications
//
// Assign topics as real-time service to the app only, if the app isn't assigned with topics.
- private void handleInstalledApps(@NonNull Set<String> newlyInstalledApps, long currentEpochId) {
+ private void handleInstalledAppsInReconciliation(
+ @NonNull Set<String> newlyInstalledApps, long currentEpochId) {
for (String newlyInstalledApp : newlyInstalledApps) {
assignTopicsToNewlyInstalledApps(newlyInstalledApp, currentEpochId);
}
}
-
- //
- // For newly installed app, to allow it get topics in current epoch, one of top topics in past
- // epochs will be assigned to this app.
- //
- // See more details in go/rb-topics-app-update
- private void assignTopicsToNewlyInstalledApps(@NonNull String app, long currentEpochId) {
- Objects.requireNonNull(app);
-
- // Read topics related configurations from Flags
- final int numberOfEpochsToAssignTopics = mFlags.getTopicsNumberOfLookBackEpochs();
- final int topicsNumberOfTopTopics = mFlags.getTopicsNumberOfTopTopics();
- final int topicsNumberOfRandomTopics = mFlags.getTopicsNumberOfRandomTopics();
- final int topicsPercentageForRandomTopic = mFlags.getTopicsPercentageForRandomTopic();
-
- Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
-
- // For each past epoch, assign a random topic to this newly installed app.
- // The assigned topic should align the probability with rule to generate top topics.
- for (long epochId = currentEpochId - 1;
- epochId >= currentEpochId - numberOfEpochsToAssignTopics && epochId >= 0;
- epochId--) {
- List<Topic> topTopics = mTopicsDao.retrieveTopTopics(epochId);
-
- if (topTopics.isEmpty()) {
- LogUtil.v(
- "Empty top topic list in Epoch %d, do not assign topic to App %s in Epoch"
- + "%d.",
- epochId, app, epochId);
- continue;
- }
-
- Topic assignedTopic =
- selectAssignedTopicFromTopTopics(
- topTopics,
- topicsNumberOfTopTopics,
- topicsNumberOfRandomTopics,
- topicsPercentageForRandomTopic);
-
- // Persist this topic to database as returned topic in this epoch
- mTopicsDao.persistReturnedAppTopicsMap(epochId, Map.of(appOnlyCaller, assignedTopic));
-
- LogUtil.v(
- "Topic %s has been assigned to newly installed App %s in Epoch %d",
- assignedTopic.getTopic(), app, epochId);
- }
- }
-
- // packageUri.toString() has only app name, without "package:" in the front, i.e. it'll be like
- // "com.example.adservices.sampleapp".
- private String convertUriToAppName(@NonNull Uri packageUri) {
- return packageUri.getSchemeSpecificPart();
- }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/CacheManager.java b/adservices/service-core/java/com/android/adservices/service/topics/CacheManager.java
index 91c9950a7..25358bf52 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/CacheManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/CacheManager.java
@@ -26,6 +26,9 @@ import com.android.adservices.data.topics.Topic;
import com.android.adservices.data.topics.TopicsDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.AdServicesLoggerImpl;
+import com.android.adservices.service.stats.GetTopicsReportedStats;
import com.android.internal.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -61,12 +64,15 @@ public class CacheManager implements Dumpable {
private HashSet<Topic> mCachedBlockedTopics = new HashSet<>();
// HashSet<TopicId>
private HashSet<Integer> mCachedBlockedTopicIds = new HashSet<>();
+ private final AdServicesLogger mLogger;
@VisibleForTesting
- CacheManager(EpochManager epochManager, TopicsDao topicsDao, Flags flags) {
+ CacheManager(
+ EpochManager epochManager, TopicsDao topicsDao, Flags flags, AdServicesLogger logger) {
mEpochManager = epochManager;
mTopicsDao = topicsDao;
mFlags = flags;
+ mLogger = logger;
}
/** Returns an instance of the CacheManager given a context. */
@@ -78,7 +84,8 @@ public class CacheManager implements Dumpable {
new CacheManager(
EpochManager.getInstance(context),
TopicsDao.getInstance(context),
- FlagsFactory.getFlags());
+ FlagsFactory.getFlags(),
+ AdServicesLoggerImpl.getInstance());
}
return sSingleton;
}
@@ -139,13 +146,20 @@ public class CacheManager implements Dumpable {
Set<Integer> topicsSet = new HashSet<>();
mReadWriteLock.readLock().lock();
+ int duplicateTopicCount = 0, blockedTopicCount = 0;
try {
for (int numEpoch = 0; numEpoch < numberOfLookBackEpochs; numEpoch++) {
if (mCachedTopics.containsKey(epochId - numEpoch)) {
Topic topic = mCachedTopics.get(epochId - numEpoch).get(Pair.create(app, sdk));
- if (topic != null
- && !topicsSet.contains(topic.getTopic())
- && !mCachedBlockedTopicIds.contains(topic.getTopic())) {
+ if (topic != null) {
+ if (topicsSet.contains(topic.getTopic())) {
+ duplicateTopicCount++;
+ continue;
+ }
+ if (mCachedBlockedTopicIds.contains(topic.getTopic())) {
+ blockedTopicCount++;
+ continue;
+ }
topics.add(topic);
topicsSet.add(topic.getTopic());
}
@@ -156,6 +170,19 @@ public class CacheManager implements Dumpable {
}
Collections.shuffle(topics, random);
+
+ // Log GetTopics stats.
+ ImmutableList.Builder<Integer> topicIds = ImmutableList.builder();
+ for (Topic topic : topics) {
+ topicIds.add(topic.getTopic());
+ }
+ mLogger.logGetTopicsReportedStats(
+ GetTopicsReportedStats.builder()
+ .setDuplicateTopicCount(duplicateTopicCount)
+ .setFilteredBlockedTopicCount(blockedTopicCount)
+ .setTopicIdsCount(topics.size())
+ .build());
+
return topics;
}
@@ -173,6 +200,42 @@ public class CacheManager implements Dumpable {
}
/**
+ * Get cached topics within certain epoch range. This is a helper method to get cached topics
+ * for an app-sdk caller, without considering other constraints, like UI blocking logic.
+ *
+ * @param epochLowerBound the earliest epoch to include cached topics from
+ * @param epochUpperBound the latest epoch to included cached topics to
+ * @param app the app
+ * @param sdk the sdk. In case the app calls the Topics API directly, the sdk == empty string.
+ * @return {@link List<Topic>} a list of Topics between {@code epochLowerBound} and {@code
+ * epochUpperBound}.
+ */
+ @NonNull
+ public List<Topic> getTopicsInEpochRange(
+ long epochLowerBound, long epochUpperBound, @NonNull String app, @NonNull String sdk) {
+ List<Topic> topics = new ArrayList<>();
+ // To deduplicate returned topics
+ Set<Integer> topicsSet = new HashSet<>();
+
+ mReadWriteLock.readLock().lock();
+ try {
+ for (long epochId = epochLowerBound; epochId <= epochUpperBound; epochId++) {
+ if (mCachedTopics.containsKey(epochId)) {
+ Topic topic = mCachedTopics.get(epochId).get(Pair.create(app, sdk));
+ if (topic != null && !topicsSet.contains(topic.getTopic())) {
+ topics.add(topic);
+ topicsSet.add(topic.getTopic());
+ }
+ }
+ }
+ } finally {
+ mReadWriteLock.readLock().unlock();
+ }
+
+ return topics;
+ }
+
+ /**
* Gets a list of all topics that could be returned to the user in the last
* numberOfLookBackEpochs epochs. Does not include the current epoch, so range is
* [currentEpochId - numberOfLookBackEpochs, currentEpochId - 1].
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/EpochJobService.java b/adservices/service-core/java/com/android/adservices/service/topics/EpochJobService.java
index 5b28e40cf..a0aeadf0e 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/EpochJobService.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/EpochJobService.java
@@ -32,6 +32,7 @@ import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.service.FlagsFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -91,7 +92,8 @@ public final class EpochJobService extends JobService {
return false;
}
- private static void schedule(
+ @VisibleForTesting
+ static void schedule(
Context context,
@NonNull JobScheduler jobScheduler,
long epochJobPeriodMs,
@@ -101,6 +103,7 @@ public final class EpochJobService extends JobService {
TOPICS_EPOCH_JOB_ID,
new ComponentName(context, EpochJobService.class))
.setRequiresCharging(true)
+ .setPersisted(true)
.setPeriodic(epochJobPeriodMs, epochJobFlexMs)
.build();
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/EpochManager.java b/adservices/service-core/java/com/android/adservices/service/topics/EpochManager.java
index 2e02c220f..e71244780 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/EpochManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/EpochManager.java
@@ -50,17 +50,6 @@ import java.util.Set;
/** A class to manage Epoch computation. */
public class EpochManager implements Dumpable {
- // TODO(b/223915674): make this configurable.
- // The Top Topics will have 6 topics.
- // The first 5 topics are the Top Topics derived by ML, and the 6th is a random topic from
- // taxonomy.
- // The index starts from 0.
- private static final int RANDOM_TOPIC_INDEX = 5;
-
- // TODO(b/223916172): make this configurable.
- // The number of top Topics not including the random one.
- private static final int NUM_TOP_TOPICS_NOT_INCLUDING_RANDOM_ONE = 5;
-
// The tables to do garbage collection for old epochs
// and its corresponding epoch_id column name.
// Pair<Table Name, Column Name>
@@ -86,6 +75,28 @@ public class EpochManager implements Dumpable {
TopicsTables.AppUsageHistoryContract.EPOCH_ID)
};
+ /**
+ * The string to annotate that the topic is a padded topic in {@code TopicContributors} table.
+ * After the computation of {@code TopicContributors} table, if there is a top topic without
+ * contributors, it must be a padded topic. Persist {@code Entry{Topic,
+ * PADDED_TOP_TOPICS_STRING}} into {@code TopicContributors} table.
+ *
+ * <p>The reason to persist {@code Entry{Topic, PADDED_TOP_TOPICS_STRING}} is because topics
+ * need to be assigned to newly installed app. Moreover, non-random top topics without
+ * contributors, due to app uninstallations, are filtered out as candidate topics to assign
+ * with. Generally, a padded topic should have no contributors, but it should NOT be filtered
+ * out as a non-random top topics without contributors. Based on these facts, {@code
+ * Entry{Topic, PADDED_TOP_TOPICS_STRING}} is persisted to annotate that do NOT remove this
+ * padded topic though it has no contributors.
+ *
+ * <p>Put a "!" at last to avoid a spoof app to name itself with {@code
+ * PADDED_TOP_TOPICS_STRING}. Refer to
+ * https://developer.android.com/studio/build/configure-app-module, application name can only
+ * contain [a-zA-Z0-9_].
+ */
+ @VisibleForTesting
+ public static final String PADDED_TOP_TOPICS_STRING = "no_contributors_due_to_padding!";
+
private static EpochManager sSingleton;
private final TopicsDao mTopicsDao;
@@ -138,6 +149,7 @@ public class EpochManager implements Dumpable {
}
// This cross db and java boundaries multiple times, so we need to have a db transaction.
+ LogUtil.d("Start of Epoch Computation");
db.beginTransaction();
long currentEpochId = getCurrentEpochId();
@@ -201,6 +213,17 @@ public class EpochManager implements Dumpable {
// Then save Top Topics into DB
mTopicsDao.persistTopTopics(currentEpochId, topTopics);
+ // Compute TopicToContributors mapping for top topics. In an epoch, an app is a
+ // contributor to a topic if the app has called Topics API in this epoch and is
+ // classified to the topic.
+ // Do this only when feature is enabled.
+ if (supportsTopicContributorFeature()) {
+ Map<Integer, Set<String>> topTopicsToContributorsMap =
+ computeTopTopicsToContributorsMap(appClassificationTopicsMap, topTopics);
+ // Then save Topic Contributors into DB
+ mTopicsDao.persistTopicContributors(currentEpochId, topTopicsToContributorsMap);
+ }
+
// Step 6: Assign topics to apps and SDK from the global top topics.
// Currently, hard-code the taxonomyVersion and the modelVersion.
// Return returnedAppSdkTopics = Map<Pair<App, Sdk>, Topic>
@@ -218,6 +241,7 @@ public class EpochManager implements Dumpable {
db.setTransactionSuccessful();
} finally {
db.endTransaction();
+ LogUtil.d("End of Epoch Computation");
}
}
@@ -412,14 +436,16 @@ public class EpochManager implements Dumpable {
+ mFlags.getTopicsNumberOfRandomTopics());
int random = mRandom.nextInt(100);
+ // First random topic would be after numberOfTopTopics.
+ int randomTopicIndex = mFlags.getTopicsNumberOfTopTopics();
// For 5%, get the random topic.
if (random < mFlags.getTopicsPercentageForRandomTopic()) {
// The random topic is the last one on the list.
- return topTopics.get(RANDOM_TOPIC_INDEX);
+ return topTopics.get(randomTopicIndex);
}
- // For 95%, pick randomly one out of 5 top topics.
- return topTopics.get(random % NUM_TOP_TOPICS_NOT_INCLUDING_RANDOM_ONE);
+ // For 95%, pick randomly one out of first n top topics.
+ return topTopics.get(random % randomTopicIndex);
}
// To garbage collect data for old epochs.
@@ -433,6 +459,56 @@ public class EpochManager implements Dumpable {
mTopicsDao.deleteDataOfOldEpochs(
tableColumnPair.first, tableColumnPair.second, epochToDeleteFrom);
}
+
+ // Handle TopicContributors Table if feature flag is ON
+ if (supportsTopicContributorFeature()) {
+ mTopicsDao.deleteDataOfOldEpochs(
+ TopicsTables.TopicContributorsContract.TABLE,
+ TopicsTables.TopicContributorsContract.EPOCH_ID,
+ epochToDeleteFrom);
+ }
+ }
+
+ // Compute the mapping of topic to its contributor apps. In an epoch, an app is a contributor to
+ // a topic if the app has called Topics API in this epoch and is classified to the topic. Only
+ // computed for top topics.
+ @VisibleForTesting
+ Map<Integer, Set<String>> computeTopTopicsToContributorsMap(
+ @NonNull Map<String, List<Topic>> appClassificationTopicsMap,
+ @NonNull List<Topic> topTopics) {
+ Map<Integer, Set<String>> topicToContributorMap = new HashMap<>();
+
+ for (Map.Entry<String, List<Topic>> appTopics : appClassificationTopicsMap.entrySet()) {
+ String app = appTopics.getKey();
+
+ for (Topic topic : appTopics.getValue()) {
+ // Only compute for top topics.
+ if (topTopics.contains(topic)) {
+ int topicId = topic.getTopic();
+ topicToContributorMap.putIfAbsent(topicId, new HashSet<>());
+ topicToContributorMap.get(topicId).add(app);
+ }
+ }
+ }
+
+ // At last, check whether there is any top topics without contributors. If so, annotate it
+ // with PADDED_TOP_TOPICS_STRING in the map. See PADDED_TOP_TOPICS_STRING for more details.
+ for (int i = 0; i < mFlags.getTopicsNumberOfTopTopics(); i++) {
+ Topic topTopic = topTopics.get(i);
+ topicToContributorMap.putIfAbsent(
+ topTopic.getTopic(), Set.of(PADDED_TOP_TOPICS_STRING));
+ }
+
+ return topicToContributorMap;
+ }
+
+ /**
+ * Check whether TopContributors Feature is enabled. It's enabled only when TopicContributors
+ * table is supported and the feature flag is on.
+ */
+ public boolean supportsTopicContributorFeature() {
+ return mFlags.getEnableTopicContributorsCheck()
+ && mTopicsDao.supportsTopicContributorsTable();
}
@Override
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/TopicsServiceImpl.java b/adservices/service-core/java/com/android/adservices/service/topics/TopicsServiceImpl.java
index 393d51fba..e66206fe9 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/TopicsServiceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/TopicsServiceImpl.java
@@ -15,7 +15,6 @@
*/
package com.android.adservices.service.topics;
-
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;
@@ -27,6 +26,7 @@ import static android.adservices.common.AdServicesStatusUtils.STATUS_USER_CONSEN
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__TARGETING;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS_PREVIEW_API;
import android.adservices.common.AdServicesStatusUtils;
import android.adservices.common.CallerMetadata;
@@ -138,8 +138,10 @@ public class TopicsServiceImpl extends ITopicsService.Stub {
callback.onResult(mTopicsWorker.getTopics(packageName, sdkName));
- mTopicsWorker.recordUsage(
- topicsParam.getAppPackageName(), topicsParam.getSdkName());
+ if (topicsParam.shouldRecordObservation()) {
+ mTopicsWorker.recordUsage(
+ topicsParam.getAppPackageName(), topicsParam.getSdkName());
+ }
} catch (RemoteException e) {
LogUtil.e(e, "Unable to send result to the callback");
resultCode = STATUS_INTERNAL_ERROR;
@@ -154,7 +156,10 @@ public class TopicsServiceImpl extends ITopicsService.Stub {
new ApiCallStats.Builder()
.setCode(AdServicesStatsLog.AD_SERVICES_API_CALLED)
.setApiClass(AD_SERVICES_API_CALLED__API_CLASS__TARGETING)
- .setApiName(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS)
+ .setApiName(
+ topicsParam.shouldRecordObservation()
+ ? AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS
+ : AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS_PREVIEW_API)
.setAppPackageName(packageName)
.setSdkPackageName(sdkName)
.setLatencyMillisecond(apiLatency)
@@ -264,7 +269,7 @@ public class TopicsServiceImpl extends ITopicsService.Stub {
return false;
}
- AdServicesApiConsent userConsent = mConsentManager.getConsent(mContext.getPackageManager());
+ AdServicesApiConsent userConsent = mConsentManager.getConsent();
if (!userConsent.isGiven()) {
invokeCallbackWithStatus(
callback, STATUS_USER_CONSENT_REVOKED, "User consent revoked.");
@@ -283,7 +288,9 @@ public class TopicsServiceImpl extends ITopicsService.Stub {
mContext,
Process.isSdkSandboxUid(callingUid),
topicsParam.getAppPackageName(),
- enrollmentData.getEnrollmentId());
+ enrollmentData.getEnrollmentId())
+ && !mFlags.isEnrollmentBlocklisted(enrollmentData.getEnrollmentId());
+
if (!permitted) {
invokeCallbackWithStatus(
callback, STATUS_CALLER_NOT_ALLOWED, "Caller is not authorized.");
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/TopicsWorker.java b/adservices/service-core/java/com/android/adservices/service/topics/TopicsWorker.java
index b28ffe284..7277281d9 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/TopicsWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/TopicsWorker.java
@@ -26,6 +26,7 @@ import android.net.Uri;
import com.android.adservices.LogUtil;
import com.android.adservices.data.topics.Topic;
+import com.android.adservices.data.topics.TopicsTables;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.internal.annotations.VisibleForTesting;
@@ -271,12 +272,16 @@ public class TopicsWorker {
/**
* Delete all data generated by Topics API, except for tables in the exclusion list.
*
- * @param tablesToExclude a {@link List} of tables that won't be deleted.
+ * @param tablesToExclude an {@link ArrayList} of tables that won't be deleted.
*/
- public void clearAllTopicsData(@NonNull List<String> tablesToExclude) {
+ public void clearAllTopicsData(@NonNull ArrayList<String> tablesToExclude) {
// Here we use Write lock to block Read during that computation time.
mReadWriteLock.writeLock().lock();
try {
+ // Clear data for TopicContributors Table only when feature flag is supported
+ if (!mEpochManager.supportsTopicContributorFeature()) {
+ tablesToExclude.add(TopicsTables.TopicContributorsContract.TABLE);
+ }
mCacheManager.clearAllTopicsData(tablesToExclude);
loadCache();
@@ -301,24 +306,26 @@ public class TopicsWorker {
public void reconcileApplicationUpdate(Context context) {
mReadWriteLock.writeLock().lock();
try {
- mAppUpdateManager.reconcileUninstalledApps(context);
+ mAppUpdateManager.reconcileUninstalledApps(context, mEpochManager.getCurrentEpochId());
mAppUpdateManager.reconcileInstalledApps(context, mEpochManager.getCurrentEpochId());
loadCache();
} finally {
mReadWriteLock.writeLock().unlock();
+ LogUtil.d("App Update Reconciliation is done!");
}
}
/**
- * Delete derived data for a specific app
+ * Handle application uninstallation for Topics API.
*
* @param packageUri The {@link Uri} got from Broadcast Intent
*/
- public void deletePackageData(@NonNull Uri packageUri) {
+ public void handleAppUninstallation(@NonNull Uri packageUri) {
mReadWriteLock.writeLock().lock();
try {
- mAppUpdateManager.deleteAppDataByUri(packageUri);
+ mAppUpdateManager.handleAppUninstallationInRealTime(
+ packageUri, mEpochManager.getCurrentEpochId());
loadCache();
LogUtil.v("Derived data is cleared for %s", packageUri.toString());
@@ -335,7 +342,7 @@ public class TopicsWorker {
public void handleAppInstallation(@NonNull Uri packageUri) {
mReadWriteLock.writeLock().lock();
try {
- mAppUpdateManager.assignTopicsToNewlyInstalledApps(
+ mAppUpdateManager.handleAppInstallationInRealTime(
packageUri, mEpochManager.getCurrentEpochId());
loadCache();
@@ -349,7 +356,13 @@ public class TopicsWorker {
// Handle topic assignment to SDK for newly installed applications. Cached topics need to be
// reloaded if any topic assignment happens.
- private void handleSdkTopicsAssignment(String app, String sdk) {
+ private void handleSdkTopicsAssignment(@NonNull String app, @NonNull String sdk) {
+ // Return if any topic has been assigned to this app-sdk.
+ List<Topic> existingTopics = getExistingTopicsForAppSdk(app, sdk);
+ if (!existingTopics.isEmpty()) {
+ return;
+ }
+
mReadWriteLock.writeLock().lock();
try {
if (mAppUpdateManager.assignTopicsToSdkForAppInstallation(
@@ -364,4 +377,27 @@ public class TopicsWorker {
mReadWriteLock.writeLock().unlock();
}
}
+
+ // Get all existing topics from cache for a pair of app and sdk.
+ // The epoch range is [currentEpochId - numberOfLookBackEpochs, currentEpochId].
+ @NonNull
+ private List<Topic> getExistingTopicsForAppSdk(@NonNull String app, @NonNull String sdk) {
+ List<Topic> existingTopics;
+
+ mReadWriteLock.readLock().lock();
+ // Get existing returned topics map for last 3 epochs and current epoch.
+ try {
+ long currentEpochId = mEpochManager.getCurrentEpochId();
+ existingTopics =
+ mCacheManager.getTopicsInEpochRange(
+ currentEpochId - mFlags.getTopicsNumberOfLookBackEpochs(),
+ currentEpochId,
+ app,
+ sdk);
+ } finally {
+ mReadWriteLock.readLock().unlock();
+ }
+
+ return existingTopics == null ? new ArrayList<>() : existingTopics;
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierManager.java b/adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierManager.java
index 55809cb97..e395db77f 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierManager.java
@@ -26,6 +26,7 @@ import com.android.adservices.data.topics.Topic;
import com.android.adservices.service.Flags;
import com.android.adservices.service.Flags.ClassifierType;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.topics.PackageManagerUtil;
import com.android.internal.annotations.VisibleForTesting;
@@ -71,11 +72,13 @@ public class ClassifierManager implements Classifier {
new Preprocessor(context),
new PackageManagerUtil(context),
new Random(),
- ModelManager.getInstance(context))),
+ ModelManager.getInstance(context),
+ AdServicesLoggerImpl.getInstance())),
Suppliers.memoize(
() ->
new PrecomputedClassifier(
- ModelManager.getInstance(context))));
+ ModelManager.getInstance(context),
+ AdServicesLoggerImpl.getInstance())));
}
}
return sSingleton;
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/classifier/CommonClassifierHelper.java b/adservices/service-core/java/com/android/adservices/service/topics/classifier/CommonClassifierHelper.java
index 13e05eea5..0b59b706a 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/classifier/CommonClassifierHelper.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/classifier/CommonClassifierHelper.java
@@ -21,6 +21,8 @@ import android.content.res.AssetManager;
import com.android.adservices.LogUtil;
import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.EpochComputationGetTopTopicsStats;
import com.android.internal.util.Preconditions;
import java.io.IOException;
@@ -74,7 +76,7 @@ class CommonClassifierHelper {
// This bytes[] has bytes in decimal format;
// Convert it to hexadecimal format
- for(int i = 0; i < bytes.length; i++) {
+ for (int i = 0; i < bytes.length; i++) {
assetSha256CheckSum.append(
Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
@@ -109,7 +111,8 @@ class CommonClassifierHelper {
@NonNull List<Integer> labelIds,
@NonNull Random random,
@NonNull int numberOfTopTopics,
- @NonNull int numberOfRandomTopics) {
+ @NonNull int numberOfRandomTopics,
+ @NonNull AdServicesLogger logger) {
Preconditions.checkArgument(
numberOfTopTopics > 0, "numberOfTopTopics should larger than 0");
Preconditions.checkArgument(
@@ -126,7 +129,14 @@ class CommonClassifierHelper {
// If there are no topic in the appTopics list, an empty topic list will be returned.
if (topicsToAppTopicCount.isEmpty()) {
LogUtil.w("Unable to retrieve any topics from device.");
-
+ // Log atom for getTopTopics call.
+ logger.logEpochComputationGetTopTopicsStats(
+ EpochComputationGetTopTopicsStats.builder()
+ .setTopTopicCount(0)
+ .setPaddedRandomTopicsCount(0)
+ .setAppsConsideredCount(-1)
+ .setSdksConsideredCount(-1)
+ .build());
return new ArrayList<>();
}
@@ -142,6 +152,16 @@ class CommonClassifierHelper {
List<Topic> topTopics =
allSortedTopics.subList(0, Math.min(numberOfTopTopics, allSortedTopics.size()));
+ // Log atom for getTopTopics call.
+ // TODO(b/256638889): Log apps and sdk considered count.
+ logger.logEpochComputationGetTopTopicsStats(
+ EpochComputationGetTopTopicsStats.builder()
+ .setTopTopicCount(numberOfTopTopics)
+ .setPaddedRandomTopicsCount(numberOfRandomPaddingTopics)
+ .setAppsConsideredCount(-1)
+ .setSdksConsideredCount(-1)
+ .build());
+
// If the size of topTopics smaller than numberOfTopTopics,
// the top topics list will be padded by numberOfRandomPaddingTopics random topics.
return getRandomTopics(
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/classifier/ModelManager.java b/adservices/service-core/java/com/android/adservices/service/topics/classifier/ModelManager.java
index f18249288..eadc892e0 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/classifier/ModelManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/classifier/ModelManager.java
@@ -91,7 +91,8 @@ public class ModelManager {
private static final String ASSET_ELEMENT_NAME = "asset_name";
// The attributions of assets property in classifier_assets_metadata.json
private static final Set<String> ASSETS_PROPERTY_ATTRIBUTIONS =
- new HashSet(Arrays.asList("taxonomy_type", "taxonomy_version", "updated_date"));
+ new HashSet(
+ Arrays.asList("taxonomy_type", "taxonomy_version", "build_id", "updated_date"));
// The attributions of assets metadata in classifier_assets_metadata.json
private static final Set<String> ASSETS_NORMAL_ATTRIBUTIONS =
new HashSet(Arrays.asList("asset_version", "path", "checksum", "updated_date"));
@@ -179,9 +180,11 @@ public class ModelManager {
return downloadedFiles;
}
- // Return true if Model Manager should uses downloaded model. Otherwise, use bundled model.
+ // Return true if Model Manager should use downloaded model. Otherwise, use bundled model.
private boolean useDownloadedFiles() {
- return mDownloadedFiles != null && mDownloadedFiles.size() > 0;
+ return mDownloadedFiles != null
+ && mDownloadedFiles.size() > 0
+ && !FlagsFactory.getFlags().getClassifierForceUseBundledFiles();
}
/**
@@ -205,14 +208,36 @@ public class ModelManager {
return buffer;
}
} else {
- // Use bundled files.
- AssetFileDescriptor fileDescriptor = mAssetManager.openFd(mModelFilePath);
- FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
- FileChannel fileChannel = inputStream.getChannel();
+ try {
+ // Use bundled files.
+ AssetFileDescriptor fileDescriptor = mAssetManager.openFd(mModelFilePath);
+ FileInputStream inputStream =
+ new FileInputStream(fileDescriptor.getFileDescriptor());
+ FileChannel fileChannel = inputStream.getChannel();
+
+ long startOffset = fileDescriptor.getStartOffset();
+ long declaredLength = fileDescriptor.getDeclaredLength();
+ return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
+ } catch (IOException | NullPointerException e) {
+ LogUtil.e(e, "Error loading the bundled classifier model");
+ return ByteBuffer.allocate(0);
+ }
+ }
+ }
- long startOffset = fileDescriptor.getStartOffset();
- long declaredLength = fileDescriptor.getDeclaredLength();
- return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
+ /** Returns true if the classifier model is available for classification. */
+ public boolean isModelAvailable() {
+ if (useDownloadedFiles()) {
+ // Downloaded model is always expected to be available.
+ return true;
+ } else {
+ // Check if the non-zero model file is present in the apk assets.
+ try {
+ return mAssetManager.openFd(mModelFilePath).getLength() > 0;
+ } catch (IOException e) {
+ LogUtil.e(e, "[ML] No classifier model available.");
+ return false;
+ }
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/classifier/OnDeviceClassifier.java b/adservices/service-core/java/com/android/adservices/service/topics/classifier/OnDeviceClassifier.java
index af32594aa..70c2a135a 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/classifier/OnDeviceClassifier.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/classifier/OnDeviceClassifier.java
@@ -16,6 +16,10 @@
package com.android.adservices.service.topics.classifier;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__ON_DEVICE_CLASSIFIER;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_SUCCESS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_NOT_INVOKED;
import static com.android.adservices.service.topics.classifier.Preprocessor.limitDescriptionSize;
import android.annotation.NonNull;
@@ -23,6 +27,8 @@ import android.annotation.NonNull;
import com.android.adservices.LogUtil;
import com.android.adservices.data.topics.Topic;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.EpochComputationClassifierStats;
import com.android.adservices.service.topics.AppInfo;
import com.android.adservices.service.topics.PackageManagerUtil;
@@ -54,6 +60,8 @@ public class OnDeviceClassifier implements Classifier {
private static final String MODEL_ASSET_FIELD = "tflite_model";
private static final String LABELS_ASSET_FIELD = "labels_topics";
private static final String ASSET_VERSION_FIELD = "asset_version";
+ private static final String VERSION_INFO_FIELD = "version_info";
+ private static final String BUILD_ID_FIELD = "build_id";
private static final String NO_VERSION_INFO = "NO_VERSION_INFO";
@@ -66,20 +74,24 @@ public class OnDeviceClassifier implements Classifier {
private ImmutableList<Integer> mLabels;
private long mModelVersion;
private long mLabelsVersion;
+ private int mBuildId;
private boolean mLoaded;
private ImmutableMap<String, AppInfo> mAppInfoMap;
+ private final AdServicesLogger mLogger;
OnDeviceClassifier(
@NonNull Preprocessor preprocessor,
@NonNull PackageManagerUtil packageManagerUtil1,
@NonNull Random random,
- @NonNull ModelManager modelManager) {
+ @NonNull ModelManager modelManager,
+ @NonNull AdServicesLogger logger) {
mPreprocessor = preprocessor;
mPackageManagerUtil = packageManagerUtil1;
mRandom = random;
mLoaded = false;
mAppInfoMap = ImmutableMap.of();
mModelManager = modelManager;
+ mLogger = logger;
}
@Override
@@ -89,6 +101,12 @@ public class OnDeviceClassifier implements Classifier {
return ImmutableMap.of();
}
+ if (!mModelManager.isModelAvailable()) {
+ // Return empty map since no model is available.
+ LogUtil.d("[ML] No ML model available for classification. Return empty Map.");
+ return ImmutableMap.of();
+ }
+
// Load the assets if not loaded already.
if (!isLoaded()) {
mLoaded = load();
@@ -104,6 +122,7 @@ public class OnDeviceClassifier implements Classifier {
for (String appPackageName : appPackageNames) {
String appDescription = getProcessedAppDescription(appPackageName);
List<Topic> appClassificationTopics = getAppClassificationTopics(appDescription);
+ logEpochComputationClassifierStats(appClassificationTopics);
LogUtil.v(
"[ML] Top classification for app description \""
+ appDescription
@@ -115,6 +134,28 @@ public class OnDeviceClassifier implements Classifier {
return packageNameToTopics.build();
}
+ private void logEpochComputationClassifierStats(List<Topic> topics) {
+ // Log atom for getTopTopics call.
+ ImmutableList.Builder<Integer> topicIds = ImmutableList.builder();
+ for (Topic topic : topics) {
+ topicIds.add(topic.getTopic());
+ }
+ mLogger.logEpochComputationClassifierStats(
+ EpochComputationClassifierStats.builder()
+ .setTopicIds(topicIds.build())
+ .setBuildId(mBuildId)
+ .setAssetVersion(Long.toString(mModelVersion))
+ .setClassifierType(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__ON_DEVICE_CLASSIFIER)
+ .setOnDeviceClassifierStatus(
+ topics.isEmpty()
+ ? AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_FAILURE
+ : AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_SUCCESS)
+ .setPrecomputedClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_NOT_INVOKED)
+ .build());
+ }
+
@Override
@NonNull
public List<Topic> getTopTopics(
@@ -125,7 +166,7 @@ public class OnDeviceClassifier implements Classifier {
}
return CommonClassifierHelper.getTopTopics(
- appTopics, mLabels, mRandom, numberOfTopTopics, numberOfRandomTopics);
+ appTopics, mLabels, mRandom, numberOfTopTopics, numberOfRandomTopics, mLogger);
}
// Uses the BertNLClassifier to fetch the most relevant topic id based on the input app
@@ -133,7 +174,15 @@ public class OnDeviceClassifier implements Classifier {
private List<Topic> getAppClassificationTopics(@NonNull String appDescription) {
// Returns list of labelIds with their corresponding score in Category for the app
// description.
- List<Category> classifications = mBertNLClassifier.classify(appDescription);
+ List<Category> classifications = ImmutableList.of();
+ try {
+ classifications = mBertNLClassifier.classify(appDescription);
+ } catch (Exception e) {
+ // (TODO:b/242926783): Update to more granular Exception after resolving JNI error
+ // propagation.
+ LogUtil.e("[ML] classify call failed for mBertNLClassifier.");
+ return ImmutableList.of();
+ }
// Get the highest score first. Sort in decreasing order.
classifications.sort(Comparator.comparing(Category::getScore).reversed());
@@ -253,7 +302,7 @@ public class OnDeviceClassifier implements Classifier {
// Load Bert model.
try {
mBertNLClassifier = loadModel();
- } catch (IOException e) {
+ } catch (Exception e) {
LogUtil.e(e, "Loading ML model failed.");
return false;
}
@@ -270,7 +319,15 @@ public class OnDeviceClassifier implements Classifier {
mLabelsVersion =
Long.parseLong(
classifierAssetsMetadata.get(LABELS_ASSET_FIELD).get(ASSET_VERSION_FIELD));
-
+ try {
+ mBuildId =
+ Integer.parseInt(
+ classifierAssetsMetadata.get(VERSION_INFO_FIELD).get(BUILD_ID_FIELD));
+ } catch (NumberFormatException e) {
+ // No build id is available.
+ LogUtil.d(e, "Build id is not available");
+ mBuildId = -1;
+ }
return true;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/classifier/PrecomputedClassifier.java b/adservices/service-core/java/com/android/adservices/service/topics/classifier/PrecomputedClassifier.java
index 002beef06..b7074d981 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/classifier/PrecomputedClassifier.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/classifier/PrecomputedClassifier.java
@@ -16,9 +16,17 @@
package com.android.adservices.service.topics.classifier;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__PRECOMPUTED_CLASSIFIER;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_NOT_INVOKED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_SUCCESS;
+
import android.annotation.NonNull;
+import com.android.adservices.LogUtil;
import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.EpochComputationClassifierStats;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -48,6 +56,8 @@ public class PrecomputedClassifier implements Classifier {
private static final String MODEL_ASSET_FIELD = "tflite_model";
private static final String LABELS_ASSET_FIELD = "labels_topics";
private static final String ASSET_VERSION_FIELD = "asset_version";
+ private static final String VERSION_INFO_FIELD = "version_info";
+ private static final String BUILD_ID_FIELD = "build_id";
private final ModelManager mModelManager;
@@ -58,10 +68,13 @@ public class PrecomputedClassifier implements Classifier {
private Map<String, List<Integer>> mAppTopics = new HashMap<>();
private long mModelVersion;
private long mLabelsVersion;
+ private int mBuildId;
+ private final AdServicesLogger mLogger;
- PrecomputedClassifier(@NonNull ModelManager modelManager) {
+ PrecomputedClassifier(@NonNull ModelManager modelManager, @NonNull AdServicesLogger logger) {
mModelManager = modelManager;
mLoaded = false;
+ mLogger = logger;
}
@NonNull
@@ -79,6 +92,22 @@ public class PrecomputedClassifier implements Classifier {
List<Topic> topics =
topicIds.stream().map(this::createTopic).collect(Collectors.toList());
+ // Log atom for getTopTopics call.
+ mLogger.logEpochComputationClassifierStats(
+ EpochComputationClassifierStats.builder()
+ .setTopicIds(ImmutableList.copyOf(topicIds))
+ .setBuildId(mBuildId)
+ .setAssetVersion(Long.toString(mModelVersion))
+ .setClassifierType(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__PRECOMPUTED_CLASSIFIER)
+ .setOnDeviceClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_NOT_INVOKED)
+ .setPrecomputedClassifierStatus(
+ topicIds.isEmpty()
+ ? AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_FAILURE
+ : AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_SUCCESS)
+ .build());
+
appsToClassifiedTopics.put(app, topics);
}
}
@@ -97,7 +126,7 @@ public class PrecomputedClassifier implements Classifier {
}
return CommonClassifierHelper.getTopTopics(
- appTopics, mLabels, new Random(), numberOfTopTopics, numberOfRandomTopics);
+ appTopics, mLabels, new Random(), numberOfTopTopics, numberOfRandomTopics, mLogger);
}
long getModelVersion() {
@@ -136,7 +165,15 @@ public class PrecomputedClassifier implements Classifier {
mLabelsVersion =
Long.parseLong(
classifierAssetsMetadata.get(LABELS_ASSET_FIELD).get(ASSET_VERSION_FIELD));
-
+ try {
+ mBuildId =
+ Integer.parseInt(
+ classifierAssetsMetadata.get(VERSION_INFO_FIELD).get(BUILD_ID_FIELD));
+ } catch (NumberFormatException e) {
+ // No build id is available.
+ LogUtil.d(e, "Build id is not available");
+ mBuildId = -1;
+ }
mLoaded = true;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/classifier/Preprocessor.java b/adservices/service-core/java/com/android/adservices/service/topics/classifier/Preprocessor.java
index ac7ad5eab..ef19adba0 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/classifier/Preprocessor.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/classifier/Preprocessor.java
@@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.content.Context;
+import android.util.Patterns;
import com.android.adservices.LogUtil;
@@ -36,19 +37,22 @@ import java.util.stream.Collectors;
/** Util class for pre-processing input for the classifier. */
public final class Preprocessor {
- // This regular expression is to identify URLs like https://google.com
- private static final Pattern URL_REGEX =
- Pattern.compile("(https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
+ // This regular expression is to identify URLs like https://google.com or www.youtube.com.
+ private static final Pattern URL_REGEX = Patterns.WEB_URL;
// At mention regular expression. Example @Rubidium1.
private static final Pattern MENTIONS_REGEX = Pattern.compile("@[A-Za-z0-9]+");
+ // This regular expression is to identify html tags like <span><\span>
+ private static final Pattern HTML_TAG_REGEX = Pattern.compile("\\<.*?\\>");
+
// Regular expression to primarily remove punctuation marks.
// Selects out lower case english alphabets and space.
private static final Pattern ALPHABET_REGEX = Pattern.compile("[^a-z\\s]|");
private static final Pattern NEW_LINE_REGEX = Pattern.compile("\\n");
private static final Pattern MULTIPLE_SPACES_REGEX = Pattern.compile("\\s+");
+ private static final Pattern TAB_REGEX = Pattern.compile("\\t");
private static final String SINGLE_SPACE = " ";
private static final String EMPTY_STRING = "";
@@ -114,8 +118,10 @@ public final class Preprocessor {
* <li>Converts text to lowercase.
* <li>Removes URLs.
* <li>Removes @mentions.
- * <li>Removes everything other than lower case english alphabets and spaces.
- * <li>Convert multiple spaces to a single space.
+ * <li>Removes html tags
+ * <li>Removes tabs and newlines
+ * <li>Converts multiple spaces to a single space.
+ * <li>Eliminates leading and tailing spaces.
* </ul>
*
* @param description is the string description of the app.
@@ -129,11 +135,14 @@ public final class Preprocessor {
description = URL_REGEX.matcher(description).replaceAll(EMPTY_STRING);
description = MENTIONS_REGEX.matcher(description).replaceAll(EMPTY_STRING);
- description = ALPHABET_REGEX.matcher(description).replaceAll(EMPTY_STRING);
+ description = HTML_TAG_REGEX.matcher(description).replaceAll(EMPTY_STRING);
description = NEW_LINE_REGEX.matcher(description).replaceAll(SINGLE_SPACE);
+ description = TAB_REGEX.matcher(description).replaceAll(SINGLE_SPACE);
description = MULTIPLE_SPACES_REGEX.matcher(description).replaceAll(SINGLE_SPACE);
+ description = description.trim();
+
return description;
}
diff --git a/adservices/service-core/proto/Android.bp b/adservices/service-core/proto/Android.bp
new file mode 100644
index 000000000..eb229d3e8
--- /dev/null
+++ b/adservices/service-core/proto/Android.bp
@@ -0,0 +1,94 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "adservices-proto",
+ srcs: [
+ "*.proto",
+ ],
+}
+
+// Generate the Proto POJO builders, etc.
+java_library {
+ name: "adservices-proto-lite",
+ sdk_version: "core_current",
+ proto: {
+ type: "lite",
+ include_dirs: [
+ "external/protobuf/src",
+ "external/protobuf/java",
+ ],
+ },
+ srcs: [
+ ":adservices-proto",
+ ":libprotobuf-internal-protos",
+ ],
+ static_libs: ["libprotobuf-java-lite"],
+ apex_available: ["com.android.adservices"],
+}
+
+// Generate gRPC client code
+PROTO_TOOLS = [
+ "aprotoc",
+ "protoc-gen-grpc-java-plugin",
+ "soong_zip",
+]
+LITE_PROTO_CMD = "mkdir -p $(genDir)/gen && " +
+ "$(location aprotoc) --java_opt=annotate_code=false -Ipackages/modules/AdServices/adservices/service-core -Iexternal/protobuf/src " +
+ "--plugin=protoc-gen-grpc-java=$(location protoc-gen-grpc-java-plugin) --grpc-java_out=lite:$(genDir)/gen $(in) && " +
+ "$(location soong_zip) -o $(out) -C $(genDir)/gen -D $(genDir)/gen"
+genrule {
+ name: "seller-frontend-service-stub-lite",
+ tools: PROTO_TOOLS,
+ cmd: LITE_PROTO_CMD,
+ srcs: [
+ ":adservices-proto",
+ ],
+ out: [
+ "protos.srcjar",
+ ],
+}
+
+// Package into java_library to reference in "static_libs" in "adservices-service-core"
+java_library {
+ name: "adservices-grpclib-lite",
+ min_sdk_version: "33",
+ apex_available: ["com.android.adservices"],
+ host_supported: true,
+ srcs: [
+ ":seller-frontend-service-stub-lite",
+ ":adservices-proto",
+ ":libprotobuf-internal-protos",
+ ],
+ libs: [
+ "javax_annotation-api_1.3.2",
+ ],
+ static_libs: [
+ "libprotobuf-java-lite",
+ "guava",
+ "grpc-java-core-android",
+ "grpc-java-okhttp-client-lite",
+ ],
+ proto: {
+ type: "lite",
+ include_dirs: [
+ "external/protobuf/src",
+ "external/protobuf/java",
+ ],
+ },
+} \ No newline at end of file
diff --git a/adservices/service-core/proto/seller_frontend_service.proto b/adservices/service-core/proto/seller_frontend_service.proto
new file mode 100644
index 000000000..0fdb608ad
--- /dev/null
+++ b/adservices/service-core/proto/seller_frontend_service.proto
@@ -0,0 +1,245 @@
+// Copyright 2022 Google LLC
+//
+// 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.
+
+syntax = "proto3";
+
+package com.android.adservices.service.proto;
+
+import "google/protobuf/struct.proto";
+import "google/protobuf/timestamp.proto";
+
+// Seller's FrontEnd service.
+service SellerFrontEnd {
+ // Selects a winning ad for the Publisher ad slot that would be
+ // rendered on the user's device.
+ // TODO(b/237817698): Add rpc timeout
+ rpc SelectWinningAd(SelectWinningAdRequest)
+ returns (SelectWinningAdResponse) {}
+}
+
+// SelectWinningAdRequest is sent from user's device to the
+// SellerFrontEnd Service.
+message SelectWinningAdRequest {
+ // Unencrypted request.
+ message SelectWinningAdRawRequest {
+ enum ClientType {
+ UNKNOWN = 0;
+
+ // An Android device with Google Mobile Services (GMS).
+ // Note: This covers apps on Android and browsers on Android.
+ ANDROID = 1;
+
+ // An Android device without Google Mobile Services (GMS).
+ ANDROID_NON_GMS = 2;
+
+ // Any browser.
+ BROWSER = 3;
+ }
+
+ // AuctionConfig required by the seller for ad auction.
+ // The auction config includes contextual signals and other data required
+ // for auction. This config is passed from client to SellerFrontEnd service
+ // in the umbrella request (SelectWinningAdRequest).
+ message AuctionConfig {
+ // Custom seller inputs for advertising on Android.
+ message CustomSellerInputsForAndroid {
+ // To be updated later if any custom fields are required to support
+ // Android.
+ }
+
+ // Custom seller inputs for advertising on web.
+ message CustomSellerInputsForBrowser {
+ // This timeout can be specified to restrict the runtime (in
+ // milliseconds) of the seller's scoreAd() script for auction.
+ float seller_timeout_ms = 1;
+
+ // Optional. Component auction configuration can contain additional
+ // auction configurations for each seller's "component auction".
+ google.protobuf.Struct component_auctions = 2;
+
+ // The Id is specified by the seller to support coordinated experiments
+ // with the seller's Key/Value services.
+ int32 experiment_group_id = 3;
+ }
+
+ // Optional. Custom seller inputs.
+ oneof CustomSellerInputs {
+ CustomSellerInputsForAndroid custom_seller_inputs_android = 1;
+
+ CustomSellerInputsForBrowser custom_seller_inputs_browser = 2;
+ }
+
+ /*..........................Contextual Signals.........................*/
+ // Contextual Signals refer to seller_signals and auction_signals
+ // derived contextually.
+
+ // Seller specific signals that include information about the context
+ // (e.g. Category blocks Publisher has chosen and so on). This can
+ // not be fetched real-time from Key-Value Server.
+ // Represents a JSON object.
+ google.protobuf.Struct seller_signals = 3;
+
+ // Information about auction (ad format, size).
+ // This information is required for both bidding and auction.
+ // Represents a JSON object.
+ google.protobuf.Struct auction_signals = 4;
+ }
+
+ // Optional. Required by Android to identify an ad selection request.
+ int64 ad_selection_request_id = 1;
+
+ // Encrypted BuyerInput per buyer.
+ // The key in the map corresponds to buyer Id that can identify a buyer
+ // participating in the auction. Buyer Id can be eTLD+1; i.e. domain address
+ // (ETLD+1) of the global load balancer of Buyer Frontend Service.
+ // The value corresponds to BuyerInput ciphertext that will be ingested by
+ // the buyer for bidding.
+ map<string, bytes> encrypted_input_per_buyer = 2;
+
+ // Includes configuration data required in Remarketing ad auction.
+ // Some of the data in AuctionConfig is passed to BuyerFrontEnd.
+ AuctionConfig auction_config = 3;
+
+ // Signals about device.
+ // Required for both bidding and auction.
+ oneof DeviceSignals {
+ // A JSON object constructed by Android containing contextual
+ // information that SDK or app knows about and that adtech's bidding
+ // and auction code can ingest.
+ google.protobuf.Struct android_signals = 4;
+
+ // A JSON object constructed by the browser, containing information that
+ // the browser knows about and that adtech's bidding and auction code
+ // can ingest.
+ google.protobuf.Struct browser_signals = 5;
+ }
+
+ // Type of end user's device / client, that would help in validating the
+ // integrity of an attested client.
+ // Note: Not all types of clients will be attested.
+ ClientType client_type = 6;
+
+ // Raw (unencrypted) version of the encrypted_input_per_buyer field above.
+ // See encrypted_input_per_buyer for details.
+ // TODO(b/239242947): Remove this field and reserve, after encryption is
+ // incorporated.
+ map<string, BuyerInput> raw_buyer_input = 7;
+
+ // Field representing client attestation data will be added later.
+ }
+
+ // Encrypted SelectWinningAdRawRequest.
+ bytes request_ciphertext = 1;
+
+ // Version of the public key used for request encryption. The service
+ // needs use private keys corresponding to same key_id to decrypt
+ // 'request_ciphertext'.
+ string key_id = 2;
+
+ // Unencrypted form of the SelectWinningAdRawRequest to be used until
+ // encryption is incorporated in the client/server communication.
+ // TODO(b/239242947): Remove this field and reserve, after request
+ // encryption / decryption is complete.
+ SelectWinningAdRawRequest raw_request = 3;
+}
+
+message SelectWinningAdResponse {
+ // Unencrypted response.
+ message SelectWinningAdRawResponse {
+ // The ad that will be rendered on the end user's device. This url is
+ // validated on the client side to ensure that it actually belongs to
+ // Custom Audience (a.k.a Interest Group).
+ // Note: This could be an int32 instead given an ad can be uniquely identified
+ // by the Buyer. In that case, the device would keep the mapping of the ad
+ // identifier to ad_render_url.
+ string ad_render_url = 1;
+
+ // Score of the winning ad.
+ float score = 2;
+
+ // Custom Audience (a.k.a Interest Group) name.
+ string custom_audience_name = 3;
+
+ // Bid for the winning ad candidate, generated by a buyer participating in
+ // the auction.
+ float bid_price = 4;
+
+ // The version of the binary running on the server and Guest OS of Secure
+ // Virtual Machine. The client may validate that with the version
+ // available in open source repo.
+ string server_binary_version = 5;
+ }
+
+ // Encrypted SelectWinningAdRawResponse.
+ bytes response_ciphertext = 1;
+
+ // TODO(b/239076127): Remove this field and reserve, after request
+ // encryption / decryption is incorporated.
+ SelectWinningAdRawResponse raw_response = 2;
+}
+
+// A BuyerInput includes data that a buyer (DSP) requires to generate bids.
+message BuyerInput {
+ // CustomAudience (a.k.a InterestGroup) includes name, the set of ads
+ // corresponding to this audience, Buyer Key Value lookup keys, user bidding
+ // signals and other optional fields.
+ message CustomAudience {
+ // Name or tag of Custom Audience (a.k.a Interest Group).
+ string name = 1;
+
+ // Keys to lookup from Buyer Key/Value service.
+ // NOTE: CA name would be another lookup key besides the keys in this field.
+ repeated string bidding_signals_keys = 2;
+
+ // Buyer Key Value shard url.
+ string bidding_signals_url = 3;
+
+ // User bidding signals for storing additional metadata that the Buyer can
+ // use during bidding.
+ // NOTE: This can be passed from device or fetched from Buyer Key Value service.
+ google.protobuf.Struct user_bidding_signals = 4;
+ }
+
+ // The Custom Audiences (a.k.a Interest Groups) corresponding to the buyer.
+ repeated CustomAudience custom_audiences = 1;
+
+ // Buyer may provide additional contextual information that could help in
+ // generating bids. Not fetched real-time.
+ // Represents a JSON object.
+ google.protobuf.Struct buyer_signals = 2;
+
+ // Custom buyer inputs for advertising on Android.
+ message CustomBuyerInputsForAndroid {
+ // To be updated later if any custom fields are required to support Android.
+ }
+
+ // Custom buyer inputs for advertising on browsers.
+ message CustomBuyerInputsForBrowser {
+ // This timeout can be specified to restrict the runtime (in milliseconds)
+ // of the buyer's generateBid() scripts for bidding. This can also be a
+ // default value if timeout is unspecified for the buyer.
+ float buyer_timeout_ms = 1;
+
+ // The Id is specified by the buyer to support coordinated experiments with
+ // the buyer's Key/Value services.
+ int32 experiment_group_id = 2;
+ }
+
+ // Optional. Custom buyer input for app or web advertising.
+ oneof CustomBuyerInputs {
+ CustomBuyerInputsForAndroid custom_buyer_inputs_android = 3;
+
+ CustomBuyerInputsForBrowser custom_buyer_inputs_browser = 4;
+ }
+}
diff --git a/adservices/service-core/schemas/com.android.adservices.data.adselection.AdSelectionDatabase/1.json b/adservices/service-core/schemas/com.android.adservices.data.adselection.AdSelectionDatabase/1.json
new file mode 100644
index 000000000..8df6acf9a
--- /dev/null
+++ b/adservices/service-core/schemas/com.android.adservices.data.adselection.AdSelectionDatabase/1.json
@@ -0,0 +1,180 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "4410f85d4543ed441bdc968315b345f6",
+ "entities": [
+ {
+ "tableName": "ad_selection",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`ad_selection_id` INTEGER NOT NULL, `contextual_signals` TEXT, `bidding_logic_uri` TEXT, `winning_ad_render_uri` TEXT, `winning_ad_bid` REAL NOT NULL, `creation_timestamp` INTEGER, `caller_package_name` TEXT, `custom_audience_signals_owner` TEXT, `custom_audience_signals_buyer` TEXT, `custom_audience_signals_name` TEXT, `custom_audience_signals_activation_time` INTEGER, `custom_audience_signals_expiration_time` INTEGER, `custom_audience_signals_user_bidding_signals` TEXT, PRIMARY KEY(`ad_selection_id`))",
+ "fields": [
+ {
+ "fieldPath": "mAdSelectionId",
+ "columnName": "ad_selection_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mContextualSignals",
+ "columnName": "contextual_signals",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mBiddingLogicUri",
+ "columnName": "bidding_logic_uri",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mWinningAdRenderUri",
+ "columnName": "winning_ad_render_uri",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mWinningAdBid",
+ "columnName": "winning_ad_bid",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mCreationTimestamp",
+ "columnName": "creation_timestamp",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mCallerPackageName",
+ "columnName": "caller_package_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mCustomAudienceSignals.mOwner",
+ "columnName": "custom_audience_signals_owner",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mCustomAudienceSignals.mBuyer",
+ "columnName": "custom_audience_signals_buyer",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mCustomAudienceSignals.mName",
+ "columnName": "custom_audience_signals_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mCustomAudienceSignals.mActivationTime",
+ "columnName": "custom_audience_signals_activation_time",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mCustomAudienceSignals.mExpirationTime",
+ "columnName": "custom_audience_signals_expiration_time",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mCustomAudienceSignals.mUserBiddingSignals",
+ "columnName": "custom_audience_signals_user_bidding_signals",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "ad_selection_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_ad_selection_bidding_logic_uri",
+ "unique": false,
+ "columnNames": [
+ "bidding_logic_uri"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_ad_selection_bidding_logic_uri` ON `${TABLE_NAME}` (`bidding_logic_uri`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "buyer_decision_logic",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bidding_logic_uri` TEXT NOT NULL, `buyer_decision_logic_js` TEXT NOT NULL, PRIMARY KEY(`bidding_logic_uri`))",
+ "fields": [
+ {
+ "fieldPath": "mBiddingLogicUri",
+ "columnName": "bidding_logic_uri",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mBuyerDecisionLogicJs",
+ "columnName": "buyer_decision_logic_js",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "bidding_logic_uri"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ad_selection_overrides",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`ad_selection_config_id` TEXT NOT NULL, `app_package_name` TEXT NOT NULL, `decision_logic` TEXT NOT NULL, `trusted_scoring_signals` TEXT NOT NULL, PRIMARY KEY(`ad_selection_config_id`))",
+ "fields": [
+ {
+ "fieldPath": "adSelectionConfigId",
+ "columnName": "ad_selection_config_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "appPackageName",
+ "columnName": "app_package_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "decisionLogicJS",
+ "columnName": "decision_logic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "trustedScoringSignals",
+ "columnName": "trusted_scoring_signals",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "ad_selection_config_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4410f85d4543ed441bdc968315b345f6')"
+ ]
+ }
+} \ No newline at end of file
diff --git a/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java b/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java
index 1f87123c9..e99db0b52 100644
--- a/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java
+++ b/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java
@@ -24,6 +24,7 @@ import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -57,20 +58,33 @@ public class AdServicesManagerService {
private static final String PACKAGE_DATA_CLEARED = "package_data_cleared";
private final Context mContext;
- private final Handler mHandler;
+
+ private BroadcastReceiver mSystemServicePackageChangedReceiver;
+
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+
+ // This will be triggered when there is a flag change.
+ private final DeviceConfig.OnPropertiesChangedListener mOnFlagsChangedListener =
+ properties -> {
+ if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_ADSERVICES)) {
+ return;
+ }
+ registerPackagedChangedBroadcastReceivers();
+ };
@VisibleForTesting
AdServicesManagerService(Context context) {
mContext = context;
- // Start the handler thread.
- HandlerThread handlerThread = new HandlerThread("AdServicesManagerServiceHandler");
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ mContext.getMainExecutor(),
+ mOnFlagsChangedListener);
+
registerPackagedChangedBroadcastReceivers();
}
-
/** @hide */
public static class Lifecycle extends SystemService {
private AdServicesManagerService mService;
@@ -93,28 +107,62 @@ public class AdServicesManagerService {
* the device at boot up. After receiving the broadcast, send an explicit broadcast to the
* AdServices module as that user.
*/
- private void registerPackagedChangedBroadcastReceivers() {
- final IntentFilter packageChangedIntentFilter = new IntentFilter();
-
- packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
- packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
- packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageChangedIntentFilter.addDataScheme("package");
-
- BroadcastReceiver systemServicePackageChangedReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- UserHandle user = getSendingUser();
- mHandler.post(() -> onPackageChange(intent, user));
- }
- };
- mContext.registerReceiverForAllUsers(
- systemServicePackageChangedReceiver,
- packageChangedIntentFilter,
- /* broadcastPermission */ null,
- mHandler);
- Log.d(TAG, "Package changed broadcast receivers registered.");
+ @VisibleForTesting
+ void registerPackagedChangedBroadcastReceivers() {
+ // There could be race condition between registerPackagedChangedBroadcastReceivers call
+ // in the AdServicesManagerService constructor and the mOnFlagsChangedListener.
+ synchronized (AdServicesManagerService.class) {
+ if (FlagsFactory.getFlags().getAdServicesSystemServiceEnabled()) {
+ if (mSystemServicePackageChangedReceiver != null) {
+ // We already register the receiver.
+ Log.d(TAG, "SystemServicePackageChangedReceiver is already registered.");
+ return;
+ }
+
+ // mSystemServicePackageChangedReceiver == null
+ // We haven't registered the receiver.
+ // Start the handler thread.
+ mHandlerThread = new HandlerThread("AdServicesManagerServiceHandler");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ final IntentFilter packageChangedIntentFilter = new IntentFilter();
+
+ packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+ packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageChangedIntentFilter.addDataScheme("package");
+
+ mSystemServicePackageChangedReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ UserHandle user = getSendingUser();
+ mHandler.post(() -> onPackageChange(intent, user));
+ }
+ };
+ mContext.registerReceiverForAllUsers(
+ mSystemServicePackageChangedReceiver,
+ packageChangedIntentFilter,
+ /* broadcastPermission */ null,
+ mHandler);
+ Log.d(TAG, "Package changed broadcast receivers registered.");
+ } else {
+ // FlagsFactory.getFlags().getAdServicesSystemServiceEnabled() == false
+ Log.d(TAG, "AdServicesSystemServiceEnabled is FALSE.");
+
+ // If there is a SystemServicePackageChangeReceiver, unregister it.
+ if (mSystemServicePackageChangedReceiver != null) {
+ Log.d(TAG, "Unregistering the existing SystemServicePackageChangeReceiver");
+ mContext.unregisterReceiver(mSystemServicePackageChangedReceiver);
+ mSystemServicePackageChangedReceiver = null;
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ }
+
+ return;
+ }
+ }
}
/** Sends an explicit broadcast to the AdServices module when a package change occurs. */
diff --git a/adservices/service/java/com/android/server/adservices/Flags.java b/adservices/service/java/com/android/server/adservices/Flags.java
new file mode 100644
index 000000000..925f88db2
--- /dev/null
+++ b/adservices/service/java/com/android/server/adservices/Flags.java
@@ -0,0 +1,35 @@
+/*
+ * 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.server.adservices;
+
+/**
+ * AdServices System Service Feature Flags interface. This Flags interface hold the default values
+ * of AdServices System Service Flags.
+ *
+ * @hide
+ */
+interface Flags {
+ /**
+ * Whether to enable the AdServices System Service. By default, the AdServices System Service is
+ * disabled.
+ */
+ boolean ADSERVICES_SYSTEM_SERVICE_ENABLED = false;
+
+ default boolean getAdServicesSystemServiceEnabled() {
+ return ADSERVICES_SYSTEM_SERVICE_ENABLED;
+ }
+}
diff --git a/adservices/service/java/com/android/server/adservices/FlagsFactory.java b/adservices/service/java/com/android/server/adservices/FlagsFactory.java
new file mode 100644
index 000000000..e6c4030ae
--- /dev/null
+++ b/adservices/service/java/com/android/server/adservices/FlagsFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.server.adservices;
+
+/**
+ * Factory class to create AdServices System Service Flags
+ *
+ * @hide
+ */
+public class FlagsFactory {
+ /** AdServices System Service Flags backed by PH. */
+ public static Flags getFlags() {
+ // Use the Flags backed by PH.
+ return PhFlags.getInstance();
+ }
+}
diff --git a/adservices/service/java/com/android/server/adservices/PhFlags.java b/adservices/service/java/com/android/server/adservices/PhFlags.java
new file mode 100644
index 000000000..89ed912ed
--- /dev/null
+++ b/adservices/service/java/com/android/server/adservices/PhFlags.java
@@ -0,0 +1,50 @@
+/*
+ * 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.server.adservices;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * Flags Implementation that delegates to DeviceConfig.
+ *
+ * @hide
+ */
+public final class PhFlags implements Flags {
+
+ private static final PhFlags sSingleton = new PhFlags();
+
+ /** Returns the singleton instance of the PhFlags. */
+ @NonNull
+ public static PhFlags getInstance() {
+ return sSingleton;
+ }
+
+ /*
+ * Keys for ALL the flags stored in DeviceConfig.
+ */
+ // Adservices System Service enable status keys.
+ static final String KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED = "adservice_system_service_enabled";
+
+ @Override
+ public boolean getAdServicesSystemServiceEnabled() {
+ // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ /* flagName */ KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED,
+ /* defaultValue */ ADSERVICES_SYSTEM_SERVICE_ENABLED);
+ }
+}
diff --git a/adservices/tests/cts/Android.bp b/adservices/tests/cts/Android.bp
index b5d60385d..cdc1e8aa8 100644
--- a/adservices/tests/cts/Android.bp
+++ b/adservices/tests/cts/Android.bp
@@ -45,6 +45,7 @@ android_test {
"testng",
],
sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
compile_multilib: "both",
}
android_test {
@@ -78,6 +79,7 @@ android_test {
"testng",
],
sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
manifest: "AndroidManifestDebuggable.xml",
test_config: "AndroidTestDebuggable.xml",
}
diff --git a/adservices/tests/cts/AndroidManifest.xml b/adservices/tests/cts/AndroidManifest.xml
index 9eba1b775..80b8a9d48 100644
--- a/adservices/tests/cts/AndroidManifest.xml
+++ b/adservices/tests/cts/AndroidManifest.xml
@@ -20,6 +20,7 @@
android:targetSandboxVersion="2">
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
<application>
diff --git a/adservices/tests/cts/AndroidTest.xml b/adservices/tests/cts/AndroidTest.xml
index b0f9dd96e..2068dc663 100644
--- a/adservices/tests/cts/AndroidTest.xml
+++ b/adservices/tests/cts/AndroidTest.xml
@@ -16,13 +16,37 @@
<configuration description="Config for CTS AdServices test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAdServicesDeviceTestCases.apk" />
</target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable kill switches to ignore the effect of actual PH values. -->
+ <option name="run-command" value="setprop debug.adservices.global_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_select_ads_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_custom_audience_service_kill_switch false" />
+
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+
+ <!-- Force enable enrollment checks for FLEDGE when testing enrollment -->
+ <option name="run-command" value="setprop debug.adservices.disable_fledge_enrollment_check false" />
+
+ <!-- Increase the allowed API queries per second -->
+ <option name="run-command" value="setprop debug.adservices.sdk_request_permits_per_second 1000" />
+ <option name="teardown-command" value="setprop debug.adservices.sdk_request_permits_per_second 1" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.adservices.cts" />
<option name="hidden-api-checks" value="false" />
diff --git a/adservices/tests/cts/AndroidTestDebuggable.xml b/adservices/tests/cts/AndroidTestDebuggable.xml
index 8d69d8f76..026a399d0 100644
--- a/adservices/tests/cts/AndroidTestDebuggable.xml
+++ b/adservices/tests/cts/AndroidTestDebuggable.xml
@@ -19,7 +19,7 @@
<option name="mainline-module-package-name" value="com.google.android.adservices" />
</object>
<option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="test-suite-tag" value="cts" />
@@ -27,6 +27,27 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAdServicesDebuggableDeviceTestCases.apk" />
</target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable kill switches to ignore the effect of actual PH values. -->
+ <option name="run-command" value="setprop debug.adservices.global_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_select_ads_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_custom_audience_service_kill_switch false" />
+
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+
+ <!-- Increase the allowed API queries per second -->
+ <option name="run-command" value="setprop debug.adservices.sdk_request_permits_per_second 1000" />
+ <option name="teardown-command" value="setprop debug.adservices.sdk_request_permits_per_second 1" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="android.adservices.debuggablects" />
<option name="hidden-api-checks" value="false" />
diff --git a/adservices/tests/cts/adid/Android.bp b/adservices/tests/cts/adid/Android.bp
index 78384f0e6..15a98e200 100644
--- a/adservices/tests/cts/adid/Android.bp
+++ b/adservices/tests/cts/adid/Android.bp
@@ -40,6 +40,7 @@ android_test {
],
javacflags: ["-parameters"],
sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
test_mainline_modules: ["com.google.android.adservices.apex"],
manifest: "AndroidManifest.xml",
}
diff --git a/adservices/tests/cts/adid/AndroidTest.xml b/adservices/tests/cts/adid/AndroidTest.xml
index 6d3545737..76703a6c2 100644
--- a/adservices/tests/cts/adid/AndroidTest.xml
+++ b/adservices/tests/cts/adid/AndroidTest.xml
@@ -27,6 +27,16 @@
<option name="test-file-name" value="CtsAdIdEndToEndTest.apk"/>
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global_kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.adservices.tests.adid"/>
</test>
diff --git a/adservices/tests/cts/adid/src/com/android/adservices/tests/adid/AdIdManagerTest.java b/adservices/tests/cts/adid/src/com/android/adservices/tests/adid/AdIdManagerTest.java
index f86e380a5..8b74ac3c8 100644
--- a/adservices/tests/cts/adid/src/com/android/adservices/tests/adid/AdIdManagerTest.java
+++ b/adservices/tests/cts/adid/src/com/android/adservices/tests/adid/AdIdManagerTest.java
@@ -23,7 +23,11 @@ import android.os.OutcomeReceiver;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,6 +40,25 @@ public class AdIdManagerTest {
private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
private static final Context sContext = ApplicationProvider.getApplicationContext();
+ @Before
+ public void setup() {
+ overrideAdIdKillSwitch(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ overrideAdIdKillSwitch(false);
+ }
+
+ // Override adid related kill switch to ignore the effect of actual PH values.
+ // If shouldOverride = true, override adid related kill switch to OFF to allow adservices
+ // If shouldOverride = false, override adid related kill switch to meaningless value so that
+ // PhFlags will use the default value.
+ private void overrideAdIdKillSwitch(boolean shouldOverride) {
+ String overrideString = shouldOverride ? "false" : "null";
+ ShellUtils.runShellCommand("setprop debug.adservices.adid_kill_switch " + overrideString);
+ }
+
@Test
public void testAdIdManager() throws Exception {
AdIdManager adIdManager = sContext.getSystemService(AdIdManager.class);
@@ -55,6 +78,6 @@ public class AdIdManagerTest {
adIdManager.getAdId(CALLBACK_EXECUTOR, callback);
AdId resultAdId = future.get();
Assert.assertNotNull(resultAdId.getAdId());
- Assert.assertEquals(false, resultAdId.isLimitAdTrackingEnabled());
+ Assert.assertNotNull(resultAdId.isLimitAdTrackingEnabled());
}
}
diff --git a/adservices/tests/cts/appsetid/Android.bp b/adservices/tests/cts/appsetid/Android.bp
index b241fcf3e..4b0fffd22 100644
--- a/adservices/tests/cts/appsetid/Android.bp
+++ b/adservices/tests/cts/appsetid/Android.bp
@@ -40,6 +40,7 @@ android_test {
],
javacflags: ["-parameters"],
sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
test_mainline_modules: ["com.google.android.adservices.apex"],
manifest: "AndroidManifest.xml",
}
diff --git a/adservices/tests/cts/appsetid/AndroidTest.xml b/adservices/tests/cts/appsetid/AndroidTest.xml
index 2a21cd2e7..3956cc61e 100644
--- a/adservices/tests/cts/appsetid/AndroidTest.xml
+++ b/adservices/tests/cts/appsetid/AndroidTest.xml
@@ -27,6 +27,16 @@
<option name="test-file-name" value="CtsAppSetIdEndToEndTest.apk"/>
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global_kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.adservices.tests.appsetid"/>
</test>
diff --git a/adservices/tests/cts/appsetid/src/com/android/adservices/tests/appsetid/AppSetIdManagerTest.java b/adservices/tests/cts/appsetid/src/com/android/adservices/tests/appsetid/AppSetIdManagerTest.java
index 741b054ec..3b911c92f 100644
--- a/adservices/tests/cts/appsetid/src/com/android/adservices/tests/appsetid/AppSetIdManagerTest.java
+++ b/adservices/tests/cts/appsetid/src/com/android/adservices/tests/appsetid/AppSetIdManagerTest.java
@@ -23,7 +23,11 @@ import android.os.OutcomeReceiver;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,6 +40,26 @@ public class AppSetIdManagerTest {
private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
private static final Context sContext = ApplicationProvider.getApplicationContext();
+ @Before
+ public void setup() {
+ overrideAppSetIdKillSwitch(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ overrideAppSetIdKillSwitch(false);
+ }
+
+ // Override appsetid related kill switch to ignore the effect of actual PH values.
+ // If shouldOverride = true, override appsetid related kill switch to OFF to allow adservices
+ // If shouldOverride = false, override appsetid related kill switch to meaningless value so that
+ // PhFlags will use the default value.
+ private void overrideAppSetIdKillSwitch(boolean shouldOverride) {
+ String overrideString = shouldOverride ? "false" : "null";
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.appsetid_kill_switch " + overrideString);
+ }
+
@Test
public void testAppSetIdManager() throws Exception {
AppSetIdManager appSetIdManager = sContext.getSystemService(AppSetIdManager.class);
@@ -55,6 +79,6 @@ public class AppSetIdManagerTest {
appSetIdManager.getAppSetId(CALLBACK_EXECUTOR, callback);
AppSetId resultAppSetId = future.get();
Assert.assertNotNull(resultAppSetId.getId());
- Assert.assertEquals(0, resultAppSetId.getScope());
+ Assert.assertNotNull(resultAppSetId.getScope());
}
}
diff --git a/adservices/tests/cts/endtoends/measurement/Android.bp b/adservices/tests/cts/endtoends/measurement/Android.bp
index c4e54a783..4ba582db9 100644
--- a/adservices/tests/cts/endtoends/measurement/Android.bp
+++ b/adservices/tests/cts/endtoends/measurement/Android.bp
@@ -42,5 +42,6 @@ android_test {
// To fix Pre-submit warning, suggested by farivar@
javacflags: ["-parameters"],
sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
test_mainline_modules: ["com.google.android.adservices.apex"],
}
diff --git a/adservices/tests/cts/endtoends/measurement/AndroidTest.xml b/adservices/tests/cts/endtoends/measurement/AndroidTest.xml
index e7a923952..30b2619d9 100644
--- a/adservices/tests/cts/endtoends/measurement/AndroidTest.xml
+++ b/adservices/tests/cts/endtoends/measurement/AndroidTest.xml
@@ -28,6 +28,16 @@
<option name="test-file-name" value="CtsAdServicesEndToEndTestMeasurement.apk"/>
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global_kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="hidden-api-checks" value="false" /> <!-- Allow hidden API uses -->
<option name="package" value="com.android.adservices.tests.cts.endtoendtest.measurement"/>
diff --git a/adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerCtsTest.java b/adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerCtsTest.java
index a4d69ccc6..1c301bb39 100644
--- a/adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerCtsTest.java
+++ b/adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerCtsTest.java
@@ -88,6 +88,8 @@ public class MeasurementManagerCtsTest {
// We need to turn the Consent Manager into debug mode
overrideConsentManagerDebugMode();
+ overrideMeasurementKillSwitches(true);
+
mMeasurementClient =
new MeasurementClient.Builder()
.setContext(sContext)
@@ -100,6 +102,7 @@ public class MeasurementManagerCtsTest {
resetAllowSandboxPackageNameAccessMeasurementApis();
resetOverrideConsentManagerDebugMode();
resetOverrideDisableMeasurementEnrollmentCheck();
+ overrideMeasurementKillSwitches(false);
TimeUnit.SECONDS.sleep(1);
}
@@ -304,4 +307,32 @@ public class MeasurementManagerCtsTest {
ShellUtils.runShellCommand(
"setprop debug.adservices.disable_measurement_enrollment_check null");
}
+
+ // Override measurement related kill switch to ignore the effect of actual PH values.
+ // If isOverride = true, override measurement related kill switch to OFF to allow adservices
+ // If isOverride = false, override measurement related kill switch to meaningless value so that
+ // PhFlags will use the default value.
+ private void overrideMeasurementKillSwitches(boolean isOverride) {
+ String overrideString = isOverride ? "false" : "null";
+ ShellUtils.runShellCommand("setprop debug.adservices.global_kill_switch " + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_kill_switch " + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_source_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_trigger_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_web_source_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_web_trigger_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_delete_registrations_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_status_kill_switch " + overrideString);
+ }
}
diff --git a/adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerSandboxCtsTest.java b/adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerSandboxCtsTest.java
index 2e35eb500..3ac685cf2 100644
--- a/adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerSandboxCtsTest.java
+++ b/adservices/tests/cts/endtoends/measurement/src/com/android/adservices/tests/cts/measurement/MeasurementManagerSandboxCtsTest.java
@@ -45,6 +45,9 @@ import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -62,6 +65,7 @@ public class MeasurementManagerSandboxCtsTest {
protected static final Context sSandboxedSdkContext =
new SandboxedSdkContext(
/* baseContext = */ sContext,
+ /* classLoader = */ sContext.getClassLoader(),
/* clientPackageName = */ sContext.getPackageName(),
/* info = */ new ApplicationInfo(),
/* sdkName = */ "sdkName",
@@ -88,6 +92,13 @@ public class MeasurementManagerSandboxCtsTest {
mMeasurementManager =
Mockito.spy(sSandboxedSdkContext.getSystemService(MeasurementManager.class));
doReturn(mMockMeasurementService).when(mMeasurementManager).getService();
+
+ overrideMeasurementKillSwitches(true);
+ }
+
+ @After
+ public void teardown() {
+ overrideMeasurementKillSwitches(false);
}
@Test
@@ -218,4 +229,32 @@ public class MeasurementManagerSandboxCtsTest {
Assert.assertNotNull(captor.getValue());
Assert.assertEquals(sContext.getPackageName(), captor.getValue().getAppPackageName());
}
+
+ // Override measurement related kill switch to ignore the effect of actual PH values.
+ // If isOverride = true, override measurement related kill switch to OFF to allow adservices
+ // If isOverride = false, override measurement related kill switch to meaningless value so that
+ // PhFlags will use the default value.
+ private void overrideMeasurementKillSwitches(boolean isOverride) {
+ String overrideString = isOverride ? "false" : "null";
+ ShellUtils.runShellCommand("setprop debug.adservices.global_kill_switch " + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_kill_switch " + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_source_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_trigger_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_web_source_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_web_trigger_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_delete_registrations_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_status_kill_switch " + overrideString);
+ }
}
diff --git a/adservices/tests/cts/endtoends/permissions/appoptout/AndroidTest.xml b/adservices/tests/cts/endtoends/permissions/appoptout/AndroidTest.xml
index d4bb18b09..28a3bf571 100644
--- a/adservices/tests/cts/endtoends/permissions/appoptout/AndroidTest.xml
+++ b/adservices/tests/cts/endtoends/permissions/appoptout/AndroidTest.xml
@@ -15,24 +15,51 @@
~ limitations under the License.
-->
<configuration description="Config for Cts Ad Services Permissions E2E tests">
-<option name="test-suite-tag" value="cts" />
-<option name="test-tag" value="CtsAdServicesPermissionsAppOptOutEndToEndTests" />
-<option name="config-descriptor:metadata" key="component" value="framework"/>
-<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-
-<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="CtsAdServicesPermissionsAppOptOutEndToEndTests.apk"/>
-</target_preparer>
-
-<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.adservices.tests.permissions.appoptout"/>
-</test>
-
-<object type="module_controller"
- class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.adservices"/>
-</object>
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="CtsAdServicesPermissionsAppOptOutEndToEndTests" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CtsAdServicesPermissionsAppOptOutEndToEndTests.apk"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable kill switches to ignore the effect of actual PH values. -->
+ <option name="run-command" value="setprop debug.adservices.global_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_select_ads_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_custom_audience_service_kill_switch false" />
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+
+ <!-- Force enable enrollment checks when testing enrollment -->
+ <option name="run-command" value="setprop debug.adservices.disable_fledge_enrollment_check false" />
+ <option name="run-command" value="setprop debug.adservices.disable_topics_enrollment_check false" />
+
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+
+ <!-- Increase the allowed API queries per second -->
+ <option name="run-command" value="setprop debug.adservices.sdk_request_permits_per_second 1000" />
+ <option name="teardown-command" value="setprop debug.adservices.sdk_request_permits_per_second 1" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.adservices.tests.permissions.appoptout"/>
+ </test>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.adservices"/>
+ </object>
</configuration>
diff --git a/adservices/tests/cts/endtoends/permissions/appoptout/src/com/android/adservices/tests/permissions/PermissionsAppOptOutTest.java b/adservices/tests/cts/endtoends/permissions/appoptout/src/com/android/adservices/tests/permissions/PermissionsAppOptOutTest.java
index e6ebbdee7..6f22eafb2 100644
--- a/adservices/tests/cts/endtoends/permissions/appoptout/src/com/android/adservices/tests/permissions/PermissionsAppOptOutTest.java
+++ b/adservices/tests/cts/endtoends/permissions/appoptout/src/com/android/adservices/tests/permissions/PermissionsAppOptOutTest.java
@@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
-import android.Manifest;
import android.adservices.adselection.AdSelectionConfig;
import android.adservices.adselection.AdSelectionConfigFixture;
import android.adservices.adselection.ReportImpressionRequest;
@@ -34,13 +33,7 @@ import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.adservices.service.PhFlagsFixture;
-import com.android.compatibility.common.util.ShellUtils;
-
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,29 +51,8 @@ public class PermissionsAppOptOutTest {
"java.lang.SecurityException: Caller is not authorized to call this API. "
+ "Caller is not allowed.";
- private static final int TEST_API_REQUEST_PER_SECOND = 2;
- private static final int DEFAULT_API_REQUEST_PER_SECOND = 1;
-
- @Before
- public void setup() {
- overrideConsentManagerDebugMode(true);
- overridingAdservicesLoggingLevel("VERBOSE");
- overrideAPIRateLimit(TEST_API_REQUEST_PER_SECOND);
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
- PhFlagsFixture.overrideSdkRequestPermitsPerSecond(Integer.MAX_VALUE);
- }
-
- @After
- public void teardown() {
- overrideConsentManagerDebugMode(false);
- overrideAPIRateLimit(DEFAULT_API_REQUEST_PER_SECOND);
- }
-
@Test
- public void testAppOptOut_topics() throws Exception {
- overrideDisableTopicsEnrollmentCheck("0");
+ public void testAppOptOut_topics() {
AdvertisingTopicsClient advertisingTopicsClient1 =
new AdvertisingTopicsClient.Builder()
.setContext(sContext)
@@ -92,12 +64,10 @@ public class PermissionsAppOptOutTest {
assertThrows(
ExecutionException.class, () -> advertisingTopicsClient1.getTopics().get());
assertThat(exception.getMessage()).isEqualTo(CALLER_NOT_AUTHORIZED);
- overrideDisableTopicsEnrollmentCheck("1");
}
@Test
public void testNoEnrollment_fledgeJoinCustomAudience() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
AdvertisingCustomAudienceClient customAudienceClient =
new AdvertisingCustomAudienceClient.Builder()
.setContext(sContext)
@@ -117,13 +87,11 @@ public class PermissionsAppOptOutTest {
ExecutionException.class,
() -> customAudienceClient.joinCustomAudience(customAudience).get());
assertThat(exception.getMessage()).isEqualTo(CALLER_NOT_AUTHORIZED);
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
}
@Test
public void testWithEnrollment_fledgeJoinCustomAudience()
throws ExecutionException, InterruptedException {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
AdvertisingCustomAudienceClient customAudienceClient =
new AdvertisingCustomAudienceClient.Builder()
.setContext(sContext)
@@ -141,12 +109,10 @@ public class PermissionsAppOptOutTest {
// When the ad tech is properly enrolled, just verify that no error is thrown
customAudienceClient.joinCustomAudience(customAudience).get();
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
}
@Test
public void testNoEnrollment_fledgeLeaveCustomAudience() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
AdvertisingCustomAudienceClient customAudienceClient =
new AdvertisingCustomAudienceClient.Builder()
.setContext(sContext)
@@ -163,13 +129,11 @@ public class PermissionsAppOptOutTest {
"exampleCustomAudience")
.get());
assertThat(exception.getMessage()).isEqualTo(CALLER_NOT_AUTHORIZED);
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
}
@Test
public void testWithEnrollment_fledgeLeaveCustomAudience()
throws ExecutionException, InterruptedException {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
AdvertisingCustomAudienceClient customAudienceClient =
new AdvertisingCustomAudienceClient.Builder()
.setContext(sContext)
@@ -182,13 +146,10 @@ public class PermissionsAppOptOutTest {
.leaveCustomAudience(
AdTechIdentifier.fromString("test.com"), "exampleCustomAudience")
.get();
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
}
@Test
public void testNoEnrollment_selectAds() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
-
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfig(
AdTechIdentifier.fromString("seller.example.com"));
@@ -204,13 +165,10 @@ public class PermissionsAppOptOutTest {
ExecutionException.class,
() -> mAdSelectionClient.selectAds(adSelectionConfig).get());
assertThat(exception.getMessage()).isEqualTo(CALLER_NOT_AUTHORIZED);
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
}
@Test
public void testWithEnrollment_selectAds() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
-
// The "test.com" buyer is a pre-seeded enrolled ad tech
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfig(
@@ -229,13 +187,10 @@ public class PermissionsAppOptOutTest {
ExecutionException.class,
() -> mAdSelectionClient.selectAds(adSelectionConfig).get());
assertThat(exception.getMessage()).isNotEqualTo(CALLER_NOT_AUTHORIZED);
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
}
@Test
public void testNoEnrollment_reportImpression() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
-
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfig(
AdTechIdentifier.fromString("seller.example.com"));
@@ -256,13 +211,10 @@ public class PermissionsAppOptOutTest {
ExecutionException.class,
() -> mAdSelectionClient.reportImpression(request).get());
assertThat(exception.getMessage()).isEqualTo(CALLER_NOT_AUTHORIZED);
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
}
@Test
public void testWithEnrollment_reportImpression() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
-
// The "test.com" buyer is a pre-seeded enrolled ad tech
AdSelectionConfig adSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfig(
@@ -286,28 +238,5 @@ public class PermissionsAppOptOutTest {
ExecutionException.class,
() -> mAdSelectionClient.reportImpression(request).get());
assertThat(exception.getMessage()).isNotEqualTo(CALLER_NOT_AUTHORIZED);
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
- }
-
- // Override the flag to disable Topics enrollment check.
- private void overrideDisableTopicsEnrollmentCheck(String val) {
- // Setting it to 1 here disables the Topics enrollment check.
- ShellUtils.runShellCommand(
- "setprop debug.adservices.disable_topics_enrollment_check " + val);
- }
-
- private void overridingAdservicesLoggingLevel(String loggingLevel) {
- ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
- }
-
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode(boolean isGiven) {
- ShellUtils.runShellCommand(
- "setprop debug.adservices.consent_manager_debug_mode " + isGiven);
- }
-
- private void overrideAPIRateLimit(int requestPerSecond) {
- ShellUtils.runShellCommand(
- "setprop debug.adservices.sdk_request_permits_per_second " + requestPerSecond);
}
}
diff --git a/adservices/tests/cts/endtoends/permissions/noperm/AndroidTest.xml b/adservices/tests/cts/endtoends/permissions/noperm/AndroidTest.xml
index ce5dce100..3c785ce5e 100644
--- a/adservices/tests/cts/endtoends/permissions/noperm/AndroidTest.xml
+++ b/adservices/tests/cts/endtoends/permissions/noperm/AndroidTest.xml
@@ -15,24 +15,47 @@
~ limitations under the License.
-->
<configuration description="Config for Cts Ad Services Permissions E2E tests">
-<option name="test-suite-tag" value="cts" />
-<option name="test-tag" value="CtsAdServicesPermissionsNoPermEndToEndTests" />
-<option name="config-descriptor:metadata" key="component" value="framework"/>
-<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-
-<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="CtsAdServicesPermissionsNoPermEndToEndTests.apk"/>
-</target_preparer>
-
-<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.adservices.tests.permissions.noperm"/>
-</test>
-
-<object type="module_controller"
- class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.adservices"/>
-</object>
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="CtsAdServicesPermissionsNoPermEndToEndTests" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CtsAdServicesPermissionsNoPermEndToEndTests.apk"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable kill switches to ignore the effect of actual PH values. -->
+ <option name="run-command" value="setprop debug.adservices.global_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_select_ads_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_custom_audience_service_kill_switch false" />
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+
+ <!-- Increase the allowed API queries per second -->
+ <option name="run-command" value="setprop debug.adservices.sdk_request_permits_per_second 1000" />
+ <option name="teardown-command" value="setprop debug.adservices.sdk_request_permits_per_second 1" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.adservices.tests.permissions.noperm"/>
+ </test>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.adservices"/>
+ </object>
</configuration>
diff --git a/adservices/tests/cts/endtoends/permissions/noperm/src/com/android/adservices/tests/permissions/PermissionsNoPermTest.java b/adservices/tests/cts/endtoends/permissions/noperm/src/com/android/adservices/tests/permissions/PermissionsNoPermTest.java
index 9287ddc58..f88d52328 100644
--- a/adservices/tests/cts/endtoends/permissions/noperm/src/com/android/adservices/tests/permissions/PermissionsNoPermTest.java
+++ b/adservices/tests/cts/endtoends/permissions/noperm/src/com/android/adservices/tests/permissions/PermissionsNoPermTest.java
@@ -41,10 +41,6 @@ import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.ShellUtils;
-
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -62,19 +58,8 @@ public class PermissionsNoPermTest {
"java.lang.SecurityException: Caller is not authorized to call this API. "
+ "Permission was not requested.";
- @Before
- public void setup() {
- overrideConsentManagerDebugMode(true);
- overridingAdservicesLoggingLevel("VERBOSE");
- }
-
- @After
- public void teardown() {
- overrideConsentManagerDebugMode(false);
- }
-
@Test
- public void testNoPerm_topics() throws Exception {
+ public void testNoPerm_topics() {
AdvertisingTopicsClient advertisingTopicsClient1 =
new AdvertisingTopicsClient.Builder()
.setContext(sContext)
@@ -308,14 +293,4 @@ public class PermissionsNoPermTest {
});
assertThat(exception.getMessage()).isEqualTo(CALLER_NOT_AUTHORIZED);
}
-
- private void overridingAdservicesLoggingLevel(String loggingLevel) {
- ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
- }
-
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode(boolean isGiven) {
- ShellUtils.runShellCommand(
- "setprop debug.adservices.consent_manager_debug_mode " + isGiven);
- }
}
diff --git a/adservices/tests/cts/endtoends/permissions/notallowed/AndroidTest.xml b/adservices/tests/cts/endtoends/permissions/notallowed/AndroidTest.xml
index 6b88ba277..66c9709a2 100644
--- a/adservices/tests/cts/endtoends/permissions/notallowed/AndroidTest.xml
+++ b/adservices/tests/cts/endtoends/permissions/notallowed/AndroidTest.xml
@@ -27,6 +27,25 @@
<option name="test-file-name" value="CtsAdServicesNotInAllowListEndToEndTests.apk"/>
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global/topics kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+ <!-- Override the flag to disable Topics enrollment check. -->
+ <option name="run-command" value="setprop debug.adservices.disable_topics_enrollment_check true" />
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+ <option name="teardown-command" value="setprop debug.adservices.disable_topics_enrollment_check false" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.adservices.tests.permissions.notallowed"/>
</test>
diff --git a/adservices/tests/cts/endtoends/permissions/notallowed/src/com/android/adservices/tests/permissions/NotInAllowListTest.java b/adservices/tests/cts/endtoends/permissions/notallowed/src/com/android/adservices/tests/permissions/NotInAllowListTest.java
index 48ade8a6e..610189b62 100644
--- a/adservices/tests/cts/endtoends/permissions/notallowed/src/com/android/adservices/tests/permissions/NotInAllowListTest.java
+++ b/adservices/tests/cts/endtoends/permissions/notallowed/src/com/android/adservices/tests/permissions/NotInAllowListTest.java
@@ -45,16 +45,20 @@ public class NotInAllowListTest {
private static final String CALLER_NOT_ALLOWED =
"java.lang.SecurityException: Caller is not authorized to call this API. "
+ "Caller is not allowed.";
+ private static final String SIGNATURE_ALLOWLIST =
+ "6cecc50e34ae31bfb5678986d6d6d3736c571ded2f2459527793e1f054eb0c9b,"
+ + "a40da80a59d170caa950cf15c18c454d47a39b26989d8b640ecd745ba71bf5dc,"
+ + "301aa3cb081134501c45f1422abc66c24224fd5ded5fdc8f17e697176fd866aa,"
+ + "c8a2e9bccf597c2fb6dc66bee293fc13f2fc47ec77bc6b2b0d52c11f51192ab8";
@Before
public void setup() {
- overrideConsentManagerDebugMode(true);
- overridingAdservicesLoggingLevel("VERBOSE");
+ overrideSignatureAllowListToEmpty(true);
}
@After
public void teardown() {
- overrideConsentManagerDebugMode(false);
+ overrideSignatureAllowListToEmpty(false);
}
@Test
@@ -72,13 +76,10 @@ public class NotInAllowListTest {
assertThat(exception.getMessage()).isEqualTo(CALLER_NOT_ALLOWED);
}
- private void overridingAdservicesLoggingLevel(String loggingLevel) {
- ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
- }
-
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode(boolean isGiven) {
+ // Override Signature Allow List to deny the signature of this test
+ public void overrideSignatureAllowListToEmpty(boolean isEmpty) {
+ String overrideString = isEmpty ? "empty" : SIGNATURE_ALLOWLIST;
ShellUtils.runShellCommand(
- "setprop debug.adservices.consent_manager_debug_mode " + isGiven);
+ "device_config put adservices ppapi_app_signature_allow_list %s", overrideString);
}
}
diff --git a/adservices/tests/cts/endtoends/permissions/valid/AndroidTest.xml b/adservices/tests/cts/endtoends/permissions/valid/AndroidTest.xml
index 4c9bb4707..901460ad0 100644
--- a/adservices/tests/cts/endtoends/permissions/valid/AndroidTest.xml
+++ b/adservices/tests/cts/endtoends/permissions/valid/AndroidTest.xml
@@ -15,24 +15,52 @@
~ limitations under the License.
-->
<configuration description="Config for Cts Ad Services Permissions E2E tests">
-<option name="test-suite-tag" value="cts" />
-<option name="test-tag" value="CtsAdServicesPermissionsValidEndToEndTests" />
-<option name="config-descriptor:metadata" key="component" value="framework"/>
-<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-
-<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="CtsAdServicesPermissionsValidEndToEndTests.apk"/>
-</target_preparer>
-
-<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.adservices.tests.permissions.valid"/>
-</test>
-
-<object type="module_controller"
- class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.adservices"/>
-</object>
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="CtsAdServicesPermissionsValidEndToEndTests" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CtsAdServicesPermissionsValidEndToEndTests.apk"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable kill switches to ignore the effect of actual PH values. -->
+ <option name="run-command" value="setprop debug.adservices.global_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_select_ads_kill_switch false" />
+ <option name="run-command" value="setprop debug.adservices.fledge_custom_audience_service_kill_switch false" />
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+
+ <!-- Force enable enrollment checks when testing enrollment -->
+ <option name="run-command" value="setprop debug.adservices.disable_fledge_enrollment_check false" />
+ <option name="run-command" value="setprop debug.adservices.disable_topics_enrollment_check true" />
+ <option name="teardown-command" value="setprop debug.adservices.disable_topics_enrollment_check false" />
+
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+
+ <!-- Increase the allowed API queries per second -->
+ <option name="run-command" value="setprop debug.adservices.sdk_request_permits_per_second 1000" />
+ <option name="teardown-command" value="setprop debug.adservices.sdk_request_permits_per_second 1" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.adservices.tests.permissions.valid"/>
+ </test>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.adservices"/>
+ </object>
</configuration>
diff --git a/adservices/tests/cts/endtoends/permissions/valid/src/com/android/adservices/tests/permissions/PermissionsValidTest.java b/adservices/tests/cts/endtoends/permissions/valid/src/com/android/adservices/tests/permissions/PermissionsValidTest.java
index 4d8eda8c3..9886ad18d 100644
--- a/adservices/tests/cts/endtoends/permissions/valid/src/com/android/adservices/tests/permissions/PermissionsValidTest.java
+++ b/adservices/tests/cts/endtoends/permissions/valid/src/com/android/adservices/tests/permissions/PermissionsValidTest.java
@@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
-import android.Manifest;
import android.adservices.adselection.AdSelectionConfig;
import android.adservices.adselection.AdSelectionConfigFixture;
import android.adservices.adselection.ReportImpressionRequest;
@@ -35,13 +34,7 @@ import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.adservices.service.PhFlagsFixture;
-import com.android.compatibility.common.util.ShellUtils;
-
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,24 +50,8 @@ public class PermissionsValidTest {
private static final String PERMISSION_NOT_REQUESTED =
"Caller is not authorized to call this API. Permission was not requested.";
- @Before
- public void setup() {
- overrideConsentManagerDebugMode(true);
- overridingAdservicesLoggingLevel("VERBOSE");
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
- }
-
- @After
- public void teardown() {
- overrideConsentManagerDebugMode(false);
- }
-
@Test
public void testValidPermissions_topics() throws Exception {
- overrideDisableTopicsEnrollmentCheck("1");
-
AdvertisingTopicsClient advertisingTopicsClient1 =
new AdvertisingTopicsClient.Builder()
.setContext(sContext)
@@ -86,124 +63,81 @@ public class PermissionsValidTest {
// Not getting an error here indicates that permissions are valid. The valid case is also
// tested in TopicsManagerTest.
assertThat(sdk1Result.getTopics()).isEmpty();
- overrideDisableTopicsEnrollmentCheck("0");
}
@Test
public void testValidPermissions_fledgeJoinCustomAudience()
throws ExecutionException, InterruptedException {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
-
- try {
- AdvertisingCustomAudienceClient customAudienceClient =
- new AdvertisingCustomAudienceClient.Builder()
- .setContext(sContext)
- .setExecutor(CALLBACK_EXECUTOR)
- .build();
-
- CustomAudience customAudience =
- new CustomAudience.Builder()
- .setBuyer(AdTechIdentifier.fromString("test.com"))
- .setName("exampleCustomAudience")
- .setDailyUpdateUri(Uri.parse("https://test.com/daily-update"))
- .setBiddingLogicUri(Uri.parse("https://test.com/bidding-logic"))
- .build();
-
- customAudienceClient.joinCustomAudience(customAudience).get();
- } finally {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
- }
+ AdvertisingCustomAudienceClient customAudienceClient =
+ new AdvertisingCustomAudienceClient.Builder()
+ .setContext(sContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+
+ CustomAudience customAudience =
+ new CustomAudience.Builder()
+ .setBuyer(AdTechIdentifier.fromString("test.com"))
+ .setName("exampleCustomAudience")
+ .setDailyUpdateUri(Uri.parse("https://test.com/daily-update"))
+ .setBiddingLogicUri(Uri.parse("https://test.com/bidding-logic"))
+ .build();
+
+ customAudienceClient.joinCustomAudience(customAudience).get();
}
@Test
public void testValidPermissions_selectAds() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
-
AdSelectionConfig adSelectionConfig = AdSelectionConfigFixture.anAdSelectionConfig();
- try {
- AdSelectionClient mAdSelectionClient =
- new AdSelectionClient.Builder()
- .setContext(sContext)
- .setExecutor(CALLBACK_EXECUTOR)
- .build();
-
- ExecutionException exception =
- assertThrows(
- ExecutionException.class,
- () -> mAdSelectionClient.selectAds(adSelectionConfig).get());
- // We only need to get past the permissions check for this test to be valid
- assertThat(exception.getMessage()).isNotEqualTo(PERMISSION_NOT_REQUESTED);
- } finally {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
- }
+ AdSelectionClient mAdSelectionClient =
+ new AdSelectionClient.Builder()
+ .setContext(sContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+
+ ExecutionException exception =
+ assertThrows(
+ ExecutionException.class,
+ () -> mAdSelectionClient.selectAds(adSelectionConfig).get());
+ // We only need to get past the permissions check for this test to be valid
+ assertThat(exception.getMessage()).isNotEqualTo(PERMISSION_NOT_REQUESTED);
}
@Test
public void testValidPermissions_reportImpression() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
-
AdSelectionConfig adSelectionConfig = AdSelectionConfigFixture.anAdSelectionConfig();
long adSelectionId = 1;
- try {
- AdSelectionClient mAdSelectionClient =
- new AdSelectionClient.Builder()
- .setContext(sContext)
- .setExecutor(CALLBACK_EXECUTOR)
- .build();
-
- ReportImpressionRequest request =
- new ReportImpressionRequest(adSelectionId, adSelectionConfig);
-
- ExecutionException exception =
- assertThrows(
- ExecutionException.class,
- () -> mAdSelectionClient.reportImpression(request).get());
- // We only need to get past the permissions check for this test to be valid
- assertThat(exception.getMessage()).isNotEqualTo(PERMISSION_NOT_REQUESTED);
- } finally {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
- }
+ AdSelectionClient mAdSelectionClient =
+ new AdSelectionClient.Builder()
+ .setContext(sContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+
+ ReportImpressionRequest request =
+ new ReportImpressionRequest(adSelectionId, adSelectionConfig);
+
+ ExecutionException exception =
+ assertThrows(
+ ExecutionException.class,
+ () -> mAdSelectionClient.reportImpression(request).get());
+ // We only need to get past the permissions check for this test to be valid
+ assertThat(exception.getMessage()).isNotEqualTo(PERMISSION_NOT_REQUESTED);
}
@Test
public void testValidPermissions_fledgeLeaveCustomAudience()
throws ExecutionException, InterruptedException {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
-
- try {
- AdvertisingCustomAudienceClient customAudienceClient =
- new AdvertisingCustomAudienceClient.Builder()
- .setContext(sContext)
- .setExecutor(CALLBACK_EXECUTOR)
- .build();
-
- customAudienceClient
- .leaveCustomAudience(
- AdTechIdentifier.fromString("test.com"),
- "exampleCustomAudience")
- .get();
- } finally {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
- }
- }
-
- // Override the flag to disable Topics enrollment check.
- private void overrideDisableTopicsEnrollmentCheck(String val) {
- // Setting it to 1 here disables the Topics enrollment check.
- ShellUtils.runShellCommand(
- "setprop debug.adservices.disable_topics_enrollment_check " + val);
- }
-
- private void overridingAdservicesLoggingLevel(String loggingLevel) {
- ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
- }
+ AdvertisingCustomAudienceClient customAudienceClient =
+ new AdvertisingCustomAudienceClient.Builder()
+ .setContext(sContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode(boolean isGiven) {
- ShellUtils.runShellCommand(
- "setprop debug.adservices.consent_manager_debug_mode " + isGiven);
+ customAudienceClient
+ .leaveCustomAudience(
+ AdTechIdentifier.fromString("test.com"), "exampleCustomAudience")
+ .get();
}
}
diff --git a/adservices/tests/cts/endtoends/topics/Android.bp b/adservices/tests/cts/endtoends/topics/Android.bp
index e7047fc72..ce874443a 100644
--- a/adservices/tests/cts/endtoends/topics/Android.bp
+++ b/adservices/tests/cts/endtoends/topics/Android.bp
@@ -39,12 +39,9 @@ android_test {
"general-tests",
"mts-adservices"
],
- data: [
- // Sample App will be installed and uninstalled in the test.
- ":CtsSampleTopicsApp1",
- ],
// To fix Pre-submit warning, suggested by farivar@
javacflags: ["-parameters"],
sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
test_mainline_modules: ["com.google.android.adservices.apex"],
}
diff --git a/adservices/tests/cts/endtoends/topics/AndroidTest.xml b/adservices/tests/cts/endtoends/topics/AndroidTest.xml
index 4e4b358a6..bb03e7781 100644
--- a/adservices/tests/cts/endtoends/topics/AndroidTest.xml
+++ b/adservices/tests/cts/endtoends/topics/AndroidTest.xml
@@ -15,42 +15,49 @@
~ limitations under the License.
-->
<configuration description="Config for Cts Ad Services E2E tests">
-<option name="test-suite-tag" value="cts" />
-<option name="test-tag" value="CtsAdServicesEndToEndTests" />
-<option name="config-descriptor:metadata" key="component" value="framework"/>
-<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="CtsAdServicesEndToEndTests" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <!-- The test apps to be installed and uninstalled -->
- <option name="test-file-name" value="CtsAdServicesEndToEndTests.apk"/>
-</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <!-- The test apps to be installed and uninstalled -->
+ <option name="test-file-name" value="CtsAdServicesEndToEndTests.apk"/>
+ </target_preparer>
-<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="hidden-api-checks" value="false" /> <!-- Allow hidden API uses -->
- <option name="package" value="com.android.adservices.tests.cts.endtoendtest"/>
-</test>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="hidden-api-checks" value="false" /> <!-- Allow hidden API uses -->
+ <option name="package" value="com.android.adservices.tests.cts.endtoendtest"/>
+ </test>
-<!-- Create place to store tests apks that will be installed/uninstalled in the test. -->
-<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="mkdir -p /data/local/tmp/cts/install" />
- <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
- <!-- Uninstall the test app anyway in case failed test skips the uninstallation -->
- <option name="teardown-command"
- value="pm uninstall --user 0 com.android.adservices.tests.cts.topics.testapp1" />
-</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
-<!-- Push compiled APK file of test apps to a local place for installation -->
-<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
- <option name="push"
- value="CtsSampleTopicsApp1.apk->/data/local/tmp/cts/install/CtsSampleTopicsApp1.apk" />
-</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global/topics kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+ <!-- Override the flag to disable Topics enrollment check. -->
+ <option name="run-command" value="setprop debug.adservices.disable_topics_enrollment_check true" />
+ <!-- Forces using bundled model files. -->
+ <option name="run-command" value="device_config put adservices classifier_force_use_bundled_files true" />
-<object type="module_controller"
- class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.adservices"/>
-</object>
-<option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.adservices.apex" />
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+ <option name="teardown-command" value="setprop debug.adservices.disable_topics_enrollment_check false" />
+ <option name="teardown-command" value="device_config put adservices classifier_force_use_bundled_files false" />
+ </target_preparer>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.adservices"/>
+ </object>
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.adservices.apex" />
</configuration>
diff --git a/adservices/tests/cts/endtoends/topics/appupdate/Android.bp b/adservices/tests/cts/endtoends/topics/appupdate/Android.bp
new file mode 100644
index 000000000..290e49c97
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/appupdate/Android.bp
@@ -0,0 +1,51 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsAdServicesTopicsAppUpdateTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.concurrent_concurrent-futures",
+ "compatibility-device-util-axt",
+ "truth-prebuilt",
+ "ub-uiautomator",
+ "adservices-test-fixtures",
+ "adservices-clients",
+ ],
+ libs: [
+ "android.test.base",
+ "framework-adservices.impl",
+ "framework-sdksandbox.impl",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-adservices"
+ ],
+ data: [
+ // Sample App will be installed and uninstalled in the test.
+ ":CtsSampleTopicsApp1",
+ ],
+ // To fix Pre-submit warning, suggested by farivar@
+ javacflags: ["-parameters"],
+ sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
+ test_mainline_modules: ["com.google.android.adservices.apex"],
+}
diff --git a/adservices/tests/cts/endtoends/topics/appupdate/AndroidManifest.xml b/adservices/tests/cts/endtoends/topics/appupdate/AndroidManifest.xml
new file mode 100644
index 000000000..93c459781
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/appupdate/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.adservices.tests.cts.topics.appupdate" >
+
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
+ <application>
+ <property android:name="android.adservices.AD_SERVICES_CONFIG"
+ android:resource="@xml/ad_services_config" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.adservices.tests.cts.topics.appupdate" >
+ </instrumentation>
+</manifest>
diff --git a/adservices/tests/cts/endtoends/topics/appupdate/AndroidTest.xml b/adservices/tests/cts/endtoends/topics/appupdate/AndroidTest.xml
new file mode 100644
index 000000000..8b1b69139
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/appupdate/AndroidTest.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Config for Cts Ad Services App Update tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="CtsAdServicesTopicsAppUpdateTests" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <!-- The test apps to be installed and uninstalled -->
+ <option name="test-file-name" value="CtsAdServicesTopicsAppUpdateTests.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="hidden-api-checks" value="false" /> <!-- Allow hidden API uses -->
+ <option name="package" value="com.android.adservices.tests.cts.topics.appupdate"/>
+ </test>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global/topics kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+ <!-- Override the flag to disable Topics enrollment check. -->
+ <option name="run-command" value="setprop debug.adservices.disable_topics_enrollment_check true" />
+ <!-- Create place to store tests apks that will be installed/uninstalled in the test. -->
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/install" />
+ <!-- Forces using bundled model files. -->
+ <option name="run-command" value="device_config put adservices classifier_force_use_bundled_files true" />
+
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+ <option name="teardown-command" value="setprop debug.adservices.disable_topics_enrollment_check false" />
+ <option name="teardown-command" value="device_config put adservices classifier_force_use_bundled_files false" />
+
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ <!-- Uninstall the test app anyway in case failed test skips the uninstallation -->
+ <option name="teardown-command"
+ value="pm uninstall --user 0 com.android.adservices.tests.cts.topics.testapp1" />
+ </target_preparer>
+
+ <!-- Create place to store tests apks that will be installed/uninstalled in the test. -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/install" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ <!-- Uninstall the test app anyway in case failed test skips the uninstallation -->
+ <option name="teardown-command"
+ value="pm uninstall --user 0 com.android.adservices.tests.cts.topics.testapp1" />
+ </target_preparer>
+
+ <!-- Push compiled APK file of test apps to a local place for installation -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push"
+ value="CtsSampleTopicsApp1.apk->/data/local/tmp/cts/install/CtsSampleTopicsApp1.apk" />
+ </target_preparer>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.adservices"/>
+ </object>
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.adservices.apex" />
+</configuration>
diff --git a/adservices/tests/cts/endtoends/topics/appupdate/res/xml/ad_services_config.xml b/adservices/tests/cts/endtoends/topics/appupdate/res/xml/ad_services_config.xml
new file mode 100644
index 000000000..452452355
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/appupdate/res/xml/ad_services_config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<ad-services-config>
+ <attribution allowAdPartnersToAccess="1234" allowAllToAccess="false" />
+ <custom-audiences allowAllToAccess="false" allowAdPartnersToAccess="1234, 4567" />
+ <topics allowAllToAccess="true" />
+</ad-services-config>
diff --git a/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/AppUpdateTest.java b/adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java
index 033c951d5..0fe768b0d 100644
--- a/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/AppUpdateTest.java
+++ b/adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.adservices.tests.cts.topics;
+package com.android.adservices.tests.cts.topics.appupdate;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -151,14 +151,17 @@ public class AppUpdateTest {
// not be used for epoch retrieval.
Thread.sleep(3 * TEST_EPOCH_JOB_PERIOD_MS);
- overridingBeforeTest();
+ overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
+ // We need to turn off random topic so that we can verify the returned topic.
+ overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
registerTopicResponseReceiver();
}
@After
public void tearDown() {
- overridingAfterTest();
+ overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
+ overridePercentageForRandomTopic(DEFAULT_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
}
@Test
@@ -277,37 +280,6 @@ public class AppUpdateTest {
sContext.registerReceiver(mTopicsResponseReceiver, topicResponseIntentFilter);
}
- private void overridingBeforeTest() {
- overridingAdservicesLoggingLevel("VERBOSE");
-
- overrideDisableTopicsEnrollmentCheck("1");
- overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
-
- // We need to turn off random topic so that we can verify the returned topic.
- overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
-
- // We need to turn the Consent Manager into debug mode
- overrideConsentManagerDebugMode();
-
- // Turn off MDD to avoid model mismatching
- disableMddBackgroundTasks(true);
- }
-
- // Reset back the original values.
- private void overridingAfterTest() {
- overrideDisableTopicsEnrollmentCheck("0");
- overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
- overridePercentageForRandomTopic(DEFAULT_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
- disableMddBackgroundTasks(false);
- overridingAdservicesLoggingLevel("INFO");
- }
-
- // Switch on/off for MDD service. Default value is false, which means MDD is enabled.
- private void disableMddBackgroundTasks(boolean isSwitchedOff) {
- ShellUtils.runShellCommand(
- "setprop debug.adservices.mdd_background_task_kill_switch " + isSwitchedOff);
- }
-
// Install test sample app 1 and verify the installation.
private void installTestSampleApp() {
String installMessage = ShellUtils.runShellCommand("pm install -r " + TEST_APK_PATH);
@@ -319,11 +291,10 @@ public class AppUpdateTest {
ShellUtils.runShellCommand("pm uninstall --user 0 " + TEST_PKG_NAME);
}
- // Override the flag to disable Topics enrollment check.
- private void overrideDisableTopicsEnrollmentCheck(String val) {
- // Setting it to 1 here disables the Topics enrollment check.
+ /** Forces JobScheduler to run the Epoch Computation job */
+ private void forceEpochComputationJob() {
ShellUtils.runShellCommand(
- "setprop debug.adservices.disable_topics_enrollment_check " + val);
+ "cmd jobscheduler run -f" + " " + ADSERVICES_PACKAGE_NAME + " " + EPOCH_JOB_ID);
}
// Override the Epoch Period to shorten the Epoch Length in the test.
@@ -339,17 +310,6 @@ public class AppUpdateTest {
+ overridePercentage);
}
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
- }
-
- // Forces JobScheduler to run the Epoch Computation job.
- private void forceEpochComputationJob() {
- ShellUtils.runShellCommand(
- "cmd jobscheduler run -f" + " " + ADSERVICES_PACKAGE_NAME + " " + EPOCH_JOB_ID);
- }
-
// Forces JobScheduler to run the Maintenance job.
private void forceMaintenanceJob() {
ShellUtils.runShellCommand(
@@ -360,10 +320,6 @@ public class AppUpdateTest {
+ MAINTENANCE_JOB_ID);
}
- private void overridingAdservicesLoggingLevel(String loggingLevel) {
- ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
- }
-
// Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
private static String getAdServicesPackageName() {
final Intent intent = new Intent(TOPICS_SERVICE_NAME);
diff --git a/adservices/tests/cts/endtoends/topics/connection/Android.bp b/adservices/tests/cts/endtoends/topics/connection/Android.bp
new file mode 100644
index 000000000..dbaaee2bc
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/connection/Android.bp
@@ -0,0 +1,47 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsAdServicesTopicsConnectionTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.concurrent_concurrent-futures",
+ "compatibility-device-util-axt",
+ "truth-prebuilt",
+ "ub-uiautomator",
+ "adservices-test-fixtures",
+ "adservices-clients",
+ ],
+ libs: [
+ "android.test.base",
+ "framework-adservices.impl",
+ "framework-sdksandbox.impl",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-adservices"
+ ],
+ // To fix Pre-submit warning, suggested by farivar@
+ javacflags: ["-parameters"],
+ sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
+ test_mainline_modules: ["com.google.android.adservices.apex"],
+}
diff --git a/adservices/tests/cts/endtoends/topics/connection/AndroidManifest.xml b/adservices/tests/cts/endtoends/topics/connection/AndroidManifest.xml
new file mode 100644
index 000000000..e23fe3ade
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/connection/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.adservices.tests.cts.topics.connection" >
+
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
+ <application>
+ <property android:name="android.adservices.AD_SERVICES_CONFIG"
+ android:resource="@xml/ad_services_config" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.adservices.tests.cts.topics.connection" >
+ </instrumentation>
+</manifest>
diff --git a/adservices/tests/cts/endtoends/topics/connection/AndroidTest.xml b/adservices/tests/cts/endtoends/topics/connection/AndroidTest.xml
new file mode 100644
index 000000000..a1f588a75
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/connection/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Config for Cts Ad Services Connection E2E tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="CtsAdServicesTopicsConnectionTests" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <!-- The test apps to be installed and uninstalled -->
+ <option name="test-file-name" value="CtsAdServicesTopicsConnectionTests.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="hidden-api-checks" value="false" /> <!-- Allow hidden API uses -->
+ <option name="package" value="com.android.adservices.tests.cts.topics.connection"/>
+ </test>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable topics kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+ <!-- Override the flag to disable Topics enrollment check. -->
+ <option name="run-command" value="setprop debug.adservices.disable_topics_enrollment_check true" />
+ <!-- Forces using bundled model files. -->
+ <option name="run-command" value="device_config put adservices classifier_force_use_bundled_files true" />
+
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+ <option name="teardown-command" value="setprop debug.adservices.disable_topics_enrollment_check false" />
+ <option name="teardown-command" value="device_config put adservices classifier_force_use_bundled_files false" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.adservices"/>
+ </object>
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.adservices.apex" />
+</configuration>
diff --git a/adservices/tests/cts/endtoends/topics/connection/res/xml/ad_services_config.xml b/adservices/tests/cts/endtoends/topics/connection/res/xml/ad_services_config.xml
new file mode 100644
index 000000000..452452355
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/connection/res/xml/ad_services_config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<ad-services-config>
+ <attribution allowAdPartnersToAccess="1234" allowAllToAccess="false" />
+ <custom-audiences allowAllToAccess="false" allowAdPartnersToAccess="1234, 4567" />
+ <topics allowAllToAccess="true" />
+</ad-services-config>
diff --git a/adservices/tests/cts/endtoends/topics/connection/src/com/android/adservices/tests/cts/topics/connection/TopicsConnectionTest.java b/adservices/tests/cts/endtoends/topics/connection/src/com/android/adservices/tests/cts/topics/connection/TopicsConnectionTest.java
new file mode 100644
index 000000000..077da0d1b
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/connection/src/com/android/adservices/tests/cts/topics/connection/TopicsConnectionTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.tests.cts.topics.connection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.adservices.clients.topics.AdvertisingTopicsClient;
+import android.adservices.topics.GetTopicsResponse;
+import android.adservices.topics.Topic;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+@RunWith(JUnit4.class)
+public class TopicsConnectionTest {
+ private static final String TAG = "TopicsConnectionTest";
+
+ private static final String ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE = "Service is not available.";
+
+ // The JobId of the Epoch Computation.
+ private static final int EPOCH_JOB_ID = 2;
+
+ // Override the Epoch Job Period to this value to speed up the epoch computation.
+ private static final long TEST_EPOCH_JOB_PERIOD_MS = 3000;
+
+ // Default Epoch Period.
+ private static final long TOPICS_EPOCH_JOB_PERIOD_MS = 7 * 86_400_000; // 7 days.
+
+ // Use 0 percent for random topic in the test so that we can verify the returned topic.
+ private static final int TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 0;
+ private static final int TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 5;
+
+ protected static final Context sContext = ApplicationProvider.getApplicationContext();
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+
+ // Used to get the package name. Copied over from com.android.adservices.AdServicesCommon
+ private static final String TOPICS_SERVICE_NAME = "android.adservices.TOPICS_SERVICE";
+ private static final String ADSERVICES_PACKAGE_NAME = getAdServicesPackageName();
+
+ @Before
+ public void setup() throws Exception {
+ // We need to skip 3 epochs so that if there is any usage from other test runs, it will
+ // not be used for epoch retrieval.
+ Thread.sleep(3 * TEST_EPOCH_JOB_PERIOD_MS);
+
+ overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
+
+ // We need to turn off random topic so that we can verify the returned topic.
+ overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+ }
+
+ @After
+ public void teardown() {
+ overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
+ overridePercentageForRandomTopic(TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+ }
+
+ @Test
+ public void testEnableGlobalKillSwitch() throws Exception {
+ // First enable the Global Kill Switch and then connect to the TopicsService.
+ // The connection should fail with Exception.
+ enableGlobalKillSwitch(/* enabled */ true);
+
+ // Sdk1 calls the Topics git .
+ AdvertisingTopicsClient advertisingTopicsClient1 =
+ new AdvertisingTopicsClient.Builder()
+ .setContext(sContext)
+ .setSdkName("sdk1")
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+
+ // Due to global kill switch is enabled, we receive the IllegalStateException.
+ ExecutionException exception =
+ assertThrows(
+ ExecutionException.class, () -> advertisingTopicsClient1.getTopics().get());
+ assertThat(exception).hasCauseThat().isInstanceOf(IllegalStateException.class);
+ assertThat(exception).hasMessageThat().contains(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE);
+
+ // Now disable the Global Kill Switch, we should be able to connect to the Service normally.
+ enableGlobalKillSwitch(/* enabled */ false);
+
+ // At beginning, Sdk1 receives no topic.
+ GetTopicsResponse sdk1Result = advertisingTopicsClient1.getTopics().get();
+ assertThat(sdk1Result.getTopics()).isEmpty();
+
+ // Now force the Epoch Computation Job. This should be done in the same epoch for
+ // callersCanLearnMap to have the entry for processing.
+ forceEpochComputationJob();
+
+ // Wait to the next epoch. We will not need to do this after we implement the fix in
+ // go/rb-topics-epoch-scheduling
+ Thread.sleep(TEST_EPOCH_JOB_PERIOD_MS);
+
+ // Since the sdk1 called the Topics API in the previous Epoch, it should receive some topic.
+ sdk1Result = advertisingTopicsClient1.getTopics().get();
+ assertThat(sdk1Result.getTopics()).isNotEmpty();
+
+ // We only have 1 test app which has 5 classification topics: 10147,10253,10175,10254,10333
+ // in the precomputed list.
+ // These 5 classification topics will become top 5 topics of the epoch since there is
+ // no other apps calling Topics API.
+ // The app will be assigned one random topic from one of these 5 topics.
+ assertThat(sdk1Result.getTopics()).hasSize(1);
+ Topic topic = sdk1Result.getTopics().get(0);
+
+ // topic is one of the 5 classification topics of the Test App.
+ assertThat(topic.getTopicId()).isIn(Arrays.asList(10147, 10253, 10175, 10254, 10333));
+
+ assertThat(topic.getModelVersion()).isAtLeast(1L);
+ assertThat(topic.getTaxonomyVersion()).isAtLeast(1L);
+ }
+
+ // Override global_kill_switch to ignore the effect of actual PH values.
+ // If enabled = true, override global_kill_switch to ON to turn off Adservices.
+ // If enabled = false, the AdServices is enabled.
+ private void enableGlobalKillSwitch(boolean enabled) {
+ String overrideString = enabled ? "true" : "false";
+ ShellUtils.runShellCommand("setprop debug.adservices.global_kill_switch " + overrideString);
+ }
+
+ // Override the Epoch Period to shorten the Epoch Length in the test.
+ private void overrideEpochPeriod(long overrideEpochPeriod) {
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.topics_epoch_job_period_ms " + overrideEpochPeriod);
+ }
+
+ // Override the Percentage For Random Topic in the test.
+ private void overridePercentageForRandomTopic(long overridePercentage) {
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.topics_percentage_for_random_topics "
+ + overridePercentage);
+ }
+
+ /** Forces JobScheduler to run the Epoch Computation job */
+ private void forceEpochComputationJob() {
+ ShellUtils.runShellCommand(
+ "cmd jobscheduler run -f" + " " + ADSERVICES_PACKAGE_NAME + " " + EPOCH_JOB_ID);
+ }
+
+ // Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
+ private static String getAdServicesPackageName() {
+ final Intent intent = new Intent(TOPICS_SERVICE_NAME);
+ final List<ResolveInfo> resolveInfos =
+ sContext.getPackageManager()
+ .queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ Log.e(
+ TAG,
+ "Failed to find resolveInfo for adServices service. Intent action: "
+ + TOPICS_SERVICE_NAME);
+ return null;
+ }
+
+ if (resolveInfos.size() > 1) {
+ Log.e(
+ TAG,
+ String.format(
+ "Found multiple services (%1$s) for the same intent action (%2$s)",
+ TOPICS_SERVICE_NAME, resolveInfos));
+ return null;
+ }
+
+ final ServiceInfo serviceInfo = resolveInfos.get(0).serviceInfo;
+ if (serviceInfo == null) {
+ Log.e(TAG, "Failed to find serviceInfo for adServices service");
+ return null;
+ }
+
+ return serviceInfo.packageName;
+ }
+}
diff --git a/adservices/tests/cts/endtoends/topics/mdd/Android.bp b/adservices/tests/cts/endtoends/topics/mdd/Android.bp
new file mode 100644
index 000000000..3f7e5b932
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/mdd/Android.bp
@@ -0,0 +1,44 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsAdServicesMddTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.concurrent_concurrent-futures",
+ "compatibility-device-util-axt", // Needed for ShellUtils.runShellCommand
+ "truth-prebuilt",
+ "adservices-clients",
+ ],
+ libs: [
+ "android.test.base",
+ // TODO(b/246590510): Remove these.
+ "framework-adservices.impl",
+ "framework-sdksandbox.impl",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-adservices"
+ ],
+ sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
+ test_mainline_modules: ["com.google.android.adservices.apex"],
+}
diff --git a/adservices/tests/cts/endtoends/topics/mdd/AndroidManifest.xml b/adservices/tests/cts/endtoends/topics/mdd/AndroidManifest.xml
new file mode 100644
index 000000000..2cc114418
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/mdd/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.adservices.tests.cts.topics.mdd" >
+
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
+ <application>
+ <property android:name="android.adservices.AD_SERVICES_CONFIG"
+ android:resource="@xml/ad_services_config" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.adservices.tests.cts.topics.mdd" >
+ </instrumentation>
+</manifest>
diff --git a/adservices/tests/cts/endtoends/topics/mdd/AndroidTest.xml b/adservices/tests/cts/endtoends/topics/mdd/AndroidTest.xml
new file mode 100644
index 000000000..d5401029a
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/mdd/AndroidTest.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Config for Cts Ad Services MDD E2E tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="CtsAdServicesMddTests" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <!-- The test apps to be installed and uninstalled -->
+ <option name="test-file-name" value="CtsAdServicesMddTests.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.adservices.tests.cts.topics.mdd"/>
+ </test>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global/topics kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+ <!-- Override the flag to disable Topics enrollment check. -->
+ <option name="run-command" value="setprop debug.adservices.disable_topics_enrollment_check true" />
+
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+ <option name="teardown-command" value="setprop debug.adservices.disable_topics_enrollment_check false" />
+ </target_preparer>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.adservices"/>
+ </object>
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.adservices.apex" />
+</configuration>
diff --git a/adservices/tests/cts/endtoends/topics/mdd/res/xml/ad_services_config.xml b/adservices/tests/cts/endtoends/topics/mdd/res/xml/ad_services_config.xml
new file mode 100644
index 000000000..d1ac060f6
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/mdd/res/xml/ad_services_config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<ad-services-config>
+ <topics allowAllToAccess="true" />
+</ad-services-config>
diff --git a/adservices/tests/cts/endtoends/topics/mdd/src/com/android/adservices/tests/cts/topics/mdd/TopicsManagerMddTest.java b/adservices/tests/cts/endtoends/topics/mdd/src/com/android/adservices/tests/cts/topics/mdd/TopicsManagerMddTest.java
new file mode 100644
index 000000000..8ee5a63cd
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/mdd/src/com/android/adservices/tests/cts/topics/mdd/TopicsManagerMddTest.java
@@ -0,0 +1,284 @@
+/*
+ * 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.tests.cts.topics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.adservices.clients.topics.AdvertisingTopicsClient;
+import android.adservices.topics.GetTopicsResponse;
+import android.adservices.topics.Topic;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+@RunWith(JUnit4.class)
+public class TopicsManagerMddTest {
+ private static final String TAG = "TopicsManagerMddTest";
+ // The JobId of the Epoch Computation.
+ private static final int EPOCH_JOB_ID = 2;
+ // Job ID for Mdd Wifi Charging Task.
+ public static final int MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID = 14;
+
+ // Override the Epoch Job Period to this value to speed up the epoch computation.
+ private static final long TEST_EPOCH_JOB_PERIOD_MS = 3000;
+ // Waiting time for assets to be downloaded after triggering MDD job.
+ private static final long TEST_MDD_DOWNLOAD_WAIT_TIME_MS = 10000;
+
+ // Default Epoch Period.
+ private static final long TOPICS_EPOCH_JOB_PERIOD_MS = 7 * 86_400_000; // 7 days.
+
+ // Classifier test constants.
+ private static final int TEST_CLASSIFIER_NUMBER_OF_TOP_LABELS = 5;
+ // Each app is given topics with a confidence score between 0.0 to 1.0 float value. This
+ // denotes how confident are you that a particular topic t1 is related to the app x that is
+ // classified.
+ // Threshold value for classifier confidence set to 0 to allow all topics and avoid filtering.
+ private static final float TEST_CLASSIFIER_THRESHOLD = 0.0f;
+ // classifier_type flag for ON_DEVICE_CLASSIFIER.
+ private static final int ON_DEVICE_CLASSIFIER = 1;
+ // Manifest file that points to CTS test assets:
+ // http://google3/wireless/android/adservices/mdd/topics_classifier/cts_test_1/
+ // These assets are have asset version set to 0 for verification in tests.
+ private static final String TEST_MDD_MANIFEST_FILE_URL =
+ "https://www.gstatic.com/mdi-serving/rubidium-adservices-topics-classifier/1043"
+ + "/784a7d30e5ac9fd6410fb017b05f392f67e9659a";
+
+ // Classifier default constants.
+ private static final int DEFAULT_CLASSIFIER_NUMBER_OF_TOP_LABELS = 3;
+ // Threshold value for classifier confidence set back to the default.
+ private static final float DEFAULT_CLASSIFIER_THRESHOLD = 0.1f;
+ // PRECOMPUTED_THEN_ON_DEVICE_CLASSIFIER
+ private static final int DEFAULT_CLASSIFIER_TYPE = 3;
+ private static final String DEFAULT_MDD_MANIFEST_FILE_URL =
+ "https://dl.google.com/mdi-serving/adservices/topics_classifier/manifest_configs/2"
+ + "/manifest_config_1661376643699.binaryproto";
+
+ // Use 0 percent for random topic in the test so that we can verify the returned topic.
+ private static final int TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 0;
+ private static final int TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 5;
+
+ protected static final Context sContext = ApplicationProvider.getApplicationContext();
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+
+ // Used to get the package name. Copied over from com.android.adservices.AdServicesCommon
+ private static final String TOPICS_SERVICE_NAME = "android.adservices.TOPICS_SERVICE";
+ private static final String ADSERVICES_PACKAGE_NAME = getAdServicesPackageName();
+
+ @Before
+ public void setup() throws Exception {
+ // We need to skip 3 epochs so that if there is any usage from other test runs, it will
+ // not be used for epoch retrieval.
+ Thread.sleep(3 * TEST_EPOCH_JOB_PERIOD_MS);
+
+ overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
+ // We need to turn off random topic so that we can verify the returned topic.
+ overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+ }
+
+ @After
+ public void teardown() {
+ overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
+ overridePercentageForRandomTopic(TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+ }
+
+ @Test
+ public void testTopicsManager_downloadModelViaMdd_runOnDeviceClassifier() throws Exception {
+ // Set up test flags for on-device classification.
+ setupFlagsForOnDeviceClassifier();
+
+ // Override manifest URL for Mdd.
+ overrideMddManifestFileURL(TEST_MDD_MANIFEST_FILE_URL);
+ // Download assets via Mdd for testing.
+ triggerMddToDownload();
+ // Wait for 10 seconds for the assets to be downloaded.
+ Thread.sleep(TEST_MDD_DOWNLOAD_WAIT_TIME_MS);
+
+ // The Test App has 1 SDK: sdk1
+ // sdk1 calls the Topics API.
+ AdvertisingTopicsClient advertisingTopicsClient1 =
+ new AdvertisingTopicsClient.Builder()
+ .setContext(sContext)
+ .setSdkName("sdk1")
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+
+ // At beginning, Sdk1 receives no topic.
+ GetTopicsResponse sdk1Result = advertisingTopicsClient1.getTopics().get();
+ assertThat(sdk1Result.getTopics()).isEmpty();
+
+ // Now force the Epoch Computation Job. This should be done in the same epoch for
+ // callersCanLearnMap to have the entry for processing.
+ forceEpochComputationJob();
+
+ // Wait to the next epoch. We will not need to do this after we implement the fix in
+ // go/rb-topics-epoch-scheduling
+ Thread.sleep(TEST_EPOCH_JOB_PERIOD_MS);
+
+ // Since the sdk3 called the Topics API in the previous Epoch, it should receive some topic.
+ sdk1Result = advertisingTopicsClient1.getTopics().get();
+ assertThat(sdk1Result.getTopics()).isNotEmpty();
+
+ // We only have 5 topics classified by the on-device classifier.
+ // The app will be assigned one random topic from one of these 5 topics.
+ assertThat(sdk1Result.getTopics()).hasSize(1);
+ Topic topic = sdk1Result.getTopics().get(0);
+
+ // Top 5 classifications for empty string with v1 model are [10230, 10253, 10227, 10250,
+ // 10257]. This is
+ // computed by running the model on the device for empty string.
+ // topic is one of the 5 classification topics of the Test App.
+ List<Integer> expectedTopTopicIds = Arrays.asList(10230, 10253, 10227, 10250, 10257);
+ assertThat(topic.getTopicId()).isIn(expectedTopTopicIds);
+
+ // Verify assets are from the downloaded assets. These assets have asset version set to 0
+ // for verification.
+ // Test assets downloaded for CTS test:
+ // http://google3/wireless/android/adservices/mdd/topics_classifier/cts_test_1/
+ assertThat(topic.getModelVersion()).isAtLeast(0L);
+ assertThat(topic.getTaxonomyVersion()).isAtLeast(0L);
+
+ // Clean up test flags setup for on-device classification.
+ cleanupFlagsForOnDeviceClassifier();
+
+ // Reset Mdd manifest file url to default
+ overrideMddManifestFileURL(DEFAULT_MDD_MANIFEST_FILE_URL);
+ }
+
+ // Setup test flag values for on-device classifier.
+ private void setupFlagsForOnDeviceClassifier() {
+ // Set classifier flag to use on-device classifier.
+ overrideClassifierType(ON_DEVICE_CLASSIFIER);
+
+ // Set number of top labels returned by the on-device classifier to 5.
+ overrideClassifierNumberOfTopLabels(TEST_CLASSIFIER_NUMBER_OF_TOP_LABELS);
+ // Remove classifier threshold by setting it to 0.
+ overrideClassifierThreshold(TEST_CLASSIFIER_THRESHOLD);
+ }
+
+ // Reset test flags used for on-device classifier.
+ private void cleanupFlagsForOnDeviceClassifier() {
+ // Set classifier flag back to default.
+ overrideClassifierType(DEFAULT_CLASSIFIER_TYPE);
+
+ // Set number of top labels returned by the on-device classifier back to default.
+ overrideClassifierNumberOfTopLabels(DEFAULT_CLASSIFIER_NUMBER_OF_TOP_LABELS);
+ // Set classifier threshold back to default.
+ overrideClassifierThreshold(DEFAULT_CLASSIFIER_THRESHOLD);
+ }
+
+ // Override the flag to set manifest url for Mdd.
+ private void overrideMddManifestFileURL(String val) {
+ ShellUtils.runShellCommand(
+ "device_config put adservices mdd_topics_classifier_manifest_file_url " + val);
+ }
+
+ // Override the flag to select classifier type.
+ private void overrideClassifierType(int val) {
+ ShellUtils.runShellCommand("device_config put adservices classifier_type " + val);
+ }
+
+ // Override the flag to change the number of top labels returned by on-device classifier type.
+ private void overrideClassifierNumberOfTopLabels(int val) {
+ ShellUtils.runShellCommand(
+ "device_config put adservices classifier_number_of_top_labels " + val);
+ }
+
+ // Override the flag to change the threshold for the classifier.
+ private void overrideClassifierThreshold(float val) {
+ ShellUtils.runShellCommand("device_config put adservices classifier_threshold " + val);
+ }
+
+ // Override the Epoch Period to shorten the Epoch Length in the test.
+ private void overrideEpochPeriod(long overrideEpochPeriod) {
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.topics_epoch_job_period_ms " + overrideEpochPeriod);
+ }
+
+ // Override the Percentage For Random Topic in the test.
+ private void overridePercentageForRandomTopic(long overridePercentage) {
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.topics_percentage_for_random_topics "
+ + overridePercentage);
+ }
+
+ private void triggerMddToDownload() {
+ // Forces JobScheduler to run Mdd.
+ ShellUtils.runShellCommand(
+ "cmd jobscheduler run -f"
+ + " "
+ + ADSERVICES_PACKAGE_NAME
+ + " "
+ + MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID);
+ }
+
+ /** Forces JobScheduler to run the Epoch Computation job */
+ private void forceEpochComputationJob() {
+ ShellUtils.runShellCommand(
+ "cmd jobscheduler run -f" + " " + ADSERVICES_PACKAGE_NAME + " " + EPOCH_JOB_ID);
+ }
+
+ // Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
+ private static String getAdServicesPackageName() {
+ final Intent intent = new Intent(TOPICS_SERVICE_NAME);
+ final List<ResolveInfo> resolveInfos =
+ sContext.getPackageManager()
+ .queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ Log.e(
+ TAG,
+ "Failed to find resolveInfo for adServices service. Intent action: "
+ + TOPICS_SERVICE_NAME);
+ return null;
+ }
+
+ if (resolveInfos.size() > 1) {
+ Log.e(
+ TAG,
+ String.format(
+ "Found multiple services (%1$s) for the same intent action (%2$s)",
+ TOPICS_SERVICE_NAME, resolveInfos));
+ return null;
+ }
+
+ final ServiceInfo serviceInfo = resolveInfos.get(0).serviceInfo;
+ if (serviceInfo == null) {
+ Log.e(TAG, "Failed to find serviceInfo for adServices service");
+ return null;
+ }
+
+ return serviceInfo.packageName;
+ }
+}
diff --git a/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/PreviewApiTest.java b/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/PreviewApiTest.java
new file mode 100644
index 000000000..b3eff2dd8
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/PreviewApiTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.tests.cts.topics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.adservices.clients.topics.AdvertisingTopicsClient;
+import android.adservices.topics.GetTopicsResponse;
+import android.adservices.topics.Topic;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+@RunWith(JUnit4.class)
+public class PreviewApiTest {
+ private static final String TAG = "PreviewApiTest";
+ // The JobId of the Epoch Computation.
+ private static final int EPOCH_JOB_ID = 2;
+
+ // Override the Epoch Job Period to this value to speed up the epoch computation.
+ private static final long TEST_EPOCH_JOB_PERIOD_MS = 3_000;
+
+ // Default Epoch Period.
+ private static final long TOPICS_EPOCH_JOB_PERIOD_MS = 7 * 86_400_000; // 7 days.
+
+ // Use 0 percent for random topic in the test so that we can verify the returned topic.
+ private static final int TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 0;
+ private static final int TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 5;
+
+ protected static final Context sContext = ApplicationProvider.getApplicationContext();
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+
+ // Used to get the package name. Copied over from com.android.adservices.AdServicesCommon
+ private static final String TOPICS_SERVICE_NAME = "android.adservices.TOPICS_SERVICE";
+ private static final String ADSERVICES_PACKAGE_NAME = getAdServicesPackageName();
+
+ @Before
+ public void setup() throws Exception {
+ // We need to skip 3 epochs so that if there is any usage from other test runs, it will
+ // not be used for epoch retrieval.
+ Thread.sleep(3 * TEST_EPOCH_JOB_PERIOD_MS);
+
+ overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
+ // We need to turn off random topic so that we can verify the returned topic.
+ overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+ }
+
+ @After
+ public void teardown() {
+ overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
+ overridePercentageForRandomTopic(TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+ }
+
+ @Test
+ public void testRecordObservation() throws ExecutionException, InterruptedException {
+ // The Test app has 2 SDKs: sdk1 calls the Topics API. This will record the usage for Sdk1
+ // by default, recordObservation is true.
+ AdvertisingTopicsClient advertisingTopicsClient1 =
+ new AdvertisingTopicsClient.Builder()
+ .setContext(sContext)
+ .setSdkName("sdk1")
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+
+ // Sdk2 calls the Topics API and set the Record Observation to false. This will not record
+ // the usage for sdk2.
+ AdvertisingTopicsClient advertisingTopicsClient2 =
+ new AdvertisingTopicsClient.Builder()
+ .setContext(sContext)
+ .setSdkName("sdk2")
+ .setShouldRecordObservation(false)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+
+ // At beginning, Sdk1 receives no topic.
+ GetTopicsResponse sdk1Result = advertisingTopicsClient1.getTopics().get();
+ assertThat(sdk1Result.getTopics()).isEmpty();
+
+ // At beginning, Sdk2 receives no topic.
+ GetTopicsResponse sdk2Result = advertisingTopicsClient2.getTopics().get();
+ assertThat(sdk2Result.getTopics()).isEmpty();
+
+ // Now force the Epoch Computation Job. This should be done in the same epoch for
+ // callersCanLearnMap to have the entry for processing.
+ forceEpochComputationJob();
+
+ // Wait to the next epoch. We will not need to do this after we implement the fix in
+ // go/rb-topics-epoch-scheduling
+ Thread.sleep(TEST_EPOCH_JOB_PERIOD_MS);
+
+ // Since the sdk1 called the Topics API in the previous Epoch, it should receive some topic.
+ sdk1Result = advertisingTopicsClient1.getTopics().get();
+ assertThat(sdk1Result.getTopics()).isNotEmpty();
+
+ // We only have 1 test app which has 5 classification topics: 10147,10253,10175,10254,10333
+ // in the precomputed list.
+ // These 5 classification topics will become top 5 topics of the epoch since there is
+ // no other apps calling Topics API.
+ // The app will be assigned one random topic from one of these 5 topics.
+ assertThat(sdk1Result.getTopics()).hasSize(1);
+ Topic topic = sdk1Result.getTopics().get(0);
+
+ // topic is one of the 5 classification topics of the Test App.
+ assertThat(topic.getTopicId()).isIn(Arrays.asList(10147, 10253, 10175, 10254, 10333));
+ assertThat(topic.getModelVersion()).isAtLeast(1L);
+ assertThat(topic.getTaxonomyVersion()).isAtLeast(1L);
+
+ // Sdk2 can not get any topics in this epoch because sdk2 sets not to record
+ // observation in previous epoch.
+ sdk2Result = advertisingTopicsClient2.getTopics().get();
+ assertThat(sdk2Result.getTopics()).isEmpty();
+ }
+
+ // Override the Epoch Period to shorten the Epoch Length in the test.
+ private void overrideEpochPeriod(long overrideEpochPeriod) {
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.topics_epoch_job_period_ms " + overrideEpochPeriod);
+ }
+
+ // Override the Percentage For Random Topic in the test.
+ private void overridePercentageForRandomTopic(long overridePercentage) {
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.topics_percentage_for_random_topics "
+ + overridePercentage);
+ }
+
+ /** Forces JobScheduler to run the Epoch Computation job */
+ private void forceEpochComputationJob() {
+ ShellUtils.runShellCommand(
+ "cmd jobscheduler run -f" + " " + ADSERVICES_PACKAGE_NAME + " " + EPOCH_JOB_ID);
+ }
+
+ // Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
+ private static String getAdServicesPackageName() {
+ final Intent intent = new Intent(TOPICS_SERVICE_NAME);
+ final List<ResolveInfo> resolveInfos =
+ sContext.getPackageManager()
+ .queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ Log.e(
+ TAG,
+ "Failed to find resolveInfo for adServices service. Intent action: "
+ + TOPICS_SERVICE_NAME);
+ return null;
+ }
+
+ if (resolveInfos.size() > 1) {
+ Log.e(
+ TAG,
+ String.format(
+ "Found multiple services (%1$s) for the same intent action (%2$s)",
+ TOPICS_SERVICE_NAME, resolveInfos));
+ return null;
+ }
+
+ final ServiceInfo serviceInfo = resolveInfos.get(0).serviceInfo;
+ if (serviceInfo == null) {
+ Log.e(TAG, "Failed to find serviceInfo for adServices service");
+ return null;
+ }
+
+ return serviceInfo.packageName;
+ }
+}
diff --git a/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/TopicsManagerTest.java b/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/TopicsManagerTest.java
index 40dbe05a9..b0eaf7ddc 100644
--- a/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/TopicsManagerTest.java
+++ b/adservices/tests/cts/endtoends/topics/src/com/android/adservices/tests/cts/topics/TopicsManagerTest.java
@@ -55,6 +55,23 @@ public class TopicsManagerTest {
// Default Epoch Period.
private static final long TOPICS_EPOCH_JOB_PERIOD_MS = 7 * 86_400_000; // 7 days.
+ // Classifier test constants.
+ private static final int TEST_CLASSIFIER_NUMBER_OF_TOP_LABELS = 5;
+ // Each app is given topics with a confidence score between 0.0 to 1.0 float value. This
+ // denotes how confident are you that a particular topic t1 is related to the app x that is
+ // classified.
+ // Threshold value for classifier confidence set to 0 to allow all topics and avoid filtering.
+ private static final float TEST_CLASSIFIER_THRESHOLD = 0.0f;
+ // ON_DEVICE_CLASSIFIER
+ private static final int TEST_CLASSIFIER_TYPE = 1;
+
+ // Classifier default constants.
+ private static final int DEFAULT_CLASSIFIER_NUMBER_OF_TOP_LABELS = 3;
+ // Threshold value for classifier confidence set back to the default.
+ private static final float DEFAULT_CLASSIFIER_THRESHOLD = 0.1f;
+ // PRECOMPUTED_THEN_ON_DEVICE_CLASSIFIER
+ private static final int DEFAULT_CLASSIFIER_TYPE = 3;
+
// Use 0 percent for random topic in the test so that we can verify the returned topic.
private static final int TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 0;
private static final int TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 5;
@@ -72,16 +89,20 @@ public class TopicsManagerTest {
// not be used for epoch retrieval.
Thread.sleep(3 * TEST_EPOCH_JOB_PERIOD_MS);
- overridingBeforeTest();
+ overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
+ // We need to turn off random topic so that we can verify the returned topic.
+ overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
}
@After
public void teardown() {
- overridingAfterTest();
+ overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
+ overridePercentageForRandomTopic(TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
}
@Test
- public void testTopicsManager() throws Exception {
+ public void testTopicsManager_runDefaultClassifier() throws Exception {
+ // Default classifier uses the precomputed list first, then on-device classifier.
// The Test App has 2 SDKs: sdk1 calls the Topics API and sdk2 does not.
// Sdk1 calls the Topics API.
AdvertisingTopicsClient advertisingTopicsClient1 =
@@ -116,7 +137,7 @@ public class TopicsManagerTest {
Topic topic = sdk1Result.getTopics().get(0);
// topic is one of the 5 classification topics of the Test App.
- assertThat(topic.getTopicId()).isIn(Arrays.asList(10147,10253,10175,10254,10333));
+ assertThat(topic.getTopicId()).isIn(Arrays.asList(10147, 10253, 10175, 10254, 10333));
assertThat(topic.getModelVersion()).isAtLeast(1L);
assertThat(topic.getTaxonomyVersion()).isAtLeast(1L);
@@ -133,41 +154,78 @@ public class TopicsManagerTest {
assertThat(sdk2Result2.getTopics()).isEmpty();
}
- private void overridingBeforeTest() {
- overridingAdservicesLoggingLevel("VERBOSE");
+ @Test
+ public void testTopicsManager_runOnDeviceClassifier() throws Exception {
+ // Set classifier flag to use on-device classifier.
+ overrideClassifierType(TEST_CLASSIFIER_TYPE);
+
+ // Set number of top labels returned by the on-device classifier to 5.
+ overrideClassifierNumberOfTopLabels(TEST_CLASSIFIER_NUMBER_OF_TOP_LABELS);
+ // Remove classifier threshold by setting it to 0.
+ overrideClassifierThreshold(TEST_CLASSIFIER_THRESHOLD);
+
+ // The Test App has 1 SDK: sdk3
+ // sdk3 calls the Topics API.
+ AdvertisingTopicsClient advertisingTopicsClient3 =
+ new AdvertisingTopicsClient.Builder()
+ .setContext(sContext)
+ .setSdkName("sdk3")
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
- overrideDisableTopicsEnrollmentCheck("1");
- overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
+ // At beginning, Sdk3 receives no topic.
+ GetTopicsResponse sdk3Result = advertisingTopicsClient3.getTopics().get();
+ assertThat(sdk3Result.getTopics()).isEmpty();
- // We need to turn off random topic so that we can verify the returned topic.
- overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+ // Now force the Epoch Computation Job. This should be done in the same epoch for
+ // callersCanLearnMap to have the entry for processing.
+ forceEpochComputationJob();
- // We need to turn the Consent Manager into debug mode
- overrideConsentManagerDebugMode();
+ // Wait to the next epoch. We will not need to do this after we implement the fix in
+ // go/rb-topics-epoch-scheduling
+ Thread.sleep(TEST_EPOCH_JOB_PERIOD_MS);
+
+ // Since the sdk3 called the Topics API in the previous Epoch, it should receive some topic.
+ sdk3Result = advertisingTopicsClient3.getTopics().get();
+ assertThat(sdk3Result.getTopics()).isNotEmpty();
+
+ // We only have 5 topics classified by the on-device classifier.
+ // The app will be assigned one random topic from one of these 5 topics.
+ assertThat(sdk3Result.getTopics()).hasSize(1);
+ Topic topic = sdk3Result.getTopics().get(0);
+
+ // Top 5 classifications for empty string with v2 model are [10230, 10253, 10227, 10250,
+ // 10257]. This is computed by running the model on the device for empty string.
+ // topic is one of the 5 classification topics of the Test App.
+ List<Integer> expectedTopTopicIds = Arrays.asList(10230, 10253, 10227, 10250, 10257);
+ assertThat(topic.getTopicId()).isIn(expectedTopTopicIds);
+
+ assertThat(topic.getModelVersion()).isAtLeast(2L);
+ assertThat(topic.getTaxonomyVersion()).isAtLeast(2L);
- // Turn off MDD to avoid model mismatching
- disableMddBackgroundTasks(true);
+ // Set classifier flag back to default.
+ overrideClassifierType(DEFAULT_CLASSIFIER_TYPE);
+
+ // Set number of top labels returned by the on-device classifier back to default.
+ overrideClassifierNumberOfTopLabels(DEFAULT_CLASSIFIER_NUMBER_OF_TOP_LABELS);
+ // Set classifier threshold back to default.
+ overrideClassifierThreshold(DEFAULT_CLASSIFIER_THRESHOLD);
}
- private void overridingAfterTest() {
- overrideDisableTopicsEnrollmentCheck("0");
- overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
- overridePercentageForRandomTopic(TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
- disableMddBackgroundTasks(false);
- overridingAdservicesLoggingLevel("INFO");
+ // Override the flag to select classifier type.
+ private void overrideClassifierType(int val) {
+ ShellUtils.runShellCommand("device_config put adservices classifier_type " + val);
}
- // Switch on/off for MDD service. Default value is false, which means MDD is enabled.
- private void disableMddBackgroundTasks(boolean isSwitchedOff) {
+ // Override the flag to change the number of top labels returned by on-device classifier type.
+ private void overrideClassifierNumberOfTopLabels(int val) {
ShellUtils.runShellCommand(
- "setprop debug.adservices.mdd_background_task_kill_switch " + isSwitchedOff);
+ "device_config put adservices classifier_number_of_top_labels " + val);
}
- // Override the flag to disable Topics enrollment check.
- private void overrideDisableTopicsEnrollmentCheck(String val) {
- // Setting it to 1 here disables the Topics' enrollment check.
- ShellUtils.runShellCommand(
- "setprop debug.adservices.disable_topics_enrollment_check " + val);
+ // Override the flag to change the threshold for the classifier.
+ private void overrideClassifierThreshold(float val) {
+ ShellUtils.runShellCommand("device_config put adservices classifier_threshold " + val);
}
// Override the Epoch Period to shorten the Epoch Length in the test.
@@ -183,21 +241,12 @@ public class TopicsManagerTest {
+ overridePercentage);
}
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
- }
-
/** Forces JobScheduler to run the Epoch Computation job */
private void forceEpochComputationJob() {
ShellUtils.runShellCommand(
"cmd jobscheduler run -f" + " " + ADSERVICES_PACKAGE_NAME + " " + EPOCH_JOB_ID);
}
- private void overridingAdservicesLoggingLevel(String loggingLevel) {
- ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
- }
-
// Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
private static String getAdServicesPackageName() {
final Intent intent = new Intent(TOPICS_SERVICE_NAME);
diff --git a/adservices/tests/cts/hosttests/AndroidTest.xml b/adservices/tests/cts/hosttests/AndroidTest.xml
index f9dfed673..cc97d5d5a 100644
--- a/adservices/tests/cts/hosttests/AndroidTest.xml
+++ b/adservices/tests/cts/hosttests/AndroidTest.xml
@@ -17,7 +17,7 @@
<configuration description="Config for Adservices CTS host test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
@@ -25,6 +25,12 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAdservicesHostTestApp.apk" />
</target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="disable-device-config-sync" value="true" />
+ <option name="force-skip-system-props" value="true" />
+ </target_preparer>
+
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsAdServicesHostTests.jar" />
</test>
diff --git a/adservices/tests/cts/hosttests/app/Android.bp b/adservices/tests/cts/hosttests/app/Android.bp
index 43c09dcc6..13d80736c 100644
--- a/adservices/tests/cts/hosttests/app/Android.bp
+++ b/adservices/tests/cts/hosttests/app/Android.bp
@@ -20,6 +20,7 @@ android_test_helper_app {
name: "CtsAdservicesHostTestApp",
defaults: ["cts_defaults"],
min_sdk_version: "33",
+ target_sdk_version: "33",
srcs: [
"src/**/*.java",
],
diff --git a/adservices/tests/cts/hosttests/src/com/android/adservices/cts/TopicsApiLoggingHostTest.java b/adservices/tests/cts/hosttests/src/com/android/adservices/cts/TopicsApiLoggingHostTest.java
index 92e55da26..3972ab771 100644
--- a/adservices/tests/cts/hosttests/src/com/android/adservices/cts/TopicsApiLoggingHostTest.java
+++ b/adservices/tests/cts/hosttests/src/com/android/adservices/cts/TopicsApiLoggingHostTest.java
@@ -81,6 +81,8 @@ public class TopicsApiLoggingHostTest implements IDeviceTest {
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
+ disableGlobalKillSwitch();
+ disableTopicsAPIKillSwitch();
// We need to turn the Consent Manager into debug mode
overrideConsentManagerDebugMode();
disableMddBackgroundTasks(true);
@@ -89,6 +91,7 @@ public class TopicsApiLoggingHostTest implements IDeviceTest {
@After
public void tearDown() throws Exception {
+ disableMddBackgroundTasks(false);
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
}
@@ -154,7 +157,7 @@ public class TopicsApiLoggingHostTest implements IDeviceTest {
throws DeviceNotAvailableException {
getDevice()
.executeShellCommand(
- "setprop debug.adservices.mdd_background_task_kill_switch "
+ "device_config put adservices mdd_background_task_kill_switch "
+ isSwitchedOff);
}
@@ -166,4 +169,14 @@ public class TopicsApiLoggingHostTest implements IDeviceTest {
.executeShellCommand(
"setprop debug.adservices.disable_topics_enrollment_check " + val);
}
+
+ // Disable global_kill_switch to ignore the effect of actual PH values.
+ private void disableGlobalKillSwitch() throws DeviceNotAvailableException {
+ getDevice().executeShellCommand("device_config put adservices global_kill_switch false");
+ }
+
+ // Disable topics_kill_switch to ignore the effect of actual PH values.
+ private void disableTopicsAPIKillSwitch() throws DeviceNotAvailableException {
+ getDevice().executeShellCommand("device_config put adservices topics_kill_switch false");
+ }
}
diff --git a/adservices/tests/cts/sandbox/adid/Android.bp b/adservices/tests/cts/sandbox/adid/Android.bp
new file mode 100644
index 000000000..4cfce3feb
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/Android.bp
@@ -0,0 +1,46 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsSandboxedAdIdManagerTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "androidx.test.runner",
+ "truth-prebuilt",
+ "SdkSandboxTestUtils",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ ],
+ data: [
+ ":AdIdSdk",
+ ],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+ test_suites: [
+ "cts",
+ "mts-adservices",
+ "general-tests",
+ ],
+ test_mainline_modules: ["com.google.android.adservices.apex"],
+}
diff --git a/adservices/tests/cts/sandbox/adid/AndroidManifest.xml b/adservices/tests/cts/sandbox/adid/AndroidManifest.xml
new file mode 100644
index 000000000..e60459bcd
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.sandbox.adid" >
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ <uses-sdk-library android:name="com.android.tests.providers.adidsdk"
+ android:versionMajor="1"
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
+ <property android:name="android.adservices.AD_SERVICES_CONFIG"
+ android:resource="@xml/ad_services_config" />
+ <activity android:name=".SimpleActivity"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:turnScreenOn="true"
+ android:keepScreenOn="true"
+ android:showWhenLocked="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tests.sandbox.adid"
+ android:label="Sandboxed AdId API end to end tests"/>
+</manifest>
diff --git a/adservices/tests/cts/sandbox/adid/AndroidTest.xml b/adservices/tests/cts/sandbox/adid/AndroidTest.xml
new file mode 100644
index 000000000..1aabd25a4
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/AndroidTest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Config for CtsSandboxedAdIdManagerTests end to end tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi"/>
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="AdIdSdk.apk"/>
+ <option name="test-file-name" value="CtsSandboxedAdIdManagerTests.apk"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="hidden-api-checks" value="false"/>
+ <option name="package" value="com.android.tests.sandbox.adid"/>
+ </test>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="cmd sdk_sandbox set-state --enabled" />
+ <option name="teardown-command" value="cmd sdk_sandbox set-state --reset" />
+ </target_preparer>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController" >
+ <option name="mainline-module-package-name" value="com.google.android.adservices" />
+ </object>
+ <option name="config-descriptor:metadata" key="mainline-param"
+ value="com.google.android.adservices.apex" />
+</configuration>
diff --git a/adservices/tests/cts/sandbox/adid/providers/adidsdk/Android.bp b/adservices/tests/cts/sandbox/adid/providers/adidsdk/Android.bp
new file mode 100644
index 000000000..3c18127f8
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/providers/adidsdk/Android.bp
@@ -0,0 +1,37 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "AdIdSdk",
+ defaults: ["platform_app_defaults"],
+ certificate: ":sdksandbox-test",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.concurrent_concurrent-futures",
+ "compatibility-device-util-axt",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.base",
+ ],
+ platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
diff --git a/adservices/tests/cts/sandbox/adid/providers/adidsdk/AndroidManifest.xml b/adservices/tests/cts/sandbox/adid/providers/adidsdk/AndroidManifest.xml
new file mode 100644
index 000000000..57b0e4d21
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/providers/adidsdk/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.providers.adidsdk">
+ <!-- Permissions to access PP APIs. -->
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
+
+ <application>
+ <sdk-library android:name="com.android.tests.providers.adidsdk"
+ android:versionMajor="1" />
+ <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
+ android:value="com.android.tests.providers.adidsdk.AdIdSdk" />
+ </application>
+</manifest>
diff --git a/adservices/tests/cts/sandbox/adid/providers/adidsdk/src/com/android/tests/providers/adidsdk/AdIdSdk.java b/adservices/tests/cts/sandbox/adid/providers/adidsdk/src/com/android/tests/providers/adidsdk/AdIdSdk.java
new file mode 100644
index 000000000..83d8443c0
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/providers/adidsdk/src/com/android/tests/providers/adidsdk/AdIdSdk.java
@@ -0,0 +1,86 @@
+/*
+ * 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.tests.providers.adidsdk;
+
+import android.adservices.adid.AdId;
+import android.adservices.adid.AdIdManager;
+import android.app.sdksandbox.LoadSdkException;
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.util.Log;
+import android.view.View;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class AdIdSdk extends SandboxedSdkProvider {
+ private static final String TAG = "AdIdSdk";
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+
+ @Override
+ public SandboxedSdk onLoadSdk(Bundle params) throws LoadSdkException {
+ try {
+ AdIdManager adIdManager = getContext().getSystemService(AdIdManager.class);
+
+ CompletableFuture<AdId> future = new CompletableFuture<>();
+ OutcomeReceiver<AdId, Exception> callback =
+ new OutcomeReceiver<AdId, Exception>() {
+ @Override
+ public void onResult(AdId result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ Log.e(TAG, "SDK Runtime.testAdId onError " + error.getMessage());
+ }
+ };
+
+ adIdManager.getAdId(CALLBACK_EXECUTOR, callback);
+
+ AdId resultAdId = future.get();
+
+ if (resultAdId.getAdId() != null) {
+ // Successfully called the getAdId
+ Log.d(
+ TAG,
+ "Successfully called the getAdId. resultAdId.getAdId() = "
+ + resultAdId.getAdId());
+ return new SandboxedSdk(new Binder());
+ } else {
+ // Failed to call the getAdId
+ Log.e(TAG, "Failed to call the getAdId");
+ throw new LoadSdkException(new Exception("AdId failed."), new Bundle());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ // Throw an exception to tell the Test App that some errors occurred so
+ // that it will fail the test.
+ throw new LoadSdkException(e, new Bundle());
+ }
+ }
+
+ @Override
+ public View getView(Context windowContext, Bundle params, int width, int height) {
+ return null;
+ }
+}
diff --git a/adservices/tests/cts/sandbox/adid/res/xml/ad_services_config.xml b/adservices/tests/cts/sandbox/adid/res/xml/ad_services_config.xml
new file mode 100644
index 000000000..6df3c226e
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/res/xml/ad_services_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<ad-services-config>
+ <includes-sdk-library name="1" />
+ <attribution allowAllToAccess="false" />
+ <custom-audiences allowAllToAccess="false" />
+ <topics allowAllToAccess="true" />
+</ad-services-config>
diff --git a/adservices/tests/cts/sandbox/adid/src/com/android/tests/sandbox/adid/SandboxedAdIdManagerTest.java b/adservices/tests/cts/sandbox/adid/src/com/android/tests/sandbox/adid/SandboxedAdIdManagerTest.java
new file mode 100644
index 000000000..6820f6b78
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/src/com/android/tests/sandbox/adid/SandboxedAdIdManagerTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.tests.sandbox.adid;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.NonNull;
+import android.app.sdksandbox.SdkSandboxManager;
+import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
+
+/*
+ * Test AdId API running within the Sandbox.
+ */
+@RunWith(JUnit4.class)
+public class SandboxedAdIdManagerTest {
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+ private static final String SDK_NAME = "com.android.tests.providers.adidsdk";
+ // Used to get the package name. Copied over from com.android.adservices.AdServicesCommon
+ private static final String ADID_SERVICE_NAME = "android.adservices.ADID_SERVICE";
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Before
+ public void setup() throws TimeoutException, InterruptedException {
+ // Start a foreground activity
+ SimpleActivity.startAndWaitForSimpleActivity(sContext, Duration.ofMillis(1000));
+ overridingBeforeTest();
+ }
+
+ @After
+ public void shutDown() {
+ overridingAfterTest();
+ SimpleActivity.stopSimpleActivity(sContext);
+ }
+
+ @Test
+ public void loadSdkAndRunAdIdApi() throws Exception {
+ final SdkSandboxManager sdkSandboxManager =
+ sContext.getSystemService(SdkSandboxManager.class);
+
+ final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+
+ sdkSandboxManager.loadSdk(SDK_NAME, new Bundle(), CALLBACK_EXECUTOR, callback);
+
+ // This verifies that the adidsdk in the Sandbox gets back the correct adid.
+ // If the adidsdk did not get correct adid, it will trigger the callback.onLoadSdkError
+ // callback.isLoadSdkSuccessful returns true if there were no errors.
+ assertWithMessage(
+ callback.isLoadSdkSuccessful()
+ ? "Callback was successful"
+ : "Callback failed with message " + callback.getLoadSdkErrorMsg())
+ .that(callback.isLoadSdkSuccessful())
+ .isTrue();
+ }
+
+ private void overridingBeforeTest() {
+ overridingAdservicesLoggingLevel("VERBOSE");
+ // The setup for this test:
+ // SandboxedAdIdManagerTest is the test app. It will load the adidsdk into the Sandbox.
+ // The adidsdk (running within the Sandbox) will query AdId API and verify that the correct
+ // adid are returned.
+ // After adidsdk verifies the result, it will communicate back to the
+ // SandboxedAdIdManagerTest via the loadSdk's callback.
+ // In this test, we use the loadSdk's callback as a 2-way communications between the Test
+ // app (this class) and the Sdk running within the Sandbox process.
+
+ overridingAdservicesAdIdKillSwitch(true);
+ }
+
+ // Reset back the original values.
+ private void overridingAfterTest() {
+ overridingAdservicesLoggingLevel("INFO");
+ overridingAdservicesAdIdKillSwitch(false);
+ }
+
+ private void overridingAdservicesLoggingLevel(String loggingLevel) {
+ ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
+ }
+
+ // Override adid_kill_switch to ignore the effect of actual PH values.
+ // If shouldOverride = true, override adid_kill_switch to OFF to allow adservices
+ // If shouldOverride = false, override adid_kill_switch to meaningless value so that PhFlags
+ // will
+ // use the default value.
+ private void overridingAdservicesAdIdKillSwitch(boolean shouldOverride) {
+ String overrideString = shouldOverride ? "false" : "null";
+ ShellUtils.runShellCommand("setprop debug.adservices.adid_kill_switch " + overrideString);
+ }
+
+ // Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
+ @NonNull
+ private static String getAdServicesPackageName() {
+ final Intent intent = new Intent(ADID_SERVICE_NAME);
+ final List<ResolveInfo> resolveInfos =
+ sContext.getPackageManager()
+ .queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ String errorMsg =
+ "Failed to find resolveInfo for adServices service. Intent action: "
+ + ADID_SERVICE_NAME;
+ throw new IllegalStateException(errorMsg);
+ }
+
+ if (resolveInfos.size() > 1) {
+ String errorMsg = "Found multiple services for the same intent action. ";
+ throw new IllegalStateException(errorMsg);
+ }
+
+ final ServiceInfo serviceInfo = resolveInfos.get(0).serviceInfo;
+ if (serviceInfo == null) {
+ String errorMsg = "Failed to find serviceInfo for adServices service. ";
+ throw new IllegalStateException(errorMsg);
+ }
+
+ return serviceInfo.packageName;
+ }
+}
diff --git a/adservices/tests/cts/sandbox/adid/src/com/android/tests/sandbox/adid/SimpleActivity.java b/adservices/tests/cts/sandbox/adid/src/com/android/tests/sandbox/adid/SimpleActivity.java
new file mode 100644
index 000000000..c10b2e505
--- /dev/null
+++ b/adservices/tests/cts/sandbox/adid/src/com/android/tests/sandbox/adid/SimpleActivity.java
@@ -0,0 +1,166 @@
+/*
+ * 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.tests.sandbox.adid;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import java.time.Duration;
+import java.util.concurrent.TimeoutException;
+
+/** A simple activity to launch to make the CTS be considered a foreground app by PPAPI. */
+public class SimpleActivity extends Activity {
+ private static final String EXTRA_FINISH_FLAG = "finish";
+ private static final String ACTION_SIMPLE_ACTIVITY_START_RESULT =
+ "com.android.tests.sandbox.adid.SimpleActivity.RESULT";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ Intent reply = new Intent(ACTION_SIMPLE_ACTIVITY_START_RESULT);
+ reply.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ sendBroadcast(reply);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ if (intent.getExtras().getBoolean(EXTRA_FINISH_FLAG)) {
+ finish();
+ }
+ }
+
+ /** @return an intent targeting {@link SimpleActivity} */
+ public static Intent getSimpleActivityIntent() {
+ return new Intent(Intent.ACTION_MAIN)
+ .setClassName(SimpleActivity.class.getPackageName(), SimpleActivity.class.getName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ /** Starts a {@link SimpleActivity}. Doesn't wait fore activity to be started */
+ public static void startSimpleActivity(Context targetContext) {
+ targetContext.startActivity(getSimpleActivityIntent());
+ }
+
+ /**
+ * Stops a single activity, doesn't wait for the activity to stop or check if the activity was
+ * actually running.
+ */
+ public static void stopSimpleActivity(Context targetContext) {
+ targetContext.startActivity(
+ getSimpleActivityIntent()
+ .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ .putExtra(EXTRA_FINISH_FLAG, true));
+ }
+
+ /**
+ * Starts a {@link SimpleActivity} and wait for the activity to be started the specified max
+ * waiting time.
+ *
+ * @param targetContext the context to start the activity in
+ * @param maxWaitTime the max waiting time
+ * @throws TimeoutException if the activity didn't start within timeout
+ */
+ public static void startAndWaitForSimpleActivity(Context targetContext, Duration maxWaitTime)
+ throws TimeoutException {
+ try (WaitForBroadcast waiter = new WaitForBroadcast(targetContext)) {
+ waiter.prepare(ACTION_SIMPLE_ACTIVITY_START_RESULT);
+ startSimpleActivity(targetContext);
+ waiter.doWait(maxWaitTime.toMillis());
+ }
+ }
+
+ /** See {@code android.app.cts.android.app.cts.tools.WaitForBroadcast} */
+ private static class WaitForBroadcast implements AutoCloseable {
+ @NonNull private final Context mContext;
+
+ String mWaitingAction;
+ boolean mHasResult;
+ Intent mReceivedIntent;
+ private final Object mWaitMonitor = new Object();
+
+ final BroadcastReceiver mReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mWaitMonitor) {
+ mReceivedIntent = intent;
+ mHasResult = true;
+ mWaitMonitor.notifyAll();
+ }
+ }
+ };
+
+ WaitForBroadcast(@NonNull Context context) {
+ mContext = context;
+ }
+
+ public void prepare(String action) {
+ if (mWaitingAction != null) {
+ throw new IllegalStateException("Already prepared");
+ }
+ mWaitingAction = action;
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(action);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ public Intent doWait(long timeoutMillis) throws TimeoutException {
+ final long endTime = SystemClock.uptimeMillis() + timeoutMillis;
+
+ synchronized (this) {
+ while (!mHasResult) {
+ final long now = SystemClock.uptimeMillis();
+ if (now >= endTime) {
+ String action = mWaitingAction;
+ throw new TimeoutException("Timed out waiting for broadcast " + action);
+ }
+ try {
+ wait(endTime - now);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ return mReceivedIntent;
+ }
+ }
+
+ @Override
+ public void close() {
+ if (mWaitingAction != null) {
+ mContext.unregisterReceiver(mReceiver);
+ mWaitingAction = null;
+ }
+ }
+ }
+}
diff --git a/adservices/tests/cts/sandbox/appsetid/Android.bp b/adservices/tests/cts/sandbox/appsetid/Android.bp
new file mode 100644
index 000000000..d3a871fe1
--- /dev/null
+++ b/adservices/tests/cts/sandbox/appsetid/Android.bp
@@ -0,0 +1,46 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsSandboxedAppSetIdManagerTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "androidx.test.runner",
+ "truth-prebuilt",
+ "SdkSandboxTestUtils",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ ],
+ data: [
+ ":AppSetIdSdk",
+ ],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+ test_suites: [
+ "cts",
+ "mts-adservices",
+ "general-tests",
+ ],
+ test_mainline_modules: ["com.google.android.adservices.apex"],
+}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_WebViewClient.xml b/adservices/tests/cts/sandbox/appsetid/AndroidManifest.xml
index a9810df2d..c77e0b490 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_WebViewClient.xml
+++ b/adservices/tests/cts/sandbox/appsetid/AndroidManifest.xml
@@ -14,23 +14,34 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.sdksandboxclient" >
+ package="com.android.tests.sandbox.appsetid" >
+
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <application
- android:label="@string/title_activity_main">
- <activity
- android:name="com.android.sdksandboxclient.MainActivity"
- android:exported="true">
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ <uses-sdk-library android:name="com.android.tests.providers.appsetidsdk"
+ android:versionMajor="1"
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
+ <property android:name="android.adservices.AD_SERVICES_CONFIG"
+ android:resource="@xml/ad_services_config" />
+ <activity android:name=".SimpleActivity"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:turnScreenOn="true"
+ android:keepScreenOn="true"
+ android:showWhenLocked="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <uses-sdk-library android:name="com.android.sdksandboxcode"
- android:versionMajor="1"
- android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
</application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tests.sandbox.appsetid"
+ android:label="Sandboxed AppSetId API end to end tests"/>
</manifest>
diff --git a/adservices/tests/cts/sandbox/appsetid/AndroidTest.xml b/adservices/tests/cts/sandbox/appsetid/AndroidTest.xml
new file mode 100644
index 000000000..041db7b90
--- /dev/null
+++ b/adservices/tests/cts/sandbox/appsetid/AndroidTest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Config for CtsSandboxedAppSetIdManagerTests end to end tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi"/>
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="AppSetIdSdk.apk"/>
+ <option name="test-file-name" value="CtsSandboxedAppSetIdManagerTests.apk"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="hidden-api-checks" value="false"/>
+ <option name="package" value="com.android.tests.sandbox.appsetid"/>
+ </test>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="cmd sdk_sandbox set-state --enabled" />
+ <option name="teardown-command" value="cmd sdk_sandbox set-state --reset" />
+ </target_preparer>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController" >
+ <option name="mainline-module-package-name" value="com.google.android.adservices" />
+ </object>
+ <option name="config-descriptor:metadata" key="mainline-param"
+ value="com.google.android.adservices.apex" />
+</configuration>
diff --git a/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/Android.bp b/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/Android.bp
new file mode 100644
index 000000000..d33339900
--- /dev/null
+++ b/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/Android.bp
@@ -0,0 +1,37 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "AppSetIdSdk",
+ defaults: ["platform_app_defaults"],
+ certificate: ":sdksandbox-test",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.concurrent_concurrent-futures",
+ "compatibility-device-util-axt",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.base",
+ ],
+ platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
diff --git a/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/AndroidManifest.xml b/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/AndroidManifest.xml
new file mode 100644
index 000000000..9bd203dcd
--- /dev/null
+++ b/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.providers.appsetidsdk">
+
+ <application>
+ <sdk-library android:name="com.android.tests.providers.appsetidsdk"
+ android:versionMajor="1" />
+ <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
+ android:value="com.android.tests.providers.appsetidsdk.AppSetIdSdk" />
+ </application>
+</manifest>
diff --git a/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/src/com/android/tests/providers/appsetidsdk/AppSetIdSdk.java b/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/src/com/android/tests/providers/appsetidsdk/AppSetIdSdk.java
new file mode 100644
index 000000000..5bc52b87d
--- /dev/null
+++ b/adservices/tests/cts/sandbox/appsetid/providers/appsetidsdk/src/com/android/tests/providers/appsetidsdk/AppSetIdSdk.java
@@ -0,0 +1,86 @@
+/*
+ * 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.tests.providers.appsetidsdk;
+
+import android.adservices.appsetid.AppSetId;
+import android.adservices.appsetid.AppSetIdManager;
+import android.app.sdksandbox.LoadSdkException;
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.util.Log;
+import android.view.View;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class AppSetIdSdk extends SandboxedSdkProvider {
+ private static final String TAG = "AppSetIdSdk";
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+
+ @Override
+ public SandboxedSdk onLoadSdk(Bundle params) throws LoadSdkException {
+ try {
+ AppSetIdManager appSetIdManager = getContext().getSystemService(AppSetIdManager.class);
+
+ CompletableFuture<AppSetId> future = new CompletableFuture<>();
+ OutcomeReceiver<AppSetId, Exception> callback =
+ new OutcomeReceiver<AppSetId, Exception>() {
+ @Override
+ public void onResult(AppSetId result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ Log.e(TAG, "SDK Runtime.testAppSetId onError " + error.getMessage());
+ }
+ };
+
+ appSetIdManager.getAppSetId(CALLBACK_EXECUTOR, callback);
+
+ AppSetId resultAppSetId = future.get();
+
+ if (resultAppSetId.getId() != null) {
+ // Successfully called the getAppSetId
+ Log.d(
+ TAG,
+ "Successfully called the getAppSetId. resultAppSetId.getId() = "
+ + resultAppSetId.getId());
+ return new SandboxedSdk(new Binder());
+ } else {
+ // Failed to call the getAppSetId
+ Log.e(TAG, "Failed to call the getAppSetId");
+ throw new LoadSdkException(new Exception("AppSetId failed."), new Bundle());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ // Throw an exception to tell the Test App that some errors occurred so
+ // that it will fail the test.
+ throw new LoadSdkException(e, new Bundle());
+ }
+ }
+
+ @Override
+ public View getView(Context windowContext, Bundle params, int width, int height) {
+ return null;
+ }
+}
diff --git a/adservices/tests/cts/sandbox/appsetid/res/xml/ad_services_config.xml b/adservices/tests/cts/sandbox/appsetid/res/xml/ad_services_config.xml
new file mode 100644
index 000000000..6df3c226e
--- /dev/null
+++ b/adservices/tests/cts/sandbox/appsetid/res/xml/ad_services_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<ad-services-config>
+ <includes-sdk-library name="1" />
+ <attribution allowAllToAccess="false" />
+ <custom-audiences allowAllToAccess="false" />
+ <topics allowAllToAccess="true" />
+</ad-services-config>
diff --git a/adservices/tests/cts/sandbox/appsetid/src/com/android/tests/sandbox/appsetid/SandboxedAppSetIdManagerTest.java b/adservices/tests/cts/sandbox/appsetid/src/com/android/tests/sandbox/appsetid/SandboxedAppSetIdManagerTest.java
new file mode 100644
index 000000000..c662a0c36
--- /dev/null
+++ b/adservices/tests/cts/sandbox/appsetid/src/com/android/tests/sandbox/appsetid/SandboxedAppSetIdManagerTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.tests.sandbox.appsetid;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.NonNull;
+import android.app.sdksandbox.SdkSandboxManager;
+import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
+
+/*
+ * Test AppSetId API running within the Sandbox.
+ */
+@RunWith(JUnit4.class)
+public class SandboxedAppSetIdManagerTest {
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+ private static final String SDK_NAME = "com.android.tests.providers.appsetidsdk";
+ // Used to get the package name. Copied over from com.android.adservices.AdServicesCommon
+ private static final String APPSETID_SERVICE_NAME = "android.adservices.APPSETID_SERVICE";
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Before
+ public void setup() throws TimeoutException, InterruptedException {
+ // Start a foreground activity
+ SimpleActivity.startAndWaitForSimpleActivity(sContext, Duration.ofMillis(1000));
+ overridingBeforeTest();
+ }
+
+ @After
+ public void shutDown() {
+ overridingAfterTest();
+ SimpleActivity.stopSimpleActivity(sContext);
+ }
+
+ @Test
+ public void loadSdkAndRunAppSetIdApi() throws Exception {
+ final SdkSandboxManager sdkSandboxManager =
+ sContext.getSystemService(SdkSandboxManager.class);
+
+ final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+
+ sdkSandboxManager.loadSdk(SDK_NAME, new Bundle(), CALLBACK_EXECUTOR, callback);
+
+ // This verifies that the appsetidsdk in the Sandbox gets back the correct appsetid.
+ // If the appsetidsdk did not get correct appsetid, it will trigger the
+ // callback.onLoadSdkError
+ // callback.isLoadSdkSuccessful returns true if there were no errors.
+ assertWithMessage(
+ callback.isLoadSdkSuccessful()
+ ? "Callback was successful"
+ : "Callback failed with message " + callback.getLoadSdkErrorMsg())
+ .that(callback.isLoadSdkSuccessful())
+ .isTrue();
+ }
+
+ private void overridingBeforeTest() {
+ overridingAdservicesLoggingLevel("VERBOSE");
+ // The setup for this test:
+ // SandboxedAppSetIdManagerTest is the test app. It will load the appsetidsdk into the
+ // Sandbox.
+ // The appsetidsdk (running within the Sandbox) will query AppSetId API and verify that the
+ // correct
+ // appsetid are returned.
+ // After appsetidsdk verifies the result, it will communicate back to the
+ // SandboxedAppSetIdManagerTest via the loadSdk's callback.
+ // In this test, we use the loadSdk's callback as a 2-way communications between the Test
+ // app (this class) and the Sdk running within the Sandbox process.
+
+ overrideAdservicesAppSetIdKillSwitch(true);
+ }
+
+ // Reset back the original values.
+ private void overridingAfterTest() {
+ overridingAdservicesLoggingLevel("INFO");
+ overrideAdservicesAppSetIdKillSwitch(false);
+ }
+
+ private void overridingAdservicesLoggingLevel(String loggingLevel) {
+ ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
+ }
+
+ // Override appsetid_kill_switch to ignore the effect of actual PH values.
+ // If shouldOverride = true, override appsetid_kill_switch to OFF to allow adservices
+ // If shouldOverride = false, override appsetid_kill_switch to meaningless value so that PhFlags
+ // will
+ // use the default value.
+ private void overrideAdservicesAppSetIdKillSwitch(boolean shouldOverride) {
+ String overrideString = shouldOverride ? "false" : "null";
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.appsetid_kill_switch " + overrideString);
+ }
+
+ // Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
+ @NonNull
+ private static String getAdServicesPackageName() {
+ final Intent intent = new Intent(APPSETID_SERVICE_NAME);
+ final List<ResolveInfo> resolveInfos =
+ sContext.getPackageManager()
+ .queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ String errorMsg =
+ "Failed to find resolveInfo for adServices service. Intent action: "
+ + APPSETID_SERVICE_NAME;
+ throw new IllegalStateException(errorMsg);
+ }
+
+ if (resolveInfos.size() > 1) {
+ String errorMsg = "Found multiple services for the same intent action. ";
+ throw new IllegalStateException(errorMsg);
+ }
+
+ final ServiceInfo serviceInfo = resolveInfos.get(0).serviceInfo;
+ if (serviceInfo == null) {
+ String errorMsg = "Failed to find serviceInfo for adServices service. ";
+ throw new IllegalStateException(errorMsg);
+ }
+
+ return serviceInfo.packageName;
+ }
+}
diff --git a/adservices/tests/cts/sandbox/appsetid/src/com/android/tests/sandbox/appsetid/SimpleActivity.java b/adservices/tests/cts/sandbox/appsetid/src/com/android/tests/sandbox/appsetid/SimpleActivity.java
new file mode 100644
index 000000000..8e7223dcd
--- /dev/null
+++ b/adservices/tests/cts/sandbox/appsetid/src/com/android/tests/sandbox/appsetid/SimpleActivity.java
@@ -0,0 +1,166 @@
+/*
+ * 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.tests.sandbox.appsetid;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import java.time.Duration;
+import java.util.concurrent.TimeoutException;
+
+/** A simple activity to launch to make the CTS be considered a foreground app by PPAPI. */
+public class SimpleActivity extends Activity {
+ private static final String EXTRA_FINISH_FLAG = "finish";
+ private static final String ACTION_SIMPLE_ACTIVITY_START_RESULT =
+ "com.android.tests.sandbox.appsetid.SimpleActivity.RESULT";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ Intent reply = new Intent(ACTION_SIMPLE_ACTIVITY_START_RESULT);
+ reply.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ sendBroadcast(reply);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ if (intent.getExtras().getBoolean(EXTRA_FINISH_FLAG)) {
+ finish();
+ }
+ }
+
+ /** @return an intent targeting {@link SimpleActivity} */
+ public static Intent getSimpleActivityIntent() {
+ return new Intent(Intent.ACTION_MAIN)
+ .setClassName(SimpleActivity.class.getPackageName(), SimpleActivity.class.getName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ /** Starts a {@link SimpleActivity}. Doesn't wait fore activity to be started */
+ public static void startSimpleActivity(Context targetContext) {
+ targetContext.startActivity(getSimpleActivityIntent());
+ }
+
+ /**
+ * Stops a single activity, doesn't wait for the activity to stop or check if the activity was
+ * actually running.
+ */
+ public static void stopSimpleActivity(Context targetContext) {
+ targetContext.startActivity(
+ getSimpleActivityIntent()
+ .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ .putExtra(EXTRA_FINISH_FLAG, true));
+ }
+
+ /**
+ * Starts a {@link SimpleActivity} and wait for the activity to be started the specified max
+ * waiting time.
+ *
+ * @param targetContext the context to start the activity in
+ * @param maxWaitTime the max waiting time
+ * @throws TimeoutException if the activity didn't start within timeout
+ */
+ public static void startAndWaitForSimpleActivity(Context targetContext, Duration maxWaitTime)
+ throws TimeoutException {
+ try (WaitForBroadcast waiter = new WaitForBroadcast(targetContext)) {
+ waiter.prepare(ACTION_SIMPLE_ACTIVITY_START_RESULT);
+ startSimpleActivity(targetContext);
+ waiter.doWait(maxWaitTime.toMillis());
+ }
+ }
+
+ /** See {@code android.app.cts.android.app.cts.tools.WaitForBroadcast} */
+ private static class WaitForBroadcast implements AutoCloseable {
+ @NonNull private final Context mContext;
+
+ String mWaitingAction;
+ boolean mHasResult;
+ Intent mReceivedIntent;
+ private final Object mWaitMonitor = new Object();
+
+ final BroadcastReceiver mReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mWaitMonitor) {
+ mReceivedIntent = intent;
+ mHasResult = true;
+ mWaitMonitor.notifyAll();
+ }
+ }
+ };
+
+ WaitForBroadcast(@NonNull Context context) {
+ mContext = context;
+ }
+
+ public void prepare(String action) {
+ if (mWaitingAction != null) {
+ throw new IllegalStateException("Already prepared");
+ }
+ mWaitingAction = action;
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(action);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ public Intent doWait(long timeoutMillis) throws TimeoutException {
+ final long endTime = SystemClock.uptimeMillis() + timeoutMillis;
+
+ synchronized (this) {
+ while (!mHasResult) {
+ final long now = SystemClock.uptimeMillis();
+ if (now >= endTime) {
+ String action = mWaitingAction;
+ throw new TimeoutException("Timed out waiting for broadcast " + action);
+ }
+ try {
+ wait(endTime - now);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ return mReceivedIntent;
+ }
+ }
+
+ @Override
+ public void close() {
+ if (mWaitingAction != null) {
+ mContext.unregisterReceiver(mReceiver);
+ mWaitingAction = null;
+ }
+ }
+ }
+}
diff --git a/adservices/tests/cts/sandbox/fledge/Android.bp b/adservices/tests/cts/sandbox/fledge/Android.bp
index 6432343a0..10b14c9f4 100644
--- a/adservices/tests/cts/sandbox/fledge/Android.bp
+++ b/adservices/tests/cts/sandbox/fledge/Android.bp
@@ -39,7 +39,7 @@ android_test {
":SdkFledge",
],
min_sdk_version: "33",
- target_sdk_version: "current",
+ target_sdk_version: "33",
test_suites: [
"cts",
"mts-adservices",
diff --git a/adservices/tests/cts/sandbox/fledge/AndroidTest.xml b/adservices/tests/cts/sandbox/fledge/AndroidTest.xml
index 12b4424e9..0308bfa70 100644
--- a/adservices/tests/cts/sandbox/fledge/AndroidTest.xml
+++ b/adservices/tests/cts/sandbox/fledge/AndroidTest.xml
@@ -31,6 +31,25 @@
<option name="package" value="com.android.tests.sandbox.fledge"/>
</test>
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global_kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="run-command" value="setprop debug.adservices.sdk_request_permits_per_second 3" />
+ <!-- Disable SDK sandbox killswitch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="cmd sdk_sandbox set-state --enabled" />
+
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+ <option name="teardown-command" value="setprop debug.adservices.sdk_request_permits_per_second 1" />
+ <option name="teardown-command" value="cmd sdk_sandbox set-state --reset" />
+ </target_preparer>
+
<object type="module_controller"
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController" >
<option name="mainline-module-package-name" value="com.google.android.adservices" />
diff --git a/adservices/tests/cts/sandbox/fledge/providers/sdkFledge/Android.bp b/adservices/tests/cts/sandbox/fledge/providers/sdkFledge/Android.bp
index 30fe1bc43..750bf9124 100644
--- a/adservices/tests/cts/sandbox/fledge/providers/sdkFledge/Android.bp
+++ b/adservices/tests/cts/sandbox/fledge/providers/sdkFledge/Android.bp
@@ -34,4 +34,6 @@ android_test_helper_app {
"android.test.base",
],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/adservices/tests/cts/sandbox/fledge/providers/sdkFledge/src/com/android/tests/providers/sdkfledge/SdkFledge.java b/adservices/tests/cts/sandbox/fledge/providers/sdkFledge/src/com/android/tests/providers/sdkfledge/SdkFledge.java
index 49e05abab..9ffc8faf8 100644
--- a/adservices/tests/cts/sandbox/fledge/providers/sdkFledge/src/com/android/tests/providers/sdkfledge/SdkFledge.java
+++ b/adservices/tests/cts/sandbox/fledge/providers/sdkFledge/src/com/android/tests/providers/sdkfledge/SdkFledge.java
@@ -136,9 +136,9 @@ public class SdkFledge extends SandboxedSdkProvider {
+ "' } };\n"
+ "}";
- String biddingLogicJs =
+ String biddingLogicJsBuyer1 =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
@@ -149,6 +149,19 @@ public class SdkFledge extends SandboxedSdkProvider {
+ "' } };\n"
+ "}";
+ String biddingLogicJsBuyer2 =
+ "function generateBid(ad, auction_signals, per_buyer_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ + " custom_audience_signals) { \n"
+ + " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ + "}\n"
+ + "function reportWin(ad_selection_signals, per_buyer_signals,"
+ + " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ + " return {'status': 0, 'results': {'reporting_uri': '"
+ + getUri(BUYER_2.toString(), BUYER_REPORTING_PATH).toString()
+ + "' } };\n"
+ + "}";
+
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -186,14 +199,14 @@ public class SdkFledge extends SandboxedSdkProvider {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(biddingLogicJs)
+ .setBiddingLogicJs(biddingLogicJsBuyer1)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(biddingLogicJs)
+ .setBiddingLogicJs(biddingLogicJsBuyer2)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
diff --git a/adservices/tests/cts/sandbox/fledge/src/com/android/tests/sandbox/fledge/SandboxedFledgeManagerTest.java b/adservices/tests/cts/sandbox/fledge/src/com/android/tests/sandbox/fledge/SandboxedFledgeManagerTest.java
index 8f561600a..5f043ccf1 100644
--- a/adservices/tests/cts/sandbox/fledge/src/com/android/tests/sandbox/fledge/SandboxedFledgeManagerTest.java
+++ b/adservices/tests/cts/sandbox/fledge/src/com/android/tests/sandbox/fledge/SandboxedFledgeManagerTest.java
@@ -30,7 +30,6 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.adservices.service.PhFlagsFixture;
import com.android.adservices.service.devapi.DevContext;
import com.android.adservices.service.devapi.DevContextFilter;
-import com.android.compatibility.common.util.ShellUtils;
import org.junit.After;
import org.junit.Assume;
@@ -75,10 +74,8 @@ public class SandboxedFledgeManagerTest {
PhFlagsFixture.overrideEnforceIsolateMaxHeapSize(false);
PhFlagsFixture.overrideIsolateMaxHeapSizeBytes(0);
- PhFlagsFixture.overrideSdkRequestPermitsPerSecond(Integer.MAX_VALUE);
makeTestProcessForeground();
PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
- overrideConsentManagerDebugMode(true);
}
/**
@@ -92,7 +89,6 @@ public class SandboxedFledgeManagerTest {
@After
public void shutDown() {
SimpleActivity.stopSimpleActivity(sContext);
- overrideConsentManagerDebugMode(false);
}
@Test
@@ -113,9 +109,4 @@ public class SandboxedFledgeManagerTest {
.that(callback.isLoadSdkSuccessful())
.isTrue();
}
-
- private void overrideConsentManagerDebugMode(boolean enable) {
- ShellUtils.runShellCommand(
- "setprop debug.adservices.consent_manager_debug_mode %s", enable);
- }
}
diff --git a/adservices/tests/cts/sandbox/measurement/Android.bp b/adservices/tests/cts/sandbox/measurement/Android.bp
index a6017ce0c..a99c4688a 100644
--- a/adservices/tests/cts/sandbox/measurement/Android.bp
+++ b/adservices/tests/cts/sandbox/measurement/Android.bp
@@ -37,7 +37,7 @@ android_test {
":SdkMeasurement",
],
min_sdk_version: "33",
- target_sdk_version: "current",
+ target_sdk_version: "33",
test_suites: [
"cts",
"mts-adservices",
diff --git a/adservices/tests/cts/sandbox/measurement/AndroidTest.xml b/adservices/tests/cts/sandbox/measurement/AndroidTest.xml
index 0d0c483d6..a1fcbd629 100644
--- a/adservices/tests/cts/sandbox/measurement/AndroidTest.xml
+++ b/adservices/tests/cts/sandbox/measurement/AndroidTest.xml
@@ -31,6 +31,23 @@
<option name="package" value="com.android.tests.sandbox.measurement"/>
</test>
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global_kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <!-- Disable SDK sandbox killswitch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="cmd sdk_sandbox set-state --enabled" />
+
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+ <option name="teardown-command" value="cmd sdk_sandbox set-state --reset" />
+ </target_preparer>
+
<object type="module_controller"
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController" >
<option name="mainline-module-package-name" value="com.google.android.adservices" />
diff --git a/adservices/tests/cts/sandbox/measurement/src/com/android/tests/sandbox/measurement/SandboxedMeasurementManagerTest.java b/adservices/tests/cts/sandbox/measurement/src/com/android/tests/sandbox/measurement/SandboxedMeasurementManagerTest.java
index 6b6f5adfb..2c0dac3bd 100644
--- a/adservices/tests/cts/sandbox/measurement/src/com/android/tests/sandbox/measurement/SandboxedMeasurementManagerTest.java
+++ b/adservices/tests/cts/sandbox/measurement/src/com/android/tests/sandbox/measurement/SandboxedMeasurementManagerTest.java
@@ -54,13 +54,12 @@ public class SandboxedMeasurementManagerTest {
// Start a foreground activity
SimpleActivity.startAndWaitForSimpleActivity(sContext, Duration.ofMillis(1000));
- // We need to turn the Consent Manager into debug mode to simulate grant Consent
- overrideConsentManagerDebugMode();
-
enforceMeasurementEnrollmentCheck(true);
// Allow sandbox package name to be able to execute Measurement APIs
allowSandboxPackageNameAccessMeasurementApis();
+
+ overrideMeasurementKillSwitches(true);
}
@After
@@ -69,7 +68,8 @@ public class SandboxedMeasurementManagerTest {
// Reset back the original values.
resetAllowSandboxPackageNameAccessMeasurementApis();
- resetOverrideConsentManagerDebugMode();
+
+ overrideMeasurementKillSwitches(false);
}
@Test
@@ -116,16 +116,36 @@ public class SandboxedMeasurementManagerTest {
"device_config put adservices web_context_client_allow_list " + sdkSbxName);
}
- private void overrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
- }
-
private void resetAllowSandboxPackageNameAccessMeasurementApis() {
ShellUtils.runShellCommand(
"device_config put adservices web_context_client_allow_list null");
}
- private void resetOverrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode null");
+ // Override measurement related kill switch to ignore the effect of actual PH values.
+ // If isOverride = true, override measurement related kill switch to OFF to allow adservices
+ // If isOverride = false, override measurement related kill switch to meaningless value so that
+ // PhFlags will use the default value.
+ private void overrideMeasurementKillSwitches(boolean isOverride) {
+ String overrideString = isOverride ? "false" : "null";
+ ShellUtils.runShellCommand("setprop debug.adservices.global_kill_switch " + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_kill_switch " + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_source_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_trigger_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_web_source_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_register_web_trigger_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_delete_registrations_kill_switch "
+ + overrideString);
+ ShellUtils.runShellCommand(
+ "setprop debug.adservices.measurement_api_status_kill_switch " + overrideString);
}
}
diff --git a/adservices/tests/cts/sandbox/topics/Android.bp b/adservices/tests/cts/sandbox/topics/Android.bp
index d1b3b1c58..646043ede 100644
--- a/adservices/tests/cts/sandbox/topics/Android.bp
+++ b/adservices/tests/cts/sandbox/topics/Android.bp
@@ -37,7 +37,7 @@ android_test {
":Sdk1",
],
min_sdk_version: "33",
- target_sdk_version: "current",
+ target_sdk_version: "33",
test_suites: [
"cts",
"mts-adservices",
diff --git a/adservices/tests/cts/sandbox/topics/AndroidTest.xml b/adservices/tests/cts/sandbox/topics/AndroidTest.xml
index 240224d58..8a7bf1a40 100644
--- a/adservices/tests/cts/sandbox/topics/AndroidTest.xml
+++ b/adservices/tests/cts/sandbox/topics/AndroidTest.xml
@@ -26,6 +26,31 @@
<option name="test-file-name" value="CtsSandboxedTopicsManagerTests.apk"/>
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup" >
+ <option name="force-skip-system-props" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global/topics kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ <option name="run-command" value="device_config put adservices topics_kill_switch false" />
+ <!-- Disable MDD background jobs. -->
+ <option name="run-command" value="device_config put adservices mdd_background_task_kill_switch true" />
+ <!-- Override Consent Manager to debug mode to grant user consent -->
+ <option name="run-command" value="setprop debug.adservices.consent_manager_debug_mode true" />
+ <option name="run-command" value="setprop log.tag.adservices VERBOSE" />
+ <!-- Override the flag to disable Topics enrollment check. -->
+ <option name="run-command" value="setprop debug.adservices.disable_topics_enrollment_check true" />
+ <!-- Disable SDK sandbox killswitch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="cmd sdk_sandbox set-state --enabled" />
+
+ <option name="teardown-command" value="setprop debug.adservices.consent_manager_debug_mode false"/>
+ <option name="teardown-command" value="setprop debug.adservices.disable_topics_enrollment_check false" />
+ <option name="teardown-command" value="device_config put adservices mdd_background_task_kill_switch false" />
+ <option name="teardown-command" value="cmd sdk_sandbox set-state --reset" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="hidden-api-checks" value="false"/>
<option name="package" value="com.android.tests.sandbox.topics"/>
diff --git a/adservices/tests/cts/sandbox/topics/providers/sdk1/Android.bp b/adservices/tests/cts/sandbox/topics/providers/sdk1/Android.bp
index a0a582576..d8cb2f054 100644
--- a/adservices/tests/cts/sandbox/topics/providers/sdk1/Android.bp
+++ b/adservices/tests/cts/sandbox/topics/providers/sdk1/Android.bp
@@ -33,4 +33,6 @@ android_test_helper_app {
"android.test.base",
],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/adservices/tests/cts/sandbox/topics/src/com/android/tests/sandbox/topics/SandboxedTopicsManagerTest.java b/adservices/tests/cts/sandbox/topics/src/com/android/tests/sandbox/topics/SandboxedTopicsManagerTest.java
index 87abae99e..0e63519ff 100644
--- a/adservices/tests/cts/sandbox/topics/src/com/android/tests/sandbox/topics/SandboxedTopicsManagerTest.java
+++ b/adservices/tests/cts/sandbox/topics/src/com/android/tests/sandbox/topics/SandboxedTopicsManagerTest.java
@@ -80,12 +80,27 @@ public class SandboxedTopicsManagerTest {
// Start a foreground activity
SimpleActivity.startAndWaitForSimpleActivity(sContext, Duration.ofMillis(1000));
- overridingBeforeTest();
+
+ // The setup for this test:
+ // SandboxedTopicsManagerTest is the test app. It will load the Sdk1 into the Sandbox.
+ // The Sdk1 (running within the Sandbox) will query Topics API and verify that the correct
+ // Topics are returned.
+ // After Sdk1 verifies the result, it will communicate back to the
+ // SandboxedTopicsManagerTest via the loadSdk's callback.
+ // In this test, we use the loadSdk's callback as a 2-way communications between the Test
+ // app (this class) and the Sdk running within the Sandbox process.
+
+ overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
+
+ // We need to turn off random topic so that we can verify the returned topic.
+ overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
}
@After
public void shutDown() {
- overridingAfterTest();
+ overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
+ overridePercentageForRandomTopic(TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+
SimpleActivity.stopSimpleActivity(sContext);
}
@@ -126,56 +141,12 @@ public class SandboxedTopicsManagerTest {
assertThat(callback.isLoadSdkSuccessful()).isTrue();
}
- private void overridingBeforeTest() {
- overridingAdservicesLoggingLevel("VERBOSE");
- // Turn off MDD to avoid model mismatching
- disableMddBackgroundTasks(true);
- overrideDisableTopicsEnrollmentCheck("1");
- // The setup for this test:
- // SandboxedTopicsManagerTest is the test app. It will load the Sdk1 into the Sandbox.
- // The Sdk1 (running within the Sandbox) will query Topics API and verify that the correct
- // Topics are returned.
- // After Sdk1 verifies the result, it will communicate back to the
- // SandboxedTopicsManagerTest via the loadSdk's callback.
- // In this test, we use the loadSdk's callback as a 2-way communications between the Test
- // app (this class) and the Sdk running within the Sandbox process.
-
- // We need to turn the Consent Manager into debug mode
- overrideConsentManagerDebugMode();
-
- overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
-
- // We need to turn off random topic so that we can verify the returned topic.
- overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
- }
-
- // Reset back the original values.
- private void overridingAfterTest() {
- overrideDisableTopicsEnrollmentCheck("0");
- overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
- overridePercentageForRandomTopic(TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
- disableMddBackgroundTasks(false);
- overridingAdservicesLoggingLevel("INFO");
- }
-
- // Override the flag to disable Topics enrollment check.
- private void overrideDisableTopicsEnrollmentCheck(String val) {
- // Setting it to 1 here disables the Topics enrollment check.
- ShellUtils.runShellCommand(
- "setprop debug.adservices.disable_topics_enrollment_check " + val);
- }
-
// Override the Epoch Period to shorten the Epoch Length in the test.
private void overrideEpochPeriod(long overrideEpochPeriod) {
ShellUtils.runShellCommand(
"setprop debug.adservices.topics_epoch_job_period_ms " + overrideEpochPeriod);
}
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
- }
-
// Override the Percentage For Random Topic in the test.
private void overridePercentageForRandomTopic(long overridePercentage) {
ShellUtils.runShellCommand(
@@ -183,22 +154,12 @@ public class SandboxedTopicsManagerTest {
+ overridePercentage);
}
- // Switch on/off for MDD service. Default value is false, which means MDD is enabled.
- private void disableMddBackgroundTasks(boolean isSwitchedOff) {
- ShellUtils.runShellCommand(
- "setprop debug.adservices.mdd_background_task_kill_switch " + isSwitchedOff);
- }
-
/** Forces JobScheduler to run the Epoch Computation job */
private void forceEpochComputationJob() {
ShellUtils.runShellCommand(
"cmd jobscheduler run -f" + " " + getAdServicesPackageName() + " " + EPOCH_JOB_ID);
}
- private void overridingAdservicesLoggingLevel(String loggingLevel) {
- ShellUtils.runShellCommand("setprop log.tag.adservices %s", loggingLevel);
- }
-
// Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
@NonNull
private static String getAdServicesPackageName() {
diff --git a/adservices/tests/cts/src/android/adservices/cts/CustomAudienceApiCtsTest.java b/adservices/tests/cts/src/android/adservices/cts/CustomAudienceApiCtsTest.java
index 09cddae1b..23714139a 100644
--- a/adservices/tests/cts/src/android/adservices/cts/CustomAudienceApiCtsTest.java
+++ b/adservices/tests/cts/src/android/adservices/cts/CustomAudienceApiCtsTest.java
@@ -41,7 +41,6 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.adservices.service.PhFlagsFixture;
import com.android.adservices.service.devapi.DevContext;
import com.android.adservices.service.devapi.DevContextFilter;
-import com.android.compatibility.common.util.ShellUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
@@ -83,20 +82,10 @@ public class CustomAudienceApiCtsTest extends ForegroundCtsTest {
DevContext devContext = DevContextFilter.create(sContext).createDevContext(Process.myUid());
mIsDebugMode = devContext.getDevOptionsEnabled();
+ // Needed to test different custom audience limits
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
- // Override rate limiting throttle on API calls
- PhFlagsFixture.overrideSdkRequestPermitsPerSecond(Integer.MAX_VALUE);
- // Disable the enrollment check, by default
- PhFlagsFixture.overrideFledgeEnrollmentCheck(false);
- // We need to turn the Consent Manager into debug mode
- overrideConsentManagerDebugMode();
- }
-
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
}
@Test
@@ -110,8 +99,6 @@ public class CustomAudienceApiCtsTest extends ForegroundCtsTest {
@Test
public void testJoinCustomAudience_withMissingEnrollment_fail() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
-
Exception exception =
assertThrows(
ExecutionException.class,
@@ -321,8 +308,6 @@ public class CustomAudienceApiCtsTest extends ForegroundCtsTest {
@Test
public void testLeaveCustomAudience_withMissingEnrollment_fail() {
- PhFlagsFixture.overrideFledgeEnrollmentCheck(true);
-
Exception exception =
assertThrows(
ExecutionException.class,
diff --git a/adservices/tests/cts/src/android/adservices/cts/FledgeCtsTest.java b/adservices/tests/cts/src/android/adservices/cts/FledgeCtsTest.java
deleted file mode 100644
index ba37d690d..000000000
--- a/adservices/tests/cts/src/android/adservices/cts/FledgeCtsTest.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * 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 android.adservices.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import android.Manifest;
-import android.adservices.adselection.AdSelectionConfig;
-import android.adservices.adselection.AdSelectionConfigFixture;
-import android.adservices.adselection.AddAdSelectionOverrideRequest;
-import android.adservices.clients.adselection.AdSelectionClient;
-import android.adservices.clients.adselection.TestAdSelectionClient;
-import android.adservices.clients.customaudience.AdvertisingCustomAudienceClient;
-import android.adservices.clients.customaudience.TestAdvertisingCustomAudienceClient;
-import android.adservices.common.AdData;
-import android.adservices.common.AdSelectionSignals;
-import android.adservices.common.AdTechIdentifier;
-import android.adservices.common.CommonFixture;
-import android.adservices.customaudience.AddCustomAudienceOverrideRequest;
-import android.adservices.customaudience.CustomAudience;
-import android.adservices.customaudience.CustomAudienceFixture;
-import android.adservices.customaudience.TrustedBiddingDataFixture;
-import android.net.Uri;
-import android.os.Process;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.adservices.service.PhFlagsFixture;
-import com.android.adservices.service.common.Throttler;
-import com.android.adservices.service.devapi.DevContext;
-import com.android.adservices.service.devapi.DevContextFilter;
-import com.android.compatibility.common.util.ShellUtils;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-public class FledgeCtsTest extends ForegroundCtsTest {
- private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
-
- private static final AdTechIdentifier BUYER_1 = AdSelectionConfigFixture.BUYER_1;
- private static final AdTechIdentifier BUYER_2 = AdSelectionConfigFixture.BUYER_2;
-
- private static final String SELLER_DECISION_LOGIC_URI = "/ssp/decision/logic/";
- private static final String BUYER_BIDDING_LOGIC_URI_PREFIX = "/buyer/bidding/logic/";
- private static final Uri TRUSTED_SCORING_SIGNALS_URI = Uri.parse("ssp/trusted/signals");
- private static final AdSelectionSignals TRUSTED_SCORING_SIGNALS =
- AdSelectionSignals.fromString(
- "{\n"
- + "\t\"render_uri_1\": \"signals_for_1\",\n"
- + "\t\"render_uri_2\": \"signals_for_2\"\n"
- + "}");
-
- private static final AdTechIdentifier SELLER = AdTechIdentifier.fromString("test.com");
- private static final String SELLER_REPORTING_PATH = "/reporting/seller";
- private static final String BUYER_REPORTING_PATH = "/reporting/buyer";
-
- private static final AdSelectionConfig AD_SELECTION_CONFIG =
- AdSelectionConfigFixture.anAdSelectionConfigBuilder()
- .setCustomAudienceBuyers(Arrays.asList(BUYER_1, BUYER_2))
- .setSeller(SELLER)
- .setDecisionLogicUri(
- Uri.parse("https://" + SELLER + "/" + SELLER_DECISION_LOGIC_URI))
- .setTrustedScoringSignalsUri(
- Uri.parse("https://" + SELLER + "/" + TRUSTED_SCORING_SIGNALS_URI))
- .build();
-
- private static final int DELAY_TO_AVOID_THROTTLE_MS = 1001;
- private AdSelectionClient mAdSelectionClient;
- private TestAdSelectionClient mTestAdSelectionClient;
- private AdvertisingCustomAudienceClient mCustomAudienceClient;
- private TestAdvertisingCustomAudienceClient mTestCustomAudienceClient;
- private DevContext mDevContext;
- private boolean mIsDebugMode;
-
- @Before
- public void setup() {
- assertForegroundActivityStarted();
-
- mAdSelectionClient =
- new AdSelectionClient.Builder()
- .setContext(sContext)
- .setExecutor(CALLBACK_EXECUTOR)
- .build();
- mTestAdSelectionClient =
- new TestAdSelectionClient.Builder()
- .setContext(sContext)
- .setExecutor(CALLBACK_EXECUTOR)
- .build();
- mCustomAudienceClient =
- new AdvertisingCustomAudienceClient.Builder()
- .setContext(sContext)
- .setExecutor(MoreExecutors.directExecutor())
- .build();
- mTestCustomAudienceClient =
- new TestAdvertisingCustomAudienceClient.Builder()
- .setContext(sContext)
- .setExecutor(MoreExecutors.directExecutor())
- .build();
- mDevContext = DevContextFilter.create(sContext).createDevContext(Process.myUid());
- mIsDebugMode = mDevContext.getDevOptionsEnabled();
-
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
- // Enabling tests to run with WebView version < M105
- PhFlagsFixture.overrideEnforceIsolateMaxHeapSize(false);
- PhFlagsFixture.overrideIsolateMaxHeapSizeBytes(0);
- PhFlagsFixture.overrideSdkRequestPermitsPerSecond(Integer.MAX_VALUE);
- // We need to turn the Consent Manager into debug mode
- overrideConsentManagerDebugMode();
- }
-
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
- }
-
- @Test
- public void testFledgeFlowFailsInNonDebuggableState() throws Exception {
- Assume.assumeFalse(mIsDebugMode);
-
- String decisionLogicJs =
- "function scoreAd(ad, bid, auction_config, seller_signals,"
- + " trusted_scoring_signals, contextual_signal, user_signal,"
- + " custom_audience_signal) { \n"
- + " return {'status': 0, 'score': bid };\n"
- + "}\n"
- + "function reportResult(ad_selection_config, render_uri, bid,"
- + " contextual_signals) { \n"
- + " return {'status': 0, 'results': {'signals_for_buyer':"
- + " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
- + SELLER_REPORTING_PATH
- + "' } };\n"
- + "}";
-
- String biddingLogicJs =
- "function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
- + " custom_audience_signals) { \n"
- + " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
- + "}\n"
- + "function reportWin(ad_selection_signals, per_buyer_signals,"
- + " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
- + " return {'status': 0, 'results': {'reporting_uri': '"
- + BUYER_REPORTING_PATH
- + "' } };\n"
- + "}";
-
- List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
- List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
-
- CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
-
- CustomAudience customAudience2 = createCustomAudience(BUYER_2, bidsForBuyer2);
-
- Throttler.destroyExistingThrottler();
- // Joining custom audiences, no result to do assertion on. Failures will generate an
- // exception."
- Thread.sleep(DELAY_TO_AVOID_THROTTLE_MS);
- mCustomAudienceClient.joinCustomAudience(customAudience1).get(10, TimeUnit.SECONDS);
- Thread.sleep(DELAY_TO_AVOID_THROTTLE_MS);
- mCustomAudienceClient.joinCustomAudience(customAudience2).get(10, TimeUnit.SECONDS);
-
- // Adding AdSelection override, asserting a failure since app is not debuggable.
- AddAdSelectionOverrideRequest addAdSelectionOverrideRequest =
- new AddAdSelectionOverrideRequest(
- AD_SELECTION_CONFIG, decisionLogicJs, TRUSTED_SCORING_SIGNALS);
-
- ListenableFuture<Void> adSelectionOverrideResult =
- mTestAdSelectionClient.overrideAdSelectionConfigRemoteInfo(
- addAdSelectionOverrideRequest);
-
- Exception adSelectionOverrideException =
- assertThrows(
- ExecutionException.class,
- () -> adSelectionOverrideResult.get(10, TimeUnit.SECONDS));
- assertThat(adSelectionOverrideException.getCause()).isInstanceOf(SecurityException.class);
-
- AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest =
- new AddCustomAudienceOverrideRequest.Builder()
- .setBuyer(customAudience2.getBuyer())
- .setName(customAudience2.getName())
- .setBiddingLogicJs(biddingLogicJs)
- .setTrustedBiddingSignals(AdSelectionSignals.EMPTY)
- .build();
-
- // Adding Custom audience override, asserting a failure since app is not debuggable.
- ListenableFuture<Void> customAudienceOverrideResult =
- mTestCustomAudienceClient.overrideCustomAudienceRemoteInfo(
- addCustomAudienceOverrideRequest);
-
- Exception customAudienceOverrideException =
- assertThrows(
- ExecutionException.class,
- () -> customAudienceOverrideResult.get(10, TimeUnit.SECONDS));
- assertThat(customAudienceOverrideException.getCause())
- .isInstanceOf(SecurityException.class);
-
- // Running ad selection and asserting that a failure is returned since the fetch calls
- // should fail.
- Exception selectAdsException =
- assertThrows(
- ExecutionException.class,
- () ->
- mAdSelectionClient
- .selectAds(AD_SELECTION_CONFIG)
- .get(30, TimeUnit.SECONDS));
- assertThat(selectAdsException.getCause()).isInstanceOf(IllegalStateException.class);
- // Cannot perform reporting since no ad selection id is returned.
- }
-
- /**
- * @param buyer The name of the buyer for this Custom Audience
- * @param bids these bids, are added to its metadata. Our JS logic then picks this value and
- * creates ad with the provided value as bid
- * @return a real Custom Audience object that can be persisted and used in bidding and scoring
- */
- private CustomAudience createCustomAudience(final AdTechIdentifier buyer, List<Double> bids) {
-
- // Generate ads for with bids provided
- List<AdData> ads = new ArrayList<>();
-
- // Create ads with the buyer name and bid number as the ad URI
- // Add the bid value to the metadata
- for (int i = 0; i < bids.size(); i++) {
- ads.add(
- new AdData.Builder()
- .setRenderUri(CommonFixture.getUri(buyer, "/ad" + (i + 1)))
- .setMetadata("{\"result\":" + bids.get(i) + "}")
- .build());
- }
-
- return new CustomAudience.Builder()
- .setBuyer(buyer)
- .setName(buyer + CustomAudienceFixture.VALID_NAME)
- .setActivationTime(CustomAudienceFixture.VALID_ACTIVATION_TIME)
- .setExpirationTime(CustomAudienceFixture.VALID_EXPIRATION_TIME)
- .setDailyUpdateUri(CustomAudienceFixture.getValidDailyUpdateUriByBuyer(buyer))
- .setUserBiddingSignals(CustomAudienceFixture.VALID_USER_BIDDING_SIGNALS)
- .setTrustedBiddingData(
- TrustedBiddingDataFixture.getValidTrustedBiddingDataByBuyer(buyer))
- .setBiddingLogicUri(
- CommonFixture.getUri(
- buyer, BUYER_BIDDING_LOGIC_URI_PREFIX + buyer.toString()))
- .setAds(ads)
- .build();
- }
-}
diff --git a/adservices/tests/cts/src/android/adservices/cts/TestAdSelectionManagerTest.java b/adservices/tests/cts/src/android/adservices/cts/TestAdSelectionManagerTest.java
index 2ca1e888c..139a3cfb5 100644
--- a/adservices/tests/cts/src/android/adservices/cts/TestAdSelectionManagerTest.java
+++ b/adservices/tests/cts/src/android/adservices/cts/TestAdSelectionManagerTest.java
@@ -40,7 +40,6 @@ import com.android.adservices.LogUtil;
import com.android.adservices.service.PhFlagsFixture;
import com.android.adservices.service.devapi.DevContext;
import com.android.adservices.service.devapi.DevContextFilter;
-import com.android.compatibility.common.util.ShellUtils;
import com.google.common.util.concurrent.ListenableFuture;
@@ -97,13 +96,6 @@ public class TestAdSelectionManagerTest extends ForegroundCtsTest {
.adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
PhFlagsFixture.overrideEnforceIsolateMaxHeapSize(false);
PhFlagsFixture.overrideIsolateMaxHeapSizeBytes(0);
- // We need to turn the Consent Manager into debug mode
- overrideConsentManagerDebugMode();
- }
-
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
}
@Test
diff --git a/adservices/tests/cts/src/android/adservices/cts/TrustedBiddingDataTest.java b/adservices/tests/cts/src/android/adservices/cts/TrustedBiddingDataTest.java
index c26a6dbdd..71f79aac4 100644
--- a/adservices/tests/cts/src/android/adservices/cts/TrustedBiddingDataTest.java
+++ b/adservices/tests/cts/src/android/adservices/cts/TrustedBiddingDataTest.java
@@ -44,13 +44,14 @@ public final class TrustedBiddingDataTest {
TrustedBiddingData validTrustedBiddingData =
new TrustedBiddingData.Builder()
.setTrustedBiddingUri(VALID_TRUSTED_BIDDING_URL)
- .setTrustedBiddingKeys(TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS)
+ .setTrustedBiddingKeys(
+ TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
.build();
assertThat(validTrustedBiddingData.getTrustedBiddingUri())
.isEqualTo(VALID_TRUSTED_BIDDING_URL);
assertThat(validTrustedBiddingData.getTrustedBiddingKeys())
- .isEqualTo(TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS);
+ .isEqualTo(TrustedBiddingDataFixture.getValidTrustedBiddingKeys());
}
@Test
@@ -58,7 +59,8 @@ public final class TrustedBiddingDataTest {
TrustedBiddingData validTrustedBiddingData =
new TrustedBiddingData.Builder()
.setTrustedBiddingUri(VALID_TRUSTED_BIDDING_URL)
- .setTrustedBiddingKeys(TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS)
+ .setTrustedBiddingKeys(
+ TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
.build();
Parcel p = Parcel.obtain();
@@ -68,7 +70,7 @@ public final class TrustedBiddingDataTest {
assertThat(fromParcel.getTrustedBiddingUri()).isEqualTo(VALID_TRUSTED_BIDDING_URL);
assertThat(fromParcel.getTrustedBiddingKeys())
- .isEqualTo(TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS);
+ .isEqualTo(TrustedBiddingDataFixture.getValidTrustedBiddingKeys());
}
@Test
@@ -79,7 +81,7 @@ public final class TrustedBiddingDataTest {
// TrustedBiddingUrl is not set, so it gets built as null
new TrustedBiddingData.Builder()
.setTrustedBiddingKeys(
- TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS)
+ TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
.build();
});
}
@@ -118,7 +120,8 @@ public final class TrustedBiddingDataTest {
TrustedBiddingData validTrustedBiddingData =
new TrustedBiddingData.Builder()
.setTrustedBiddingUri(VALID_TRUSTED_BIDDING_URL)
- .setTrustedBiddingKeys(TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS)
+ .setTrustedBiddingKeys(
+ TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
.build();
assertThat(validTrustedBiddingData.describeContents()).isEqualTo(0);
diff --git a/adservices/tests/cts/src/android/adservices/debuggablects/FledgeCtsDebuggableTest.java b/adservices/tests/cts/src/android/adservices/debuggablects/FledgeCtsDebuggableTest.java
index a81366ac4..5862986bc 100644
--- a/adservices/tests/cts/src/android/adservices/debuggablects/FledgeCtsDebuggableTest.java
+++ b/adservices/tests/cts/src/android/adservices/debuggablects/FledgeCtsDebuggableTest.java
@@ -40,14 +40,13 @@ import android.adservices.customaudience.CustomAudienceFixture;
import android.adservices.customaudience.TrustedBiddingDataFixture;
import android.net.Uri;
import android.os.Process;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.adservices.LogUtil;
import com.android.adservices.service.PhFlagsFixture;
import com.android.adservices.service.devapi.DevContext;
import com.android.adservices.service.devapi.DevContextFilter;
-import com.android.compatibility.common.util.ShellUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
@@ -69,6 +68,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
+ public static final String TAG = "adservices";
// Time allowed by current test setup for APIs to respond
private static final int API_RESPONSE_TIMEOUT_SECONDS = 5;
@@ -90,6 +90,9 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
private static final String SELLER_REPORTING_PATH = "/reporting/seller";
private static final String BUYER_REPORTING_PATH = "/reporting/buyer";
+ private static final String SELLER_REPORTING_URI =
+ String.format("https://%s%s", AdSelectionConfigFixture.SELLER, SELLER_REPORTING_PATH);
+
private static final String DEFAULT_DECISION_LOGIC_JS =
"function scoreAd(ad, bid, auction_config, seller_signals,"
+ " trusted_scoring_signals, contextual_signal, user_signal,"
@@ -100,7 +103,7 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
+ " contextual_signals) { \n"
+ " return {'status': 0, 'results': {'signals_for_buyer':"
+ " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
- + SELLER_REPORTING_PATH
+ + SELLER_REPORTING_URI
+ "' } };\n"
+ "}";
private static final AdSelectionSignals TRUSTED_SCORING_SIGNALS =
@@ -136,16 +139,36 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
AdSelectionConfigFixture.SELLER,
SELLER_TRUSTED_SIGNAL_URI_PATH)))
.build();
- private static final String DEFAULT_BIDDING_LOGIC_JS =
+
+ private static final String BUYER_2_REPORTING_URI =
+ String.format("https://%s%s", AdSelectionConfigFixture.BUYER_2, BUYER_REPORTING_PATH);
+
+ private static final String BUYER_2_BIDDING_LOGIC_JS =
+ "function generateBid(ad, auction_signals, per_buyer_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ + " custom_audience_signals) { \n"
+ + " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ + "}\n"
+ + "function reportWin(ad_selection_signals, per_buyer_signals,"
+ + " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ + " return {'status': 0, 'results': {'reporting_uri': '"
+ + BUYER_2_REPORTING_URI
+ + "' } };\n"
+ + "}";
+
+ private static final String BUYER_1_REPORTING_URI =
+ String.format("https://%s%s", AdSelectionConfigFixture.BUYER_1, BUYER_REPORTING_PATH);
+
+ private static final String BUYER_1_BIDDING_LOGIC_JS =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
+ "function reportWin(ad_selection_signals, per_buyer_signals,"
+ " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ " return {'status': 0, 'results': {'reporting_uri': '"
- + BUYER_REPORTING_PATH
+ + BUYER_1_REPORTING_URI
+ "' } };\n"
+ "}";
private AdSelectionClient mAdSelectionClient;
@@ -199,14 +222,6 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
// Enable CTS to be run with versions of WebView < M105
PhFlagsFixture.overrideEnforceIsolateMaxHeapSize(false);
PhFlagsFixture.overrideIsolateMaxHeapSizeBytes(0);
- PhFlagsFixture.overrideSdkRequestPermitsPerSecond(Integer.MAX_VALUE);
- // We need to turn the Consent Manager into debug mode
- overrideConsentManagerDebugMode();
- }
-
- // Override the Consent Manager behaviour - Consent Given
- private void overrideConsentManagerDebugMode() {
- ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
}
@After
@@ -249,14 +264,14 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_2_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -269,9 +284,11 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
.overrideCustomAudienceRemoteInfo(addCustomAudienceOverrideRequest2)
.get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
- LogUtil.i(
+ Log.i(
+ TAG,
"Running ad selection with logic URI " + AD_SELECTION_CONFIG.getDecisionLogicUri());
- LogUtil.i(
+ Log.i(
+ TAG,
"Decision logic URI domain is "
+ AD_SELECTION_CONFIG.getDecisionLogicUri().getHost());
@@ -327,7 +344,7 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience.getBuyer())
.setName(customAudience.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -337,9 +354,11 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
.overrideCustomAudienceRemoteInfo(addCustomAudienceOverrideRequest)
.get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
- LogUtil.i(
+ Log.i(
+ TAG,
"Running ad selection with logic URI " + AD_SELECTION_CONFIG.getDecisionLogicUri());
- LogUtil.i(
+ Log.i(
+ TAG,
"Decision logic URI domain is "
+ AD_SELECTION_CONFIG.getDecisionLogicUri().getHost());
@@ -416,14 +435,14 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_2_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -499,14 +518,14 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_2_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -581,7 +600,7 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
@@ -659,14 +678,14 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_2_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -724,7 +743,7 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
// We do not provide override for CA 2, that should lead to failure to get biddingLogic
@@ -783,14 +802,14 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_2_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -861,14 +880,14 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_2_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -945,14 +964,14 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_2_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -1011,7 +1030,7 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
String jsWaitMoreThanAllowedForBiddingPerCa = insertJsWait(5000);
String readBidFromAdMetadataWithDelayJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ jsWaitMoreThanAllowedForBiddingPerCa
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
@@ -1031,7 +1050,7 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience1.getBuyer())
.setName(customAudience1.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_1_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
AddCustomAudienceOverrideRequest addCustomAudienceOverrideRequest2 =
@@ -1115,7 +1134,7 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
new AddCustomAudienceOverrideRequest.Builder()
.setBuyer(customAudience2.getBuyer())
.setName(customAudience2.getName())
- .setBiddingLogicJs(DEFAULT_BIDDING_LOGIC_JS)
+ .setBiddingLogicJs(BUYER_2_BIDDING_LOGIC_JS)
.setTrustedBiddingSignals(TRUSTED_BIDDING_SIGNALS)
.build();
@@ -1125,9 +1144,11 @@ public class FledgeCtsDebuggableTest extends ForegroundDebuggableCtsTest {
.overrideCustomAudienceRemoteInfo(addCustomAudienceOverrideRequest)
.get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
- LogUtil.i(
+ Log.i(
+ TAG,
"Running ad selection with logic URI " + AD_SELECTION_CONFIG.getDecisionLogicUri());
- LogUtil.i(
+ Log.i(
+ TAG,
"Decision logic URI domain is "
+ AD_SELECTION_CONFIG.getDecisionLogicUri().getHost());
diff --git a/adservices/tests/cts/testapps/testapp1/Android.bp b/adservices/tests/cts/testapps/testapp1/Android.bp
index 82bce8bb2..8c90f1a33 100644
--- a/adservices/tests/cts/testapps/testapp1/Android.bp
+++ b/adservices/tests/cts/testapps/testapp1/Android.bp
@@ -29,4 +29,6 @@ android_app {
],
resource_dirs: ["res"],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/adservices/tests/cts/testapps/testapp1/java/com/android/adservices/tests/cts/topics/testapp1/MainActivity.java b/adservices/tests/cts/testapps/testapp1/java/com/android/adservices/tests/cts/topics/testapp1/MainActivity.java
index 1b5297535..cbe1ebbcb 100644
--- a/adservices/tests/cts/testapps/testapp1/java/com/android/adservices/tests/cts/topics/testapp1/MainActivity.java
+++ b/adservices/tests/cts/testapps/testapp1/java/com/android/adservices/tests/cts/topics/testapp1/MainActivity.java
@@ -75,6 +75,7 @@ public class MainActivity extends Activity {
}
// Send Broadcast to main test suite to pass the GetTopicsResponse
+ @SuppressWarnings("NewApi")
private void sendGetTopicsResponseBroadcast(@NonNull GetTopicsResponse getTopicsResponse) {
List<Topic> topics = getTopicsResponse.getTopics();
Log.v(LOG_TAG, "Test app gets topics: " + topics);
diff --git a/adservices/tests/endtoends/AndroidTest.xml b/adservices/tests/endtoends/AndroidTest.xml
index 88058d6a0..aafcfa753 100644
--- a/adservices/tests/endtoends/AndroidTest.xml
+++ b/adservices/tests/endtoends/AndroidTest.xml
@@ -15,15 +15,20 @@
~ limitations under the License.
-->
<configuration description="Config for Ad Services E2E tests">
-<option name="test-tag" value="AdServicesEndToEndTests" />
+ <option name="test-tag" value="AdServicesEndToEndTests" />
-<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="AdServicesEndToEndTests.apk"/>
-</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="AdServicesEndToEndTests.apk"/>
+ </target_preparer>
-<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="hidden-api-checks" value="false" /> <!-- Allow hidden API uses -->
- <option name="package" value="com.android.adservices.endtoendtest"/>
-</test>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable global_kill_switch to ignore the effect of actual PH values. -->
+ <option name="run-command" value="device_config put adservices global_kill_switch false" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="hidden-api-checks" value="false" /> <!-- Allow hidden API uses -->
+ <option name="package" value="com.android.adservices.endtoendtest"/>
+ </test>
</configuration> \ No newline at end of file
diff --git a/adservices/tests/endtoends/src/com/android/adservices/measurement/MeasurementManagerTest.java b/adservices/tests/endtoends/src/com/android/adservices/measurement/MeasurementManagerTest.java
index ee01f3585..0fa9238e3 100644
--- a/adservices/tests/endtoends/src/com/android/adservices/measurement/MeasurementManagerTest.java
+++ b/adservices/tests/endtoends/src/com/android/adservices/measurement/MeasurementManagerTest.java
@@ -15,6 +15,7 @@
*/
package com.android.adservices.measurement;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
@@ -40,6 +41,7 @@ import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.OutcomeReceiver;
+import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import com.android.compatibility.common.util.ShellUtils;
@@ -58,23 +60,20 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MeasurementManagerTest {
- // TODO: Add register tests with non-null callback and executor
- private static final String TAG = "MeasurementManagerTest";
- private static final String SERVICE_APK_NAME = "com.android.adservices.api";
private static final String CLIENT_PACKAGE_NAME = "com.android.adservices.endtoendtest";
- private static final long AWAIT_GET_ADID_TIMEOUT = 5000L;
+ private static final long TIMEOUT = 5000L;
protected static final Context sContext = ApplicationProvider.getApplicationContext();
private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
private static final SandboxedSdkContext sSandboxedSdkContext =
new SandboxedSdkContext(
sContext,
+ sContext.getClassLoader(),
CLIENT_PACKAGE_NAME,
new ApplicationInfo(),
"sdkName",
/* sdkCeDataDir = */ null,
/* sdkDeDataDir = */ null);
-
@After
public void tearDown() {
resetOverrideConsentManagerDebugMode();
@@ -88,7 +87,7 @@ public class MeasurementManagerTest {
ArgumentCaptor<RegistrationRequest> captor =
ArgumentCaptor.forClass(RegistrationRequest.class);
CountDownLatch countDownLatch = new CountDownLatch(1);
- Answer answer =
+ Answer<Void> answer =
(invocation) -> {
countDownLatch.countDown();
return null;
@@ -101,7 +100,7 @@ public class MeasurementManagerTest {
/* executor = */ null,
/* callback = */ null);
- assertTrue(countDownLatch.await(AWAIT_GET_ADID_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
Assert.assertNotNull(captor.getValue().getPackageName());
Assert.assertEquals(CLIENT_PACKAGE_NAME, captor.getValue().getPackageName());
}
@@ -115,7 +114,7 @@ public class MeasurementManagerTest {
ArgumentCaptor<RegistrationRequest> captor =
ArgumentCaptor.forClass(RegistrationRequest.class);
CountDownLatch countDownLatch = new CountDownLatch(1);
- Answer answer =
+ Answer<Void> answer =
(invocation) -> {
countDownLatch.countDown();
return null;
@@ -128,12 +127,52 @@ public class MeasurementManagerTest {
/* executor = */ null,
/* callback = */ null);
- assertTrue(countDownLatch.await(AWAIT_GET_ADID_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
Assert.assertNotNull(captor.getValue().getPackageName());
Assert.assertEquals(CLIENT_PACKAGE_NAME, captor.getValue().getPackageName());
}
@Test
+ public void testRegisterSource_executorAndCallbackCalled() throws Exception {
+ final MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
+ final CountDownLatch anyCountDownLatch = new CountDownLatch(1);
+
+ mm.registerSource(
+ Uri.parse("https://registration-source"),
+ /* inputEvent = */ null,
+ CALLBACK_EXECUTOR,
+ new OutcomeReceiver<Object, Exception>() {
+ @Override
+ public void onResult(@NonNull Object result) {
+ anyCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ anyCountDownLatch.countDown();
+ }
+ });
+
+ assertTrue(anyCountDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ private WebSourceRegistrationRequest buildDefaultWebSourceRegistrationRequest() {
+ WebSourceParams webSourceParams =
+ new WebSourceParams.Builder(Uri.parse("https://example.com"))
+ .setDebugKeyAllowed(false)
+ .build();
+
+ return new WebSourceRegistrationRequest.Builder(
+ Collections.singletonList(webSourceParams),
+ Uri.parse("https://example.com"))
+ .setInputEvent(null)
+ .setAppDestination(Uri.parse("android-app://com.example"))
+ .setWebDestination(Uri.parse("https://example.com"))
+ .setVerifiedDestination(null)
+ .build();
+ }
+
+ @Test
public void testRegisterWebSource_callingApp_expectedAttributionSource() throws Exception {
MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
IMeasurementService mockService = mock(IMeasurementService.class);
@@ -141,31 +180,19 @@ public class MeasurementManagerTest {
ArgumentCaptor<WebSourceRegistrationRequestInternal> captor =
ArgumentCaptor.forClass(WebSourceRegistrationRequestInternal.class);
CountDownLatch countDownLatch = new CountDownLatch(1);
- Answer answer =
+ Answer<Void> answer =
(invocation) -> {
countDownLatch.countDown();
return null;
};
doAnswer(answer).when(mockService).registerWebSource(captor.capture(), any(), any());
- WebSourceParams webSourceParams =
- new WebSourceParams.Builder(Uri.parse("https://example.com"))
- .setDebugKeyAllowed(false)
- .build();
-
- WebSourceRegistrationRequest webSourceRegistrationRequest =
- new WebSourceRegistrationRequest.Builder(
- Collections.singletonList(webSourceParams),
- Uri.parse("https://example.com"))
- .setInputEvent(null)
- .setAppDestination(Uri.parse("android-app://com.example"))
- .setWebDestination(Uri.parse("https://example.com"))
- .setVerifiedDestination(null)
- .build();
mm.registerWebSource(
- webSourceRegistrationRequest, /* executor = */ null, /* callback = */ null);
+ buildDefaultWebSourceRegistrationRequest(),
+ /* executor = */ null,
+ /* callback = */ null);
- assertTrue(countDownLatch.await(AWAIT_GET_ADID_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
Assert.assertNotNull(captor.getValue().getPackageName());
Assert.assertEquals(CLIENT_PACKAGE_NAME, captor.getValue().getPackageName());
}
@@ -179,36 +206,58 @@ public class MeasurementManagerTest {
ArgumentCaptor<WebSourceRegistrationRequestInternal> captor =
ArgumentCaptor.forClass(WebSourceRegistrationRequestInternal.class);
CountDownLatch countDownLatch = new CountDownLatch(1);
- Answer answer =
+ Answer<Void> answer =
(invocation) -> {
countDownLatch.countDown();
return null;
};
doAnswer(answer).when(mockService).registerWebSource(captor.capture(), any(), any());
- WebSourceParams webSourceParams =
- new WebSourceParams.Builder(Uri.parse("https://example.com"))
- .setDebugKeyAllowed(false)
- .build();
-
- WebSourceRegistrationRequest webSourceRegistrationRequest =
- new WebSourceRegistrationRequest.Builder(
- Collections.singletonList(webSourceParams),
- Uri.parse("https://example.com"))
- .setInputEvent(null)
- .setAppDestination(Uri.parse("android-app://com.example"))
- .setWebDestination(Uri.parse("https://example.com"))
- .setVerifiedDestination(null)
- .build();
mm.registerWebSource(
- webSourceRegistrationRequest, /* executor = */ null, /* callback = */ null);
+ buildDefaultWebSourceRegistrationRequest(),
+ /* executor = */ null,
+ /* callback = */ null);
- assertTrue(countDownLatch.await(AWAIT_GET_ADID_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
Assert.assertNotNull(captor.getValue().getPackageName());
Assert.assertEquals(CLIENT_PACKAGE_NAME, captor.getValue().getPackageName());
}
@Test
+ public void testRegisterWebSource_executorAndCallbackCalled() throws Exception {
+ final MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
+ final CountDownLatch anyCountDownLatch = new CountDownLatch(1);
+
+ mm.registerWebSource(
+ buildDefaultWebSourceRegistrationRequest(),
+ CALLBACK_EXECUTOR,
+ new OutcomeReceiver<Object, Exception>() {
+ @Override
+ public void onResult(@NonNull Object result) {
+ anyCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ anyCountDownLatch.countDown();
+ }
+ });
+
+ assertTrue(anyCountDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ private WebTriggerRegistrationRequest buildDefaultWebTriggerRegistrationRequest() {
+ WebTriggerParams webTriggerParams =
+ new WebTriggerParams.Builder(Uri.parse("https://example.com"))
+ .setDebugKeyAllowed(false)
+ .build();
+ return new WebTriggerRegistrationRequest.Builder(
+ Collections.singletonList(webTriggerParams),
+ Uri.parse("https://example.com"))
+ .build();
+ }
+
+ @Test
public void testRegisterWebTrigger_callingApp_expectedAttributionSource() throws Exception {
MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
IMeasurementService mockService = mock(IMeasurementService.class);
@@ -216,26 +265,19 @@ public class MeasurementManagerTest {
ArgumentCaptor<WebTriggerRegistrationRequestInternal> captor =
ArgumentCaptor.forClass(WebTriggerRegistrationRequestInternal.class);
CountDownLatch countDownLatch = new CountDownLatch(1);
- Answer answer =
+ Answer<Void> answer =
(invocation) -> {
countDownLatch.countDown();
return null;
};
doAnswer(answer).when(mockService).registerWebTrigger(captor.capture(), any(), any());
- WebTriggerParams webTriggerParams =
- new WebTriggerParams.Builder(Uri.parse("https://example.com"))
- .setDebugKeyAllowed(false)
- .build();
- WebTriggerRegistrationRequest webTriggerRegistrationRequest =
- new WebTriggerRegistrationRequest.Builder(
- Collections.singletonList(webTriggerParams),
- Uri.parse("https://example.com"))
- .build();
mm.registerWebTrigger(
- webTriggerRegistrationRequest, /* executor = */ null, /* callback = */ null);
+ buildDefaultWebTriggerRegistrationRequest(),
+ /* executor = */ null,
+ /* callback = */ null);
- assertTrue(countDownLatch.await(AWAIT_GET_ADID_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
Assert.assertNotNull(captor.getValue().getPackageName());
Assert.assertEquals(CLIENT_PACKAGE_NAME, captor.getValue().getPackageName());
}
@@ -249,31 +291,47 @@ public class MeasurementManagerTest {
ArgumentCaptor<WebTriggerRegistrationRequestInternal> captor =
ArgumentCaptor.forClass(WebTriggerRegistrationRequestInternal.class);
CountDownLatch countDownLatch = new CountDownLatch(1);
- Answer answer =
+ Answer<Void> answer =
(invocation) -> {
countDownLatch.countDown();
return null;
};
doAnswer(answer).when(mockService).registerWebTrigger(captor.capture(), any(), any());
- WebTriggerParams webTriggerParams =
- new WebTriggerParams.Builder(Uri.parse("https://example.com"))
- .setDebugKeyAllowed(false)
- .build();
- WebTriggerRegistrationRequest webTriggerRegistrationRequest =
- new WebTriggerRegistrationRequest.Builder(
- Collections.singletonList(webTriggerParams),
- Uri.parse("https://example.com"))
- .build();
mm.registerWebTrigger(
- webTriggerRegistrationRequest, /* executor = */ null, /* callback = */ null);
+ buildDefaultWebTriggerRegistrationRequest(),
+ /* executor = */ null,
+ /* callback = */ null);
- assertTrue(countDownLatch.await(AWAIT_GET_ADID_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
Assert.assertNotNull(captor.getValue().getPackageName());
Assert.assertEquals(CLIENT_PACKAGE_NAME, captor.getValue().getPackageName());
}
@Test
+ public void testRegisterWebTrigger_executorAndCallbackCalled() throws Exception {
+ final MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
+ final CountDownLatch anyCountDownLatch = new CountDownLatch(1);
+
+ mm.registerWebTrigger(
+ buildDefaultWebTriggerRegistrationRequest(),
+ CALLBACK_EXECUTOR,
+ new OutcomeReceiver<Object, Exception>() {
+ @Override
+ public void onResult(@NonNull Object result) {
+ anyCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ anyCountDownLatch.countDown();
+ }
+ });
+
+ assertTrue(anyCountDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
public void testRegisterTrigger_callingApp_expectedAttributionSource() throws Exception {
MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
IMeasurementService mockService = mock(IMeasurementService.class);
@@ -281,7 +339,7 @@ public class MeasurementManagerTest {
ArgumentCaptor<RegistrationRequest> captor =
ArgumentCaptor.forClass(RegistrationRequest.class);
CountDownLatch countDownLatch = new CountDownLatch(1);
- Answer answer =
+ Answer<Void> answer =
(invocation) -> {
countDownLatch.countDown();
return null;
@@ -291,7 +349,7 @@ public class MeasurementManagerTest {
mm.registerTrigger(
Uri.parse("https://example.com"), /* executor = */ null, /* callback = */ null);
- assertTrue(countDownLatch.await(AWAIT_GET_ADID_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
Assert.assertNotNull(captor.getValue().getPackageName());
Assert.assertEquals(CLIENT_PACKAGE_NAME, captor.getValue().getPackageName());
}
@@ -305,7 +363,7 @@ public class MeasurementManagerTest {
ArgumentCaptor<RegistrationRequest> captor =
ArgumentCaptor.forClass(RegistrationRequest.class);
CountDownLatch countDownLatch = new CountDownLatch(1);
- Answer answer =
+ Answer<Void> answer =
(invocation) -> {
countDownLatch.countDown();
return null;
@@ -315,12 +373,35 @@ public class MeasurementManagerTest {
mm.registerTrigger(
Uri.parse("https://example.com"), /* executor = */ null, /* callback = */ null);
- assertTrue(countDownLatch.await(AWAIT_GET_ADID_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
Assert.assertNotNull(captor.getValue().getPackageName());
Assert.assertEquals(CLIENT_PACKAGE_NAME, captor.getValue().getPackageName());
}
@Test
+ public void testRegisterTrigger_executorAndCallbackCalled() throws Exception {
+ final MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
+ final CountDownLatch anyCountDownLatch = new CountDownLatch(1);
+
+ mm.registerTrigger(
+ Uri.parse("https://registration-trigger"),
+ CALLBACK_EXECUTOR,
+ new OutcomeReceiver<Object, Exception>() {
+ @Override
+ public void onResult(@NonNull Object result) {
+ anyCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ anyCountDownLatch.countDown();
+ }
+ });
+
+ assertTrue(anyCountDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
public void testDeleteRegistrations_callingApp_expectedAttributionSource() throws Exception {
MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
IMeasurementService mockService = mock(IMeasurementService.class);
@@ -356,6 +437,32 @@ public class MeasurementManagerTest {
}
@Test
+ public void testDeleteRegistrations_nullExecutor_throwNullPointerException() {
+ MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
+
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mm.deleteRegistrations(
+ new DeletionRequest.Builder().build(),
+ /* executor */ null,
+ i -> new CompletableFuture<>().complete(i)));
+ }
+
+ @Test
+ public void testDeleteRegistrations_nullCallback_throwNullPointerException() {
+ MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
+
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mm.deleteRegistrations(
+ new DeletionRequest.Builder().build(),
+ CALLBACK_EXECUTOR,
+ /* callback */ null));
+ }
+
+ @Test
public void testGetMeasurementApiStatus() throws Exception {
MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
overrideConsentManagerDebugMode();
@@ -380,6 +487,26 @@ public class MeasurementManagerTest {
Integer.valueOf(MeasurementManager.MEASUREMENT_API_STATE_ENABLED), future.get());
}
+ @Test
+ public void testGetMeasurementApiStatus_nullExecutor_throwNullPointerException() {
+ MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
+ overrideConsentManagerDebugMode();
+
+ assertThrows(
+ NullPointerException.class,
+ () -> mm.getMeasurementApiStatus(/* executor */ null, result -> {}));
+ }
+
+ @Test
+ public void testGetMeasurementApiStatus_nullCallback_throwNullPointerException() {
+ MeasurementManager mm = spy(sContext.getSystemService(MeasurementManager.class));
+ overrideConsentManagerDebugMode();
+
+ assertThrows(
+ NullPointerException.class,
+ () -> mm.getMeasurementApiStatus(CALLBACK_EXECUTOR, /* callback */ null));
+ }
+
// Override the Consent Manager behaviour - Consent Given
private void overrideConsentManagerDebugMode() {
ShellUtils.runShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
diff --git a/adservices/tests/perf/Android.bp b/adservices/tests/perf/Android.bp
new file mode 100644
index 000000000..0eca22d42
--- /dev/null
+++ b/adservices/tests/perf/Android.bp
@@ -0,0 +1,37 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+ name: "adservices-test-scenarios-defaults",
+
+ static_libs: [
+ "mockwebserver",
+ "modules-utils-testable-device-config",
+ "androidx.test.uiautomator_uiautomator",
+ "app-helpers-handheld-interfaces",
+ "common-platform-scenarios",
+ "launcher-aosp-tapl",
+ "microbenchmark-device-lib",
+ "platform-test-options",
+ "platform-test-rules",
+ "platform-test-annotations",
+ "system-helpers",
+ "ub-uiautomator",
+ "junit",
+ ],
+}
diff --git a/adservices/tests/perf/Android.mk b/adservices/tests/perf/Android.mk
new file mode 100644
index 000000000..1db41ec69
--- /dev/null
+++ b/adservices/tests/perf/Android.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := adservices-test-scenarios
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ adservices-clients \
+ mockwebserver \
+ platform-test-annotations
+LOCAL_JAVA_LIBRARIES := \
+ androidx.test.rules \
+ androidx.test.runner \
+ app-helpers-handheld-interfaces \
+ guava \
+ platform-test-rules \
+ health-testing-utils \
+ microbenchmark-device-lib \
+ ub-uiautomator \
+ common-platform-scenarios \
+ launcher-aosp-tapl \
+ platform-test-options \
+ hamcrest-library \
+ androidx.media_media \
+ mockwebserver \
+ modules-utils-testable-device-config
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+######################################
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/adservices/tests/perf/src/android/adservices/test/scenario/adservices/MeasurementRegisterCalls.java b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/MeasurementRegisterCalls.java
new file mode 100644
index 000000000..6cabc9700
--- /dev/null
+++ b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/MeasurementRegisterCalls.java
@@ -0,0 +1,184 @@
+/*
+ * 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 android.adservices.test.scenario.adservices;
+
+import android.Manifest;
+import android.adservices.clients.measurement.MeasurementClient;
+import android.adservices.test.scenario.adservices.utils.MockWebServerRule;
+import android.content.Context;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.platform.test.scenario.annotation.Scenario;
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/** Crystal Ball tests for Measurement API. */
+@Scenario
+@RunWith(JUnit4.class)
+public class MeasurementRegisterCalls {
+ protected static final Context sContext = ApplicationProvider.getApplicationContext();
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+ private static MeasurementClient sMeasurementClient;
+ private static UiDevice sDevice;
+
+ @BeforeClass
+ public static void setupDevicePropertiesAndInitializeClient() throws Exception {
+ sMeasurementClient =
+ new MeasurementClient.Builder()
+ .setContext(sContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
+
+ // Override consent manager behavior to give user consent.
+ getUiDevice()
+ .executeShellCommand("setprop debug.adservices.consent_manager_debug_mode true");
+
+ // Override the flag to allow current package to call APIs.
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ "ppapi_app_allow_list",
+ "*",
+ /* makeDefault */ false);
+ }
+
+ @AfterClass
+ public static void resetDeviceProperties() throws Exception {
+ // Reset consent
+ getUiDevice()
+ .executeShellCommand("setprop debug.adservices.consent_manager_debug_mode false");
+
+ // Reset allowed packages.
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ "ppapi_app_allow_list",
+ "null",
+ /* makeDefault */ false);
+ }
+
+ @Test
+ public void testRegisterSourceAndTriggerAndRunAttributionAndReporting() throws Exception {
+ // Create source registration response.
+ MockResponse sourceResponse = new MockResponse();
+ final JSONObject headerRegisterSource =
+ buildJson(
+ Map.of(
+ "source_event_id", 1,
+ "destination", "android-app://android.platform.test.scenario",
+ "priority", 1));
+ sourceResponse.addHeader("Attribution-Reporting-Register-Source", headerRegisterSource);
+ sourceResponse.setResponseCode(200);
+
+ // Create trigger registration response.
+ MockResponse triggerResponse = new MockResponse();
+ final JSONObject eventTriggerData =
+ buildJson(
+ Map.of(
+ "trigger_data", "1",
+ "priority", "101"));
+ final JSONArray eventTriggerDataList = buildJsonArray(List.of(eventTriggerData));
+ final JSONObject headerRegisterTrigger =
+ buildJson(Map.of("event_trigger_data", eventTriggerDataList));
+ triggerResponse.addHeader("Attribution-Reporting-Register-Trigger", headerRegisterTrigger);
+ triggerResponse.setResponseCode(200);
+
+ // Create report response.
+ MockResponse reportResponse = new MockResponse();
+ reportResponse.setResponseCode(200);
+
+ // Start mock web server.
+ List<MockResponse> responses = List.of(sourceResponse, triggerResponse, reportResponse);
+ MockWebServer mockWebServer =
+ MockWebServerRule.forHttps(
+ sContext, "adservices_test_server.p12", "adservices_test")
+ .startMockWebServer(responses);
+
+ URL url = mockWebServer.getUrl("/mockServer");
+
+ // Set the initial time to register the source and trigger.
+ getUiDevice().executeShellCommand("date -s 2022-08-01");
+
+ sMeasurementClient.registerSource(Uri.parse(url.toString()), null).get();
+ sMeasurementClient.registerTrigger(Uri.parse(url.toString())).get();
+ runAttributionJob();
+
+ // Advance the time so that generated report is within the reporting window.
+ getUiDevice().executeShellCommand("date -s 2022-09-01");
+ runReportingJob();
+ }
+
+ private void runAttributionJob() throws InterruptedException, IOException {
+ getUiDevice()
+ .executeShellCommand("cmd jobscheduler run -f com.google.android.adservices.api 5");
+ // Wait for attribution to complete.
+ SystemClock.sleep(2000);
+ }
+
+ private void runReportingJob() throws InterruptedException, IOException {
+ getUiDevice()
+ .executeShellCommand("cmd jobscheduler run -f com.google.android.adservices.api 3");
+ // Wait for reporting to complete.
+ SystemClock.sleep(2000);
+ }
+
+ private static JSONObject buildJson(Map<String, Object> fields) throws JSONException {
+ JSONObject json = new JSONObject();
+ for (Map.Entry<String, Object> entry : fields.entrySet()) {
+ json.put(entry.getKey(), entry.getValue());
+ }
+ return json;
+ }
+
+ private static JSONArray buildJsonArray(List<JSONObject> list) throws JSONException {
+ JSONArray json = new JSONArray();
+ for (int i = 0; i < list.size(); i++) {
+ json.put(i, list.get(i));
+ }
+ return json;
+ }
+
+ private static UiDevice getUiDevice() {
+ if (sDevice == null) {
+ sDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+ return sDevice;
+ }
+}
diff --git a/adservices/tests/perf/src/android/adservices/test/scenario/adservices/fledge/LimitPerfTests.java b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/fledge/LimitPerfTests.java
new file mode 100644
index 000000000..0b15e94d6
--- /dev/null
+++ b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/fledge/LimitPerfTests.java
@@ -0,0 +1,609 @@
+/*
+ * 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 android.adservices.test.scenario.adservices.fledge;
+
+import android.Manifest;
+import android.adservices.adselection.AdSelectionConfig;
+import android.adservices.adselection.AdSelectionOutcome;
+import android.adservices.adselection.ReportImpressionRequest;
+import android.adservices.clients.adselection.AdSelectionClient;
+import android.adservices.clients.customaudience.AdvertisingCustomAudienceClient;
+import android.adservices.common.AdData;
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.customaudience.CustomAudience;
+import android.adservices.customaudience.TrustedBiddingData;
+import android.adservices.test.scenario.adservices.utils.MockWebServerRule;
+import android.adservices.test.scenario.adservices.utils.MockWebServerRuleFactory;
+import android.content.Context;
+import android.net.Uri;
+import android.platform.test.scenario.annotation.Scenario;
+import android.provider.DeviceConfig;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.mockwebserver.Dispatcher;
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.RecordedRequest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@Scenario
+@RunWith(JUnit4.class)
+public class LimitPerfTests {
+
+ // The number of ms to sleep after killing the adservices process so it has time to recover
+ public static final long SLEEP_MS_AFTER_KILL = 2000L;
+ // Command to kill the adservices process
+ public static final String KILL_ADSERVICES_CMD =
+ "su 0 killall -9 com.google.android.adservices.api";
+ // Command prevent activity manager from backing off on restarting the adservices process
+ public static final String DISABLE_ADSERVICES_BACKOFF_CMD =
+ "am service-restart-backoff disable com.google.android.adservices.api";
+
+ public static final Duration CUSTOM_AUDIENCE_EXPIRE_IN = Duration.ofDays(1);
+ public static final Instant VALID_ACTIVATION_TIME = Instant.now();
+ public static final Instant VALID_EXPIRATION_TIME =
+ VALID_ACTIVATION_TIME.plus(CUSTOM_AUDIENCE_EXPIRE_IN);
+ public static final String VALID_NAME = "testCustomAudienceName";
+ public static final AdSelectionSignals VALID_USER_BIDDING_SIGNALS =
+ AdSelectionSignals.fromString("{'valid': 'yep', 'opaque': 'definitely'}");
+ public static final String VALID_TRUSTED_BIDDING_URI_PATH = "/trusted/bidding/";
+ public static final ArrayList<String> VALID_TRUSTED_BIDDING_KEYS =
+ new ArrayList<>(Arrays.asList("example", "valid", "list", "of", "keys"));
+ public static final AdTechIdentifier SELLER = AdTechIdentifier.fromString("localhost");
+ // Uri Constants
+ public static final String DECISION_LOGIC_PATH = "/decisionFragment";
+ public static final String TRUSTED_SCORING_SIGNAL_PATH = "/trustedScoringSignalsFragment";
+ public static final String CUSTOM_AUDIENCE_SHIRT = "ca_shirt";
+ public static final String CUSTOM_AUDIENCE_SHOES = "ca_shoe";
+ // TODO(b/244530379) Make compatible with multiple buyers
+ public static final AdTechIdentifier BUYER_1 = AdTechIdentifier.fromString("localhost");
+ public static final List<AdTechIdentifier> CUSTOM_AUDIENCE_BUYERS =
+ Collections.singletonList(BUYER_1);
+ public static final AdSelectionSignals AD_SELECTION_SIGNALS =
+ AdSelectionSignals.fromString("{\"ad_selection_signals\":1}");
+ public static final AdSelectionSignals SELLER_SIGNALS =
+ AdSelectionSignals.fromString("{\"test_seller_signals\":1}");
+ public static final Map<AdTechIdentifier, AdSelectionSignals> PER_BUYER_SIGNALS =
+ Map.of(BUYER_1, AdSelectionSignals.fromString("{\"buyer_signals\":1}"));
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+ // Time allowed by current test setup for APIs to respond
+ // setting a large value for perf testing, to avoid failing for large datasets
+ private static final int API_RESPONSE_TIMEOUT_SECONDS = 100;
+ private static final String BUYER_BIDDING_LOGIC_URI_PATH = "/buyer/bidding/logic/";
+ private static final String SELLER_REPORTING_PATH = "/reporting/seller";
+ private static final String BUYER_REPORTING_PATH = "/reporting/buyer";
+ private static final String DEFAULT_DECISION_LOGIC_JS =
+ "function scoreAd(ad, bid, auction_config, seller_signals,"
+ + " trusted_scoring_signals, contextual_signal, user_signal,"
+ + " custom_audience_signal) { \n"
+ + " return {'status': 0, 'score': bid };\n"
+ + "}\n"
+ + "function reportResult(ad_selection_config, render_uri, bid,"
+ + " contextual_signals) { \n"
+ + " return {'status': 0, 'results': {'signals_for_buyer':"
+ + " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
+ + SELLER_REPORTING_PATH
+ + "' } };\n"
+ + "}";
+ private static final String DEFAULT_BIDDING_LOGIC_JS =
+ "function generateBid(ad, auction_signals, per_buyer_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ + " custom_audience_signals) { \n"
+ + " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ + "}\n"
+ + "function reportWin(ad_selection_signals, per_buyer_signals,"
+ + " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ + " return {'status': 0, 'results': {'reporting_uri': '"
+ + BUYER_REPORTING_PATH
+ + "' } };\n"
+ + "}";
+ private static final String CALCULATION_INTENSE_JS =
+ "for (let i = 1; i < 1000000000; i++) {\n" + " Math.sqrt(i);\n" + "}";
+ private static final String MEMORY_INTENSE_JS =
+ "var a = []\n" + "for (let i = 0; i < 10000; i++) {\n" + " a.push(i);" + "}";
+
+ private static final AdSelectionSignals TRUSTED_SCORING_SIGNALS =
+ AdSelectionSignals.fromString(
+ "{\n"
+ + "\t\"render_uri_1\": \"signals_for_1\",\n"
+ + "\t\"render_uri_2\": \"signals_for_2\"\n"
+ + "}");
+ private static final AdSelectionSignals TRUSTED_BIDDING_SIGNALS =
+ AdSelectionSignals.fromString(
+ "{\n"
+ + "\t\"example\": \"example\",\n"
+ + "\t\"valid\": \"Also valid\",\n"
+ + "\t\"list\": \"list\",\n"
+ + "\t\"of\": \"of\",\n"
+ + "\t\"keys\": \"trusted bidding signal Values\"\n"
+ + "}");
+ private static final String AD_URI_PREFIX = "/adverts/123/";
+ private static final int DELAY_TO_AVOID_THROTTLE_MS = 1001;
+ protected final Context mContext = ApplicationProvider.getApplicationContext();
+ private final AdSelectionClient mAdSelectionClient =
+ new AdSelectionClient.Builder()
+ .setContext(mContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+ private final AdvertisingCustomAudienceClient mCustomAudienceClient =
+ new AdvertisingCustomAudienceClient.Builder()
+ .setContext(mContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+ @Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
+ private Dispatcher mDefaultDispatcher;
+
+ @BeforeClass
+ public static void setupBeforeClass() {
+ // Disable backoff since we will be killing the process between tests
+ ShellUtils.runShellCommand(DISABLE_ADSERVICES_BACKOFF_CMD);
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
+ // TODO(b/245585645) Mark true for the heap size enforcement after installing M105 library
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ "fledge_js_isolate_enforce_max_heap_size",
+ "false",
+ true);
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES, "disable_fledge_enrollment_check", "true", true);
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES, "ppapi_app_allow_list", "*", true);
+ }
+
+ public static Uri getUri(String name, String path) {
+ return Uri.parse("https://" + name + path);
+ }
+
+ @Before
+ public void setup() throws InterruptedException {
+ ShellUtils.runShellCommand(KILL_ADSERVICES_CMD);
+ Thread.sleep(SLEEP_MS_AFTER_KILL);
+ mDefaultDispatcher =
+ new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest request) {
+ if (DECISION_LOGIC_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody(DEFAULT_DECISION_LOGIC_JS);
+ } else if (BUYER_BIDDING_LOGIC_URI_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody(DEFAULT_BIDDING_LOGIC_JS);
+ } else if (BUYER_REPORTING_PATH.equals(request.getPath())
+ || SELLER_REPORTING_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody("");
+ } else if (request.getPath().startsWith(TRUSTED_SCORING_SIGNAL_PATH)) {
+ return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
+ } else if (request.getPath().startsWith(VALID_TRUSTED_BIDDING_URI_PATH)) {
+ return new MockResponse().setBody(TRUSTED_BIDDING_SIGNALS.toString());
+ }
+ return new MockResponse().setResponseCode(404);
+ }
+ };
+ }
+
+ @Test
+ public void test_joinCustomAudience_success() throws Exception {
+ CustomAudience ca =
+ createCustomAudience(
+ BUYER_1, CUSTOM_AUDIENCE_SHOES, Collections.singletonList(1.0));
+ addDelayToAvoidThrottle();
+ mCustomAudienceClient
+ .joinCustomAudience(ca)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ addDelayToAvoidThrottle();
+ mCustomAudienceClient
+ .leaveCustomAudience(ca.getBuyer(), ca.getName())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void testAdSelectionAndReporting_normalFlow_success() throws Exception {
+ List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+ List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+ CustomAudience customAudience1 =
+ createCustomAudience(BUYER_1, CUSTOM_AUDIENCE_SHOES, bidsForBuyer1);
+
+ // TODO(b/244530379) Make compatible with multiple buyers
+ CustomAudience customAudience2 =
+ createCustomAudience(BUYER_1, CUSTOM_AUDIENCE_SHIRT, bidsForBuyer2);
+ List<CustomAudience> customAudienceList = Arrays.asList(customAudience1, customAudience2);
+
+ mMockWebServerRule.startMockWebServer(mDefaultDispatcher);
+
+ for (CustomAudience ca : customAudienceList) {
+ addDelayToAvoidThrottle();
+ mCustomAudienceClient
+ .joinCustomAudience(ca)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ // Running ad selection and asserting that the outcome is returned in < 10 seconds
+ addDelayToAvoidThrottle();
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // The winning ad should be ad3 from CA shirt
+ Assert.assertEquals(
+ "Ad selection outcome is not expected",
+ createExpectedWinningUri(BUYER_1, CUSTOM_AUDIENCE_SHIRT, 3),
+ outcome.getRenderUri());
+
+ ReportImpressionRequest reportImpressionRequest =
+ new ReportImpressionRequest(outcome.getAdSelectionId(), createAdSelectionConfig());
+
+ // Performing reporting, and asserting that no exception is thrown
+ addDelayToAvoidThrottle();
+ mAdSelectionClient
+ .reportImpression(reportImpressionRequest)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // Cleanup
+ for (CustomAudience ca : customAudienceList) {
+ addDelayToAvoidThrottle(DELAY_TO_AVOID_THROTTLE_MS);
+ mCustomAudienceClient
+ .leaveCustomAudience(ca.getBuyer(), ca.getName())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
+ public void testAdSelectionAndReporting_executionHeavyJS_success() throws Exception {
+ List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+ List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+ CustomAudience customAudience1 =
+ createCustomAudience(BUYER_1, CUSTOM_AUDIENCE_SHOES, bidsForBuyer1);
+
+ // TODO(b/244530379) Make compatible with multiple buyers
+ CustomAudience customAudience2 =
+ createCustomAudience(BUYER_1, CUSTOM_AUDIENCE_SHIRT, bidsForBuyer2);
+ List<CustomAudience> customAudienceList = Arrays.asList(customAudience1, customAudience2);
+
+ String calculation_intense_logic_js =
+ "function scoreAd(ad, bid, auction_config, seller_signals,"
+ + " trusted_scoring_signals, contextual_signal, user_signal,"
+ + " custom_audience_signal) { \n"
+ + CALCULATION_INTENSE_JS
+ + " return {'status': 0, 'score': bid };\n"
+ + "}\n"
+ + "function reportResult(ad_selection_config, render_uri, bid,"
+ + " contextual_signals) { \n"
+ + " return {'status': 0, 'results': {'signals_for_buyer':"
+ + " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
+ + SELLER_REPORTING_PATH
+ + "' } };\n"
+ + "}";
+
+ Dispatcher dispatcher =
+ new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest request) {
+ if (DECISION_LOGIC_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody(calculation_intense_logic_js);
+ } else if (BUYER_BIDDING_LOGIC_URI_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody(DEFAULT_BIDDING_LOGIC_JS);
+ } else if (BUYER_REPORTING_PATH.equals(request.getPath())
+ || SELLER_REPORTING_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody("");
+ } else if (request.getPath().startsWith(TRUSTED_SCORING_SIGNAL_PATH)) {
+ return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
+ } else if (request.getPath().startsWith(VALID_TRUSTED_BIDDING_URI_PATH)) {
+ return new MockResponse().setBody(TRUSTED_BIDDING_SIGNALS.toString());
+ }
+ return new MockResponse().setResponseCode(404);
+ }
+ };
+
+ mMockWebServerRule.startMockWebServer(dispatcher);
+
+ for (CustomAudience ca : customAudienceList) {
+ addDelayToAvoidThrottle();
+ mCustomAudienceClient
+ .joinCustomAudience(ca)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ // Running ad selection and asserting that the outcome is returned in < 10 seconds
+ addDelayToAvoidThrottle();
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // The winning ad should be ad3 from CA shirt
+ Assert.assertEquals(
+ "Ad selection outcome is not expected",
+ createExpectedWinningUri(BUYER_1, CUSTOM_AUDIENCE_SHIRT, 3),
+ outcome.getRenderUri());
+
+ ReportImpressionRequest reportImpressionRequest =
+ new ReportImpressionRequest(outcome.getAdSelectionId(), createAdSelectionConfig());
+
+ // Performing reporting, and asserting that no exception is thrown
+ addDelayToAvoidThrottle();
+ mAdSelectionClient
+ .reportImpression(reportImpressionRequest)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // Cleanup
+ for (CustomAudience ca : customAudienceList) {
+ addDelayToAvoidThrottle(DELAY_TO_AVOID_THROTTLE_MS);
+ mCustomAudienceClient
+ .leaveCustomAudience(ca.getBuyer(), ca.getName())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
+ public void testAdSelectionAndReporting_memoryHeavyJS_success() throws Exception {
+ List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+ List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+ CustomAudience customAudience1 =
+ createCustomAudience(BUYER_1, CUSTOM_AUDIENCE_SHOES, bidsForBuyer1);
+
+ // TODO(b/244530379) Make compatible with multiple buyers
+ CustomAudience customAudience2 =
+ createCustomAudience(BUYER_1, CUSTOM_AUDIENCE_SHIRT, bidsForBuyer2);
+ List<CustomAudience> customAudienceList = Arrays.asList(customAudience1, customAudience2);
+
+ String memory_intense_logic_js =
+ "function scoreAd(ad, bid, auction_config, seller_signals,"
+ + " trusted_scoring_signals, contextual_signal, user_signal,"
+ + " custom_audience_signal) { \n"
+ + MEMORY_INTENSE_JS
+ + " return {'status': 0, 'score': bid };\n"
+ + "}\n"
+ + "function reportResult(ad_selection_config, render_uri, bid,"
+ + " contextual_signals) { \n"
+ + " return {'status': 0, 'results': {'signals_for_buyer':"
+ + " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
+ + SELLER_REPORTING_PATH
+ + "' } };\n"
+ + "}";
+
+ Dispatcher dispatcher =
+ new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest request) {
+ if (DECISION_LOGIC_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody(memory_intense_logic_js);
+ } else if (BUYER_BIDDING_LOGIC_URI_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody(DEFAULT_BIDDING_LOGIC_JS);
+ } else if (BUYER_REPORTING_PATH.equals(request.getPath())
+ || SELLER_REPORTING_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody("");
+ } else if (request.getPath().startsWith(TRUSTED_SCORING_SIGNAL_PATH)) {
+ return new MockResponse().setBody(TRUSTED_SCORING_SIGNALS.toString());
+ } else if (request.getPath().startsWith(VALID_TRUSTED_BIDDING_URI_PATH)) {
+ return new MockResponse().setBody(TRUSTED_BIDDING_SIGNALS.toString());
+ }
+ return new MockResponse().setResponseCode(404);
+ }
+ };
+
+ mMockWebServerRule.startMockWebServer(dispatcher);
+
+ for (CustomAudience ca : customAudienceList) {
+ addDelayToAvoidThrottle();
+ mCustomAudienceClient
+ .joinCustomAudience(ca)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ // Running ad selection and asserting that the outcome is returned in < 10 seconds
+ addDelayToAvoidThrottle();
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // The winning ad should be ad3 from CA shirt
+ Assert.assertEquals(
+ "Ad selection outcome is not expected",
+ createExpectedWinningUri(BUYER_1, CUSTOM_AUDIENCE_SHIRT, 3),
+ outcome.getRenderUri());
+
+ ReportImpressionRequest reportImpressionRequest =
+ new ReportImpressionRequest(outcome.getAdSelectionId(), createAdSelectionConfig());
+
+ // Performing reporting, and asserting that no exception is thrown
+ addDelayToAvoidThrottle();
+ mAdSelectionClient
+ .reportImpression(reportImpressionRequest)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // Cleanup
+ for (CustomAudience ca : customAudienceList) {
+ addDelayToAvoidThrottle(DELAY_TO_AVOID_THROTTLE_MS);
+ mCustomAudienceClient
+ .leaveCustomAudience(ca.getBuyer(), ca.getName())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
+ public void testAdSelectionAndReporting_multipleCustomAudienceList_success() throws Exception {
+ List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+ List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+ CustomAudience customAudience1 =
+ createCustomAudience(BUYER_1, CUSTOM_AUDIENCE_SHOES, bidsForBuyer1);
+
+ // TODO(b/244530379) Make compatible with multiple buyers
+ CustomAudience customAudience2 =
+ createCustomAudience(BUYER_1, CUSTOM_AUDIENCE_SHIRT, bidsForBuyer2);
+ List<CustomAudience> customAudienceList = new ArrayList<>();
+ customAudienceList.add(customAudience1);
+ customAudienceList.add(customAudience2);
+
+ // Create multiple generic custom audience entries
+ for (int i = 1; i <= 48; i++) {
+ CustomAudience customAudience =
+ createCustomAudience(BUYER_1, "GENERIC_CA_" + i, bidsForBuyer1);
+ customAudienceList.add(customAudience);
+ }
+ mMockWebServerRule.startMockWebServer(mDefaultDispatcher);
+
+ for (CustomAudience ca : customAudienceList) {
+ addDelayToAvoidThrottle();
+ mCustomAudienceClient
+ .joinCustomAudience(ca)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ // Running ad selection and asserting that the outcome is returned in < 10 seconds
+ addDelayToAvoidThrottle();
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // The winning ad should be ad3 from CA shirt
+ Assert.assertEquals(
+ "Ad selection outcome is not expected",
+ createExpectedWinningUri(BUYER_1, CUSTOM_AUDIENCE_SHIRT, 3),
+ outcome.getRenderUri());
+
+ ReportImpressionRequest reportImpressionRequest =
+ new ReportImpressionRequest(outcome.getAdSelectionId(), createAdSelectionConfig());
+
+ // Performing reporting, and asserting that no exception is thrown
+ addDelayToAvoidThrottle();
+ mAdSelectionClient
+ .reportImpression(reportImpressionRequest)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ // Cleanup
+ for (CustomAudience ca : customAudienceList) {
+ addDelayToAvoidThrottle(DELAY_TO_AVOID_THROTTLE_MS);
+ mCustomAudienceClient
+ .leaveCustomAudience(ca.getBuyer(), ca.getName())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+
+ private void addDelayToAvoidThrottle() throws InterruptedException {
+ addDelayToAvoidThrottle(DELAY_TO_AVOID_THROTTLE_MS);
+ }
+
+ private void addDelayToAvoidThrottle(int delayValueMs) throws InterruptedException {
+ if (delayValueMs > 0) {
+ Thread.sleep(delayValueMs);
+ }
+ }
+
+ private CustomAudience createCustomAudience(
+ final AdTechIdentifier buyer,
+ String name,
+ List<Double> bids,
+ Instant activationTime,
+ Instant expirationTime) {
+ // Generate ads for with bids provided
+ List<AdData> ads = new ArrayList<>();
+
+ // Create ads with the custom audience name and bid number as the ad URI
+ // Add the bid value to the metadata
+ for (int i = 0; i < bids.size(); i++) {
+ ads.add(
+ new AdData.Builder()
+ .setRenderUri(
+ getUri(
+ buyer.toString(),
+ AD_URI_PREFIX + name + "/ad" + (i + 1)))
+ .setMetadata("{\"result\":" + bids.get(i) + "}")
+ .build());
+ }
+
+ return new CustomAudience.Builder()
+ .setBuyer(buyer)
+ .setName(name)
+ .setActivationTime(activationTime)
+ .setExpirationTime(expirationTime)
+ .setDailyUpdateUri(getValidDailyUpdateUriByBuyer(buyer))
+ .setUserBiddingSignals(VALID_USER_BIDDING_SIGNALS)
+ .setTrustedBiddingData(getValidTrustedBiddingDataByBuyer(buyer))
+ .setBiddingLogicUri(mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH))
+ .setAds(ads)
+ .build();
+ }
+
+ private CustomAudience createCustomAudience(
+ final AdTechIdentifier buyer, String name, List<Double> bids) {
+ return createCustomAudience(
+ buyer, name, bids, VALID_ACTIVATION_TIME, VALID_EXPIRATION_TIME);
+ }
+
+ private AdSelectionConfig createAdSelectionConfig() {
+ return new AdSelectionConfig.Builder()
+ .setSeller(SELLER)
+ .setDecisionLogicUri(mMockWebServerRule.uriForPath(DECISION_LOGIC_PATH))
+ .setCustomAudienceBuyers(CUSTOM_AUDIENCE_BUYERS)
+ .setAdSelectionSignals(AD_SELECTION_SIGNALS)
+ .setSellerSignals(SELLER_SIGNALS)
+ .setPerBuyerSignals(PER_BUYER_SIGNALS)
+ .setTrustedScoringSignalsUri(
+ mMockWebServerRule.uriForPath(TRUSTED_SCORING_SIGNAL_PATH))
+ // TODO(b/244530379) Make compatible with multiple buyers
+ .setCustomAudienceBuyers(Collections.singletonList(BUYER_1))
+ .build();
+ }
+
+ private Uri createExpectedWinningUri(
+ AdTechIdentifier buyer, String customAudienceName, int adNumber) {
+ return getUri(buyer.toString(), AD_URI_PREFIX + customAudienceName + "/ad" + adNumber);
+ }
+
+ // TODO(b/244530379) Make compatible with multiple buyers
+ public Uri getValidDailyUpdateUriByBuyer(AdTechIdentifier buyer) {
+ return mMockWebServerRule.uriForPath("/update");
+ }
+
+ public TrustedBiddingData getValidTrustedBiddingDataByBuyer(AdTechIdentifier buyer) {
+ return new TrustedBiddingData.Builder()
+ .setTrustedBiddingKeys(VALID_TRUSTED_BIDDING_KEYS)
+ .setTrustedBiddingUri(getValidTrustedBiddingUriByBuyer(buyer))
+ .build();
+ }
+
+ // TODO(b/244530379) Make compatible with multiple buyers
+ public Uri getValidTrustedBiddingUriByBuyer(AdTechIdentifier buyer) {
+ return mMockWebServerRule.uriForPath(VALID_TRUSTED_BIDDING_URI_PATH);
+ }
+}
diff --git a/adservices/tests/perf/src/android/adservices/test/scenario/adservices/fledge/SelectAdsLatency.java b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/fledge/SelectAdsLatency.java
new file mode 100644
index 000000000..5af52346a
--- /dev/null
+++ b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/fledge/SelectAdsLatency.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 src.android.adservices.test.scenario.adservices.fledge;
+
+import android.adservices.adselection.AdSelectionConfig;
+import android.adservices.adselection.AdSelectionOutcome;
+import android.adservices.clients.adselection.AdSelectionClient;
+import android.adservices.clients.customaudience.AdvertisingCustomAudienceClient;
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.test.scenario.adservices.utils.CustomAudienceSetupRule;
+import android.adservices.test.scenario.adservices.utils.MockWebServerDispatcherFactory;
+import android.adservices.test.scenario.adservices.utils.MockWebServerRule;
+import android.adservices.test.scenario.adservices.utils.MockWebServerRuleFactory;
+import android.content.Context;
+import android.net.Uri;
+import android.platform.test.scenario.annotation.Scenario;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Ticker;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/** Tests to compute latency runs for on device ad selection. */
+
+// TODO(b/251802548): Consider parameterizing the tests.
+@Scenario
+@RunWith(JUnit4.class)
+public class SelectAdsLatency {
+ private static final String TAG = "SelectAds";
+
+ private static final AdTechIdentifier BUYER = AdTechIdentifier.fromString("localhost");
+ private static final List<AdTechIdentifier> CUSTOM_AUDIENCE_BUYERS =
+ Collections.singletonList(BUYER);
+ private static final AdSelectionSignals AD_SELECTION_SIGNALS =
+ AdSelectionSignals.fromString("{\"ad_selection_signals\":1}");
+ private static final AdSelectionSignals SELLER_SIGNALS =
+ AdSelectionSignals.fromString("{\"test_seller_signals\":1}");
+ private static final Map<AdTechIdentifier, AdSelectionSignals> PER_BUYER_SIGNALS =
+ Map.of(BUYER, AdSelectionSignals.fromString("{\"buyer_signals\":1}"));
+ private static final AdTechIdentifier SELLER = AdTechIdentifier.fromString("localhost");
+ private static final int API_RESPONSE_TIMEOUT_SECONDS = 100;
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+
+ // Estimates from
+ // https://docs.google.com/spreadsheets/d/1EP_cwBbwYI-NMro0Qq5uif1krwjIQhjK8fjOu15j7hQ/edit?usp=sharing&resourcekey=0-A67kzEnAKKz1k7qpshSedg
+ private static final int NUMBER_OF_CUSTOM_AUDIENCES_MEDIUM = 10;
+ private static final int NUMBER_OF_CUSTOM_AUDIENCES_LARGE = 50;
+ private static final int NUMBER_ADS_PER_CA_MEDIUM = 5;
+ private static final int NUMBER_ADS_PER_CA_LARGE = 10;
+
+ private static final String LOG_LABEL_P50_5G = "SELECT_ADS_LATENCY_P50_5G";
+ private static final String LOG_LABEL_P50_4GPLUS = "SELECT_ADS_LATENCY_P50_4GPLUS";
+ private static final String LOG_LABEL_P50_4G = "SELECT_ADS_LATENCY_P50_4G";
+ private static final String LOG_LABEL_P90_5G = "SELECT_ADS_LATENCY_P90_5G";
+ private static final String LOG_LABEL_P90_4GPLUS = "SELECT_ADS_LATENCY_P90_4GPLUS";
+ private static final String LOG_LABEL_P90_4G = "SELECT_ADS_LATENCY_P90_4G";
+
+ private static final String AD_SELECTION_FAILURE_MESSAGE =
+ "Ad selection outcome is not expected";
+
+ protected final Context mContext = ApplicationProvider.getApplicationContext();
+ private final AdSelectionClient mAdSelectionClient =
+ new AdSelectionClient.Builder()
+ .setContext(mContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+ private final AdvertisingCustomAudienceClient mCustomAudienceClient =
+ new AdvertisingCustomAudienceClient.Builder()
+ .setContext(mContext)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+ @Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
+
+ @Rule
+ public CustomAudienceSetupRule mCustomAudienceSetupRule =
+ new CustomAudienceSetupRule(mCustomAudienceClient, mMockWebServerRule);
+
+ private final Ticker mTicker =
+ new Ticker() {
+ public long read() {
+ return android.os.SystemClock.elapsedRealtimeNanos();
+ }
+ };
+
+ @BeforeClass
+ public static void setupBeforeClass() {
+ ShellUtils.runShellCommand("su 0 killall -9 com.google.android.adservices.api");
+ }
+
+ @Test
+ public void selectAds_p50_5G() throws Exception {
+ mMockWebServerRule.createMockWebServer();
+ mMockWebServerRule.startCreatedMockWebServer(
+ MockWebServerDispatcherFactory.create5Gp50LatencyDispatcher(mMockWebServerRule));
+ mCustomAudienceSetupRule.populateCustomAudiences(
+ NUMBER_OF_CUSTOM_AUDIENCES_MEDIUM, NUMBER_ADS_PER_CA_MEDIUM);
+ Stopwatch timer = Stopwatch.createStarted(mTicker);
+
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ timer.stop();
+
+ // TODO(b/250851601): Currently we use logcat latency collector. Consider replacing this
+ // with a perfetto trace collector because it won't be affected by write to logcat latency.
+ Log.i(TAG, "(" + LOG_LABEL_P50_5G + ": " + timer.elapsed(TimeUnit.MILLISECONDS) + " ms)");
+ Assert.assertEquals(
+ AD_SELECTION_FAILURE_MESSAGE,
+ createExpectedWinningUri(BUYER, "GENERIC_CA_1", NUMBER_ADS_PER_CA_MEDIUM),
+ outcome.getRenderUri());
+ }
+
+ @Test
+ public void selectAds_p50_4GPlus() throws Exception {
+ mMockWebServerRule.createMockWebServer();
+ mMockWebServerRule.startCreatedMockWebServer(
+ MockWebServerDispatcherFactory.create4GPlusp50LatencyDispatcher(
+ mMockWebServerRule));
+ mCustomAudienceSetupRule.populateCustomAudiences(
+ NUMBER_OF_CUSTOM_AUDIENCES_MEDIUM, NUMBER_ADS_PER_CA_MEDIUM);
+
+ Stopwatch timer = Stopwatch.createStarted(mTicker);
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ timer.stop();
+
+ // TODO(b/250851601): Currently we use logcat latency collector. Consider replacing this
+ // with a perfetto trace collector because it won't be affected by write to logcat latency.
+ Log.i(
+ TAG,
+ "(" + LOG_LABEL_P50_4GPLUS + ": " + timer.elapsed(TimeUnit.MILLISECONDS) + " ms)");
+ Assert.assertEquals(
+ AD_SELECTION_FAILURE_MESSAGE,
+ createExpectedWinningUri(BUYER, "GENERIC_CA_1", NUMBER_ADS_PER_CA_MEDIUM),
+ outcome.getRenderUri());
+ }
+
+ @Test
+ public void selectAds_p50_4G() throws Exception {
+ mMockWebServerRule.createMockWebServer();
+ mMockWebServerRule.startCreatedMockWebServer(
+ MockWebServerDispatcherFactory.create4Gp50LatencyDispatcher(mMockWebServerRule));
+ mCustomAudienceSetupRule.populateCustomAudiences(
+ NUMBER_OF_CUSTOM_AUDIENCES_MEDIUM, NUMBER_ADS_PER_CA_MEDIUM);
+
+ Stopwatch timer = Stopwatch.createStarted(mTicker);
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ timer.stop();
+
+ // TODO(b/250851601): Currently we use logcat latency collector. Consider replacing this
+ // with a perfetto trace collector because it won't be affected by write to logcat latency.
+ Log.i(TAG, "(" + LOG_LABEL_P50_4G + ": " + timer.elapsed(TimeUnit.MILLISECONDS) + " ms)");
+ Assert.assertEquals(
+ AD_SELECTION_FAILURE_MESSAGE,
+ createExpectedWinningUri(BUYER, "GENERIC_CA_1", NUMBER_ADS_PER_CA_MEDIUM),
+ outcome.getRenderUri());
+ }
+
+ @Test
+ public void selectAds_p90_5G() throws Exception {
+ mMockWebServerRule.createMockWebServer();
+ mMockWebServerRule.startCreatedMockWebServer(
+ MockWebServerDispatcherFactory.create5Gp90LatencyDispatcher(mMockWebServerRule));
+ mCustomAudienceSetupRule.populateCustomAudiences(
+ NUMBER_OF_CUSTOM_AUDIENCES_LARGE, NUMBER_ADS_PER_CA_LARGE);
+
+ Stopwatch timer = Stopwatch.createStarted(mTicker);
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ timer.stop();
+
+ // TODO(b/250851601): Currently we use logcat latency collector. Consider replacing this
+ // with a perfetto trace collector because it won't be affected by write to logcat latency.
+ Log.i(TAG, "(" + LOG_LABEL_P90_5G + ": " + timer.elapsed(TimeUnit.MILLISECONDS) + " ms)");
+ Assert.assertEquals(
+ AD_SELECTION_FAILURE_MESSAGE,
+ createExpectedWinningUri(BUYER, "GENERIC_CA_1", NUMBER_ADS_PER_CA_LARGE),
+ outcome.getRenderUri());
+ }
+
+ @Test
+ public void selectAds_p90_4G() throws Exception {
+ mMockWebServerRule.createMockWebServer();
+ mMockWebServerRule.startCreatedMockWebServer(
+ MockWebServerDispatcherFactory.create4Gp90LatencyDispatcher(mMockWebServerRule));
+ mCustomAudienceSetupRule.populateCustomAudiences(
+ NUMBER_OF_CUSTOM_AUDIENCES_LARGE, NUMBER_ADS_PER_CA_LARGE);
+ Stopwatch timer = Stopwatch.createStarted(mTicker);
+
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ timer.stop();
+ // TODO(b/250851601): Currently we use logcat latency collector. Consider replacing this
+ // with a perfetto trace collector because it won't be affected by write to logcat latency.
+ Log.i(TAG, "(" + LOG_LABEL_P90_4G + ": " + timer.elapsed(TimeUnit.MILLISECONDS) + " ms)");
+ Assert.assertEquals(
+ AD_SELECTION_FAILURE_MESSAGE,
+ createExpectedWinningUri(BUYER, "GENERIC_CA_1", NUMBER_ADS_PER_CA_LARGE),
+ outcome.getRenderUri());
+ }
+
+ @Test
+ public void selectAds_p90_4GPlus() throws Exception {
+ mMockWebServerRule.createMockWebServer();
+ mMockWebServerRule.startCreatedMockWebServer(
+ MockWebServerDispatcherFactory.create4GPlusp90LatencyDispatcher(
+ mMockWebServerRule));
+ mCustomAudienceSetupRule.populateCustomAudiences(
+ NUMBER_OF_CUSTOM_AUDIENCES_LARGE, NUMBER_ADS_PER_CA_LARGE);
+
+ Stopwatch timer = Stopwatch.createStarted(mTicker);
+ AdSelectionOutcome outcome =
+ mAdSelectionClient
+ .selectAds(createAdSelectionConfig())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ timer.stop();
+ // TODO(b/250851601): Currently we use logcat latency collector. Consider replacing this
+ // with a perfetto trace collector because it won't be affected by write to logcat latency.
+ Log.i(
+ TAG,
+ "(" + LOG_LABEL_P90_4GPLUS + ": " + timer.elapsed(TimeUnit.MILLISECONDS) + " ms)");
+ Assert.assertEquals(
+ AD_SELECTION_FAILURE_MESSAGE,
+ createExpectedWinningUri(BUYER, "GENERIC_CA_1", NUMBER_ADS_PER_CA_LARGE),
+ outcome.getRenderUri());
+ }
+
+ private static Uri getUri(String name, String path) {
+ return Uri.parse("https://" + name + path);
+ }
+
+ private Uri createExpectedWinningUri(
+ AdTechIdentifier buyer, String customAudienceName, int adNumber) {
+ return getUri(buyer.toString(), "/adverts/123/" + customAudienceName + "/ad" + adNumber);
+ }
+
+ private AdSelectionConfig createAdSelectionConfig() {
+ return new AdSelectionConfig.Builder()
+ .setSeller(SELLER)
+ .setDecisionLogicUri(
+ mMockWebServerRule.uriForPath(
+ MockWebServerDispatcherFactory.getDecisionLogicPath()))
+ // TODO(b/244530379) Make compatible with multiple buyers
+ .setCustomAudienceBuyers(CUSTOM_AUDIENCE_BUYERS)
+ .setAdSelectionSignals(AD_SELECTION_SIGNALS)
+ .setSellerSignals(SELLER_SIGNALS)
+ .setPerBuyerSignals(PER_BUYER_SIGNALS)
+ .setTrustedScoringSignalsUri(
+ mMockWebServerRule.uriForPath(
+ MockWebServerDispatcherFactory.getTrustedScoringSignalPath()))
+ .build();
+ }
+}
diff --git a/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/CustomAudienceSetupRule.java b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/CustomAudienceSetupRule.java
new file mode 100644
index 000000000..a9623520f
--- /dev/null
+++ b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/CustomAudienceSetupRule.java
@@ -0,0 +1,172 @@
+/*
+ * 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 android.adservices.test.scenario.adservices.utils;
+
+import android.adservices.clients.customaudience.AdvertisingCustomAudienceClient;
+import android.adservices.common.AdData;
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.customaudience.CustomAudience;
+import android.adservices.customaudience.TrustedBiddingData;
+import android.net.Uri;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class CustomAudienceSetupRule implements TestRule {
+
+ private static final AdTechIdentifier BUYER = AdTechIdentifier.fromString("localhost");
+ private static final String AD_URI_PREFIX = "/adverts/123/";
+ private static final int DELAY_TO_AVOID_THROTTLE_MS = 1001;
+ private static final int API_RESPONSE_TIMEOUT_SECONDS = 100;
+ private static final Duration CUSTOM_AUDIENCE_EXPIRE_IN = Duration.ofDays(1);
+ private static final Instant VALID_ACTIVATION_TIME = Instant.now();
+ private static final Instant VALID_EXPIRATION_TIME =
+ VALID_ACTIVATION_TIME.plus(CUSTOM_AUDIENCE_EXPIRE_IN);
+ private static final AdSelectionSignals VALID_USER_BIDDING_SIGNALS =
+ AdSelectionSignals.fromString("{'valid': 'yep', 'opaque': 'definitely'}");
+ private final List<CustomAudience> mCustomAudiences;
+ private final AdvertisingCustomAudienceClient mAdvertisingCustomAudienceClient;
+ private final android.adservices.test.scenario.adservices.utils.MockWebServerRule
+ mMockWebServerRule;
+
+ public CustomAudienceSetupRule(
+ AdvertisingCustomAudienceClient advertisingCustomAudienceClient,
+ MockWebServerRule mockWebServerRule) {
+ mAdvertisingCustomAudienceClient = advertisingCustomAudienceClient;
+ mMockWebServerRule = mockWebServerRule;
+ mCustomAudiences = new ArrayList<>();
+ }
+
+ private static Uri getUri(String name, String path) {
+ return Uri.parse("https://" + name + path);
+ }
+
+ public void populateCustomAudiences(
+ int numberOfCustomAudiences, int numberOfAdsPerCustomAudiences) throws Exception {
+ List<Double> bidsForBuyer = new ArrayList<>();
+ for (int i = 1; i <= numberOfAdsPerCustomAudiences; i++) {
+ bidsForBuyer.add(i + 0.1);
+ }
+ // Create multiple generic custom audience entries
+ for (int i = 1; i <= numberOfCustomAudiences; i++) {
+ CustomAudience customAudience =
+ createCustomAudience(BUYER, "GENERIC_CA_" + i, bidsForBuyer);
+ mCustomAudiences.add(customAudience);
+ }
+
+ for (CustomAudience ca : mCustomAudiences) {
+ addDelayToAvoidThrottle();
+ mAdvertisingCustomAudienceClient
+ .joinCustomAudience(ca)
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ } finally {
+ if (mCustomAudiences != null) {
+ for (CustomAudience ca : mCustomAudiences) {
+ addDelayToAvoidThrottle(DELAY_TO_AVOID_THROTTLE_MS);
+ mAdvertisingCustomAudienceClient
+ .leaveCustomAudience(ca.getBuyer(), ca.getName())
+ .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+ mCustomAudiences.clear();
+ }
+ }
+ }
+ };
+ }
+
+ private CustomAudience createCustomAudience(
+ final AdTechIdentifier buyer,
+ String name,
+ List<Double> bids,
+ Instant activationTime,
+ Instant expirationTime) {
+ // Generate ads for with bids provided
+ List<AdData> ads = new ArrayList<>();
+
+ // Create ads with the custom audience name and bid number as the ad URI
+ // Add the bid value to the metadata
+ for (int i = 0; i < bids.size(); i++) {
+ ads.add(
+ new AdData.Builder()
+ .setRenderUri(
+ getUri(
+ buyer.toString(),
+ AD_URI_PREFIX + name + "/ad" + (i + 1)))
+ .setMetadata("{\"result\":" + bids.get(i) + "}")
+ .build());
+ }
+
+ return new CustomAudience.Builder()
+ .setBuyer(buyer)
+ .setName(name)
+ .setActivationTime(activationTime)
+ .setExpirationTime(expirationTime)
+ .setDailyUpdateUri(mMockWebServerRule.uriForPath("/update"))
+ .setUserBiddingSignals(VALID_USER_BIDDING_SIGNALS)
+ .setTrustedBiddingData(
+ getValidTrustedBiddingDataByBuyer(
+ mMockWebServerRule.uriForPath(
+ MockWebServerDispatcherFactory
+ .getTrustedBiddingSignalsPath())))
+ .setBiddingLogicUri(
+ mMockWebServerRule.uriForPath(
+ MockWebServerDispatcherFactory.getBiddingLogicUriPath()))
+ .setAds(ads)
+ .build();
+ }
+
+ private CustomAudience createCustomAudience(
+ final AdTechIdentifier buyer, String name, List<Double> bids) {
+ return createCustomAudience(
+ buyer, name, bids, VALID_ACTIVATION_TIME, VALID_EXPIRATION_TIME);
+ }
+
+ private TrustedBiddingData getValidTrustedBiddingDataByBuyer(Uri validTrustedBiddingUri) {
+ return new TrustedBiddingData.Builder()
+ .setTrustedBiddingKeys(MockWebServerDispatcherFactory.getValidTrustedBiddingKeys())
+ .setTrustedBiddingUri(validTrustedBiddingUri)
+ .build();
+ }
+
+ private void addDelayToAvoidThrottle() throws InterruptedException {
+ addDelayToAvoidThrottle(DELAY_TO_AVOID_THROTTLE_MS);
+ }
+
+ private void addDelayToAvoidThrottle(int delayValueMs) throws InterruptedException {
+ if (delayValueMs > 0) {
+ Thread.sleep(delayValueMs);
+ }
+ }
+}
diff --git a/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerDispatcherFactory.java b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerDispatcherFactory.java
new file mode 100644
index 000000000..14005df1b
--- /dev/null
+++ b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerDispatcherFactory.java
@@ -0,0 +1,264 @@
+/*
+ * 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 android.adservices.test.scenario.adservices.utils;
+
+import android.adservices.common.AdSelectionSignals;
+
+import com.google.common.collect.ImmutableList;
+import com.google.mockwebserver.Dispatcher;
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.RecordedRequest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/** Setup the dispatcher for mock web server. */
+public final class MockWebServerDispatcherFactory {
+
+ public static final String DECISION_LOGIC_PATH = "/seller/decision/simple_logic_with_delay";
+ public static final String TRUSTED_SCORING_SIGNAL_PATH =
+ "/trusted/scoringsignals/simple_with_delay";
+ public static final String TRUSTED_BIDDING_SIGNALS_PATH =
+ "/trusted/biddingsignals/simple_with_delay";
+ public static final ArrayList<String> VALID_TRUSTED_BIDDING_KEYS =
+ new ArrayList<>(Arrays.asList("example", "valid", "list", "of", "keys"));
+ // Estimated based on
+ // https://docs.google.com/spreadsheets/d/1EP_cwBbwYI-NMro0Qq5uif1krwjIQhjK8fjOu15j7hQ/edit?usp=sharing&resourcekey=0-A67kzEnAKKz1k7qpshSedg
+ public static final int SCORING_JS_EXECUTION_TIME_p50_MS = 40;
+ public static final int BIDDING_JS_EXECUTION_TIME_p50_MS = 40;
+ public static final int SCORING_JS_EXECUTION_TIME_p90_MS = 70;
+ public static final int BIDDING_JS_EXECUTION_TIME_p90_MS = 70;
+ public static final int DECISION_LOGIC_FETCH_DELAY_5G_p50_MS = 22;
+ public static final int DECISION_LOGIC_FETCH_DELAY_5G_p90_MS = 23;
+ public static final int DECISION_LOGIC_FETCH_DELAY_4GPLUS_p50_MS = 56;
+ public static final int DECISION_LOGIC_FETCH_DELAY_4GPLUS_p90_MS = 57;
+ public static final int DECISION_LOGIC_FETCH_DELAY_4G_p50_MS = 114;
+ public static final int DECISION_LOGIC_FETCH_DELAY_4G_p90_MS = 116;
+ public static final int BIDDING_LOGIC_FETCH_DELAY_5G_p50_MS = 23;
+ public static final int BIDDING_LOGIC_FETCH_DELAY_5G_p90_MS = 25;
+ public static final int BIDDING_LOGIC_FETCH_DELAY_4GPLUS_p50_MS = 57;
+ public static final int BIDDING_LOGIC_FETCH_DELAY_4GPLUS_p90_MS = 62;
+ public static final int BIDDING_LOGIC_FETCH_DELAY_4G_p50_MS = 116;
+ public static final int BIDDING_LOGIC_FETCH_DELAY_4G_p90_MS = 128;
+ public static final int SCORING_SIGNALS_FETCH_DELAY_5G_p50_MS = 21;
+ public static final int SCORING_SIGNALS_FETCH_DELAY_5G_p90_MS = 22;
+ public static final int SCORING_SIGNALS_FETCH_DELAY_4GPLUS_p50_MS = 51;
+ public static final int SCORING_SIGNALS_FETCH_DELAY_4GPLUS_p90_MS = 52;
+ public static final int SCORING_SIGNALS_FETCH_DELAY_4G_p50_MS = 101;
+ public static final int SCORING_SIGNALS_FETCH_DELAY_4G_p90_MS = 104;
+ public static final int BIDDING_SIGNALS_FETCH_DELAY_5G_p50_MS = 22;
+ public static final int BIDDING_SIGNALS_FETCH_DELAY_5G_p90_MS = 47;
+ public static final int BIDDING_SIGNALS_FETCH_DELAY_4GPLUS_p50_MS = 53;
+ public static final int BIDDING_SIGNALS_FETCH_DELAY_4GPLUS_p90_MS = 123;
+ public static final int BIDDING_SIGNALS_FETCH_DELAY_4G_p50_MS = 105;
+ public static final int BIDDING_SIGNALS_FETCH_DELAY_4G_p90_MS = 275;
+ private static final String BUYER_REPORTING_PATH = "/reporting/buyer";
+ private static final String SELLER_REPORTING_PATH = "/reporting/seller";
+ private static final String BUYER_BIDDING_LOGIC_URI_PATH =
+ "/buyer/bidding/simple_logic_with_delay";
+ private static final String DEFAULT_DECISION_LOGIC_JS_WITH_EXECUTION_TIME_FORMAT =
+ "function scoreAd(ad, bid, auction_config, seller_signals,"
+ + " trusted_scoring_signals, contextual_signal, user_signal,"
+ + " custom_audience_signal) { \n"
+ + " const start = Date.now(); let now = start; while (now-start < %d) "
+ + "{now=Date.now();}\n"
+ + " return {'status': 0, 'score': bid };\n"
+ + "}\n"
+ + "function reportResult(ad_selection_config, render_uri, bid,"
+ + " contextual_signals) { \n"
+ + " return {'status': 0, 'results': {'signals_for_buyer':"
+ + " '{\"signals_for_buyer\":1}', 'reporting_uri': '%s"
+ + "' } };\n"
+ + "}";
+ private static final String DEFAULT_BIDDING_LOGIC_JS_WITH_EXECUTION_TIME_FORMAT =
+ "function generateBid(ad, auction_signals, per_buyer_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ + " custom_audience_signals) { \n"
+ + " const start = Date.now(); let now = start; while (now-start < %d) "
+ + "{now=Date.now();}\n"
+ + " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ + "}\n"
+ + "function reportWin(ad_selection_signals, per_buyer_signals,"
+ + " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ + " return {'status': 0, 'results': {'reporting_uri': '%s"
+ + "' } };\n"
+ + "}";
+ private static final AdSelectionSignals TRUSTED_SCORING_SIGNALS =
+ AdSelectionSignals.fromString(
+ "{\n"
+ + "\t\"render_uri_1\": \"signals_for_1\",\n"
+ + "\t\"render_uri_2\": \"signals_for_2\"\n"
+ + "}");
+ private static final AdSelectionSignals TRUSTED_BIDDING_SIGNALS =
+ AdSelectionSignals.fromString(
+ "{\n"
+ + "\t\"example\": \"example\",\n"
+ + "\t\"valid\": \"Also valid\",\n"
+ + "\t\"list\": \"list\",\n"
+ + "\t\"of\": \"of\",\n"
+ + "\t\"keys\": \"trusted bidding signal Values\"\n"
+ + "}");
+
+ public static Dispatcher create5Gp50LatencyDispatcher(MockWebServerRule mockWebServerRule) {
+ return create(
+ DECISION_LOGIC_FETCH_DELAY_5G_p50_MS,
+ BIDDING_LOGIC_FETCH_DELAY_5G_p50_MS,
+ SCORING_SIGNALS_FETCH_DELAY_5G_p50_MS,
+ BIDDING_SIGNALS_FETCH_DELAY_5G_p50_MS,
+ BIDDING_JS_EXECUTION_TIME_p50_MS,
+ SCORING_JS_EXECUTION_TIME_p50_MS,
+ mockWebServerRule);
+ }
+
+ public static Dispatcher create5Gp90LatencyDispatcher(MockWebServerRule mockWebServerRule) {
+ return create(
+ DECISION_LOGIC_FETCH_DELAY_5G_p90_MS,
+ BIDDING_LOGIC_FETCH_DELAY_5G_p90_MS,
+ SCORING_SIGNALS_FETCH_DELAY_5G_p90_MS,
+ BIDDING_SIGNALS_FETCH_DELAY_5G_p90_MS,
+ BIDDING_JS_EXECUTION_TIME_p90_MS,
+ SCORING_JS_EXECUTION_TIME_p90_MS,
+ mockWebServerRule);
+ }
+
+ public static Dispatcher create4GPlusp50LatencyDispatcher(MockWebServerRule mockWebServerRule) {
+ return create(
+ DECISION_LOGIC_FETCH_DELAY_4GPLUS_p50_MS,
+ BIDDING_LOGIC_FETCH_DELAY_4GPLUS_p50_MS,
+ SCORING_SIGNALS_FETCH_DELAY_4GPLUS_p50_MS,
+ BIDDING_SIGNALS_FETCH_DELAY_4GPLUS_p50_MS,
+ BIDDING_JS_EXECUTION_TIME_p50_MS,
+ SCORING_JS_EXECUTION_TIME_p50_MS,
+ mockWebServerRule);
+ }
+
+ public static Dispatcher create4GPlusp90LatencyDispatcher(MockWebServerRule mockWebServerRule) {
+ return create(
+ DECISION_LOGIC_FETCH_DELAY_4GPLUS_p90_MS,
+ BIDDING_LOGIC_FETCH_DELAY_4GPLUS_p90_MS,
+ SCORING_SIGNALS_FETCH_DELAY_4GPLUS_p90_MS,
+ BIDDING_SIGNALS_FETCH_DELAY_4GPLUS_p90_MS,
+ BIDDING_JS_EXECUTION_TIME_p90_MS,
+ SCORING_JS_EXECUTION_TIME_p90_MS,
+ mockWebServerRule);
+ }
+
+ public static Dispatcher create4Gp50LatencyDispatcher(MockWebServerRule mockWebServerRule) {
+ return create(
+ DECISION_LOGIC_FETCH_DELAY_4G_p50_MS,
+ BIDDING_LOGIC_FETCH_DELAY_4G_p50_MS,
+ SCORING_SIGNALS_FETCH_DELAY_4G_p50_MS,
+ BIDDING_SIGNALS_FETCH_DELAY_4G_p50_MS,
+ BIDDING_JS_EXECUTION_TIME_p50_MS,
+ SCORING_JS_EXECUTION_TIME_p50_MS,
+ mockWebServerRule);
+ }
+
+ public static Dispatcher create4Gp90LatencyDispatcher(MockWebServerRule mockWebServerRule) {
+ return create(
+ DECISION_LOGIC_FETCH_DELAY_4G_p90_MS,
+ BIDDING_LOGIC_FETCH_DELAY_4G_p90_MS,
+ SCORING_SIGNALS_FETCH_DELAY_4G_p90_MS,
+ BIDDING_SIGNALS_FETCH_DELAY_4G_p90_MS,
+ BIDDING_JS_EXECUTION_TIME_p90_MS,
+ SCORING_JS_EXECUTION_TIME_p90_MS,
+ mockWebServerRule);
+ }
+
+ private static Dispatcher create(
+ int decisionLogicFetchDelayMs,
+ int biddingLogicFetchDelayMs,
+ int scoringSignalFetchDelayMs,
+ int biddingSignalFetchDelayMs,
+ int biddingLogicExecutionRunMs,
+ int scoringLogicExecutionRunMs,
+ MockWebServerRule mockWebServerRule) {
+
+ return new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest request) {
+ if (DECISION_LOGIC_PATH.equals(request.getPath())) {
+ return new MockResponse()
+ .setBodyDelayTimeMs(decisionLogicFetchDelayMs)
+ .setBody(
+ getDecisionLogicJS(
+ scoringLogicExecutionRunMs,
+ mockWebServerRule
+ .uriForPath(SELLER_REPORTING_PATH)
+ .toString()));
+ } else if (BUYER_BIDDING_LOGIC_URI_PATH.equals(request.getPath())) {
+ return new MockResponse()
+ .setBodyDelayTimeMs(biddingLogicFetchDelayMs)
+ .setBody(
+ getBiddingLogicJS(
+ biddingLogicExecutionRunMs,
+ mockWebServerRule
+ .uriForPath(BUYER_REPORTING_PATH)
+ .toString()));
+ } else if (BUYER_REPORTING_PATH.equals(request.getPath())
+ || SELLER_REPORTING_PATH.equals(request.getPath())) {
+ return new MockResponse().setBody("");
+ } else if (request.getPath().startsWith(TRUSTED_SCORING_SIGNAL_PATH)) {
+ return new MockResponse()
+ .setBodyDelayTimeMs(scoringSignalFetchDelayMs)
+ .setBody(TRUSTED_SCORING_SIGNALS.toString());
+ } else if (request.getPath().startsWith(TRUSTED_BIDDING_SIGNALS_PATH)) {
+ return new MockResponse()
+ .setBodyDelayTimeMs(biddingSignalFetchDelayMs)
+ .setBody(TRUSTED_BIDDING_SIGNALS.toString());
+ }
+ return new MockResponse().setResponseCode(404);
+ }
+ };
+ }
+
+ public static String getBiddingLogicUriPath() {
+ return BUYER_BIDDING_LOGIC_URI_PATH;
+ }
+
+ public static String getDecisionLogicPath() {
+ return DECISION_LOGIC_PATH;
+ }
+
+ public static String getTrustedScoringSignalPath() {
+ return TRUSTED_SCORING_SIGNAL_PATH;
+ }
+
+ public static ImmutableList<String> getValidTrustedBiddingKeys() {
+ return ImmutableList.copyOf(VALID_TRUSTED_BIDDING_KEYS);
+ }
+
+ public static String getTrustedBiddingSignalsPath() {
+ return TRUSTED_BIDDING_SIGNALS_PATH;
+ }
+
+ private static String getDecisionLogicJS(
+ int scoringLogicExecutionRunMs, String sellerReportingUri) {
+ return String.format(
+ DEFAULT_DECISION_LOGIC_JS_WITH_EXECUTION_TIME_FORMAT,
+ scoringLogicExecutionRunMs,
+ sellerReportingUri);
+ }
+
+ private static String getBiddingLogicJS(
+ int biddingLogicExecutionRunMs, String buyerReportingUri) {
+ return String.format(
+ DEFAULT_BIDDING_LOGIC_JS_WITH_EXECUTION_TIME_FORMAT,
+ biddingLogicExecutionRunMs,
+ buyerReportingUri);
+ }
+}
diff --git a/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerRule.java b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerRule.java
new file mode 100644
index 000000000..ecc447977
--- /dev/null
+++ b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerRule.java
@@ -0,0 +1,289 @@
+/*
+ * 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 android.adservices.test.scenario.adservices.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.google.mockwebserver.Dispatcher;
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
+import com.google.mockwebserver.RecordedRequest;
+
+import org.junit.Assert;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ServerSocket;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+
+/** Instances of this class are not thread safe. */
+public class MockWebServerRule implements TestRule {
+ private static final int UNINITIALIZED = -1;
+ private final InputStream mCertificateInputStream;
+ private final char[] mKeyStorePassword;
+ private int mPort = UNINITIALIZED;
+ private MockWebServer mMockWebServer;
+
+ private MockWebServerRule(InputStream inputStream, String keyStorePassword) {
+ mCertificateInputStream = inputStream;
+ mKeyStorePassword = keyStorePassword == null ? null : keyStorePassword.toCharArray();
+ }
+
+ public static MockWebServerRule forHttp() {
+ return new MockWebServerRule(null, null);
+ }
+
+ /**
+ * Builds an instance of the MockWebServerRule configured for HTTPS traffic.
+ *
+ * @param context The app context used to load the PKCS12 key store
+ * @param assetName The name of the key store under the app assets folder
+ * @param keyStorePassword The password of the keystore
+ */
+ public static MockWebServerRule forHttps(
+ Context context, String assetName, String keyStorePassword) {
+ try {
+ return new MockWebServerRule(context.getAssets().open(assetName), keyStorePassword);
+ } catch (IOException ioException) {
+ throw new RuntimeException("Unable to initialize MockWebServerRule", ioException);
+ }
+ }
+
+ /**
+ * Builds an instance of the MockWebServerRule configured for HTTPS traffic.
+ *
+ * @param certificateInputStream An input stream to load the content of a PKCS12 key store
+ * @param keyStorePassword The password of the keystore
+ */
+ public static MockWebServerRule forHttps(
+ InputStream certificateInputStream, String keyStorePassword) {
+ return new MockWebServerRule(certificateInputStream, keyStorePassword);
+ }
+
+ private boolean useHttps() {
+ return Objects.nonNull(mCertificateInputStream);
+ }
+
+ public MockWebServer startMockWebServer(List<MockResponse> responses) throws Exception {
+ if (mPort == UNINITIALIZED) {
+ reserveServerListeningPort();
+ }
+
+ mMockWebServer = new MockWebServer();
+ if (useHttps()) {
+ mMockWebServer.useHttps(getTestingSslSocketFactory(), false);
+ }
+ for (MockResponse response : responses) {
+ mMockWebServer.enqueue(response);
+ }
+ mMockWebServer.play(mPort);
+ return mMockWebServer;
+ }
+
+ public MockWebServer startMockWebServer(Function<RecordedRequest, MockResponse> lambda)
+ throws Exception {
+ Dispatcher dispatcher =
+ new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest request) {
+ return lambda.apply(request);
+ }
+ };
+ return startMockWebServer(dispatcher);
+ }
+
+ public MockWebServer startMockWebServer(Dispatcher dispatcher) throws Exception {
+ if (mPort == UNINITIALIZED) {
+ reserveServerListeningPort();
+ }
+
+ mMockWebServer = new MockWebServer();
+ if (useHttps()) {
+ mMockWebServer.useHttps(getTestingSslSocketFactory(), false);
+ }
+ mMockWebServer.setDispatcher(dispatcher);
+
+ mMockWebServer.play(mPort);
+ return mMockWebServer;
+ }
+
+ public MockWebServer createMockWebServer() throws Exception {
+ if (mPort == UNINITIALIZED) {
+ reserveServerListeningPort();
+ }
+
+ mMockWebServer = new MockWebServer();
+ if (useHttps()) {
+ mMockWebServer.useHttps(getTestingSslSocketFactory(), false);
+ }
+ return mMockWebServer;
+ }
+
+ public MockWebServer startCreatedMockWebServer(Dispatcher dispatcher) throws Exception {
+ if (mMockWebServer == null || mPort == UNINITIALIZED) {
+ throw new IllegalStateException(
+ "MockWebServer is not created or the port is not reserved.");
+ }
+ mMockWebServer.setDispatcher(dispatcher);
+
+ mMockWebServer.play(mPort);
+ return mMockWebServer;
+ }
+
+ /**
+ * @return the mock web server for this rull and {@code null} if it hasn't been started yet by
+ * calling {@link #startMockWebServer(List)}.
+ */
+ public MockWebServer getMockWebServer() {
+ return mMockWebServer;
+ }
+
+ /** @return the base address the mock web server will be listening to when started. */
+ public String getServerBaseAddress() {
+ return String.format("%s://localhost:%d", useHttps() ? "https" : "http", mPort);
+ }
+
+ /**
+ * This method is equivalent to {@link MockWebServer#getUrl(String)} but it can be used before
+ * you prepare and start the server if you need to prepare responses that will reference the
+ * same test server.
+ *
+ * @return an Uri to use to reach the given {@code @path} on the mock web server.
+ */
+ public Uri uriForPath(String path) {
+ return Uri.parse(
+ String.format(
+ "%s%s%s", getServerBaseAddress(), path.startsWith("/") ? "" : "/", path));
+ }
+
+ private void reserveServerListeningPort() throws IOException {
+ ServerSocket serverSocket = new ServerSocket(38383);
+ serverSocket.setReuseAddress(true);
+ mPort = serverSocket.getLocalPort();
+ serverSocket.close();
+ }
+
+ private SSLSocketFactory getTestingSslSocketFactory()
+ throws GeneralSecurityException, IOException {
+ final KeyManagerFactory keyManagerFactory =
+ KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ keyStore.load(mCertificateInputStream, mKeyStorePassword);
+ keyManagerFactory.init(keyStore, mKeyStorePassword);
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
+ return sslContext.getSocketFactory();
+ }
+
+ /**
+ * A utility that validates that the mock web server got the expected traffic.
+ *
+ * @param mockWebServer server instance used for making requests
+ * @param expectedRequestCount the number of requests expected to be received by the server
+ * @param expectedRequests the list of URLs that should have been requested, in case of repeat
+ * requests the size of expectedRequests list could be less than the expectedRequestCount
+ * @param requestMatcher A custom matcher that dictates if the request meets the criteria of
+ * being hit or not. This allows tests to do partial match of URLs in case of params or
+ * other sub path of URL.
+ */
+ public void verifyMockServerRequests(
+ final MockWebServer mockWebServer,
+ final int expectedRequestCount,
+ final List<String> expectedRequests,
+ final RequestMatcher<String> requestMatcher) {
+
+ assertEquals(
+ "Number of expected requests does not match actual request count",
+ expectedRequestCount,
+ mockWebServer.getRequestCount());
+
+ // For parallel executions requests should be checked agnostic of order
+ final Set<String> actualRequests = new HashSet<>();
+ for (int i = 0; i < expectedRequestCount; i++) {
+ try {
+ actualRequests.add(mockWebServer.takeRequest().getPath());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ assertFalse(
+ String.format(
+ "Expected requests cannot be empty, actual requests <%s>", actualRequests),
+ expectedRequestCount != 0 && expectedRequests.isEmpty());
+
+ for (String request : expectedRequests) {
+ Assert.assertTrue(
+ String.format(
+ "Actual requests <%s> do not contain request <%s>",
+ actualRequests, request),
+ wasPathRequested(actualRequests, request, requestMatcher));
+ }
+ }
+
+ private boolean wasPathRequested(
+ final Set<String> actualRequests,
+ final String request,
+ final RequestMatcher<String> requestMatcher) {
+ for (String actualRequest : actualRequests) {
+ // Passing a custom comparator allows tests to do partial match of URLs in case of
+ // params or other sub path of URL
+ if (requestMatcher.wasRequestMade(actualRequest, request)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ reserveServerListeningPort();
+ try {
+ base.evaluate();
+ } finally {
+ if (mMockWebServer != null) {
+ mMockWebServer.shutdown();
+ }
+ }
+ }
+ };
+ }
+
+ public interface RequestMatcher<T> {
+ boolean wasRequestMade(T actualRequest, T expectedRequest);
+ }
+}
diff --git a/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerRuleFactory.java b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerRuleFactory.java
new file mode 100644
index 000000000..00f314363
--- /dev/null
+++ b/adservices/tests/perf/src/android/adservices/test/scenario/adservices/utils/MockWebServerRuleFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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 android.adservices.test.scenario.adservices.utils;
+
+import androidx.test.core.app.ApplicationProvider;
+
+/** Utility class for tests needing to mock web server calls */
+public class MockWebServerRuleFactory {
+ /** @return A mock {@link MockWebServerRule} initialized to use HTTPS. */
+ public static MockWebServerRule createForHttps() {
+ return MockWebServerRule.forHttps(
+ ApplicationProvider.getApplicationContext(),
+ "adservices_test_server.p12",
+ "adservices_test");
+ }
+
+ /** @return A mock {@link MockWebServerRule} initialized to use HTTP cleartext. */
+ public static MockWebServerRule createForHttp() {
+ return MockWebServerRule.forHttp();
+ }
+}
diff --git a/adservices/tests/perf/tests/Android.bp b/adservices/tests/perf/tests/Android.bp
new file mode 100644
index 000000000..9ef5d144e
--- /dev/null
+++ b/adservices/tests/perf/tests/Android.bp
@@ -0,0 +1,17 @@
+// Copyright (C) 2018 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
diff --git a/adservices/tests/perf/tests/Android.mk b/adservices/tests/perf/tests/Android.mk
new file mode 100644
index 000000000..17b8700a5
--- /dev/null
+++ b/adservices/tests/perf/tests/Android.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := AdServicesScenarioTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ androidx.test.runner \
+ androidx.test.rules \
+ collector-device-lib-platform \
+ common-platform-scenarios \
+ common-platform-scenario-tests \
+ guava \
+ health-testing-utils \
+ microbenchmark-device-lib \
+ platform-test-options \
+ platform-test-rules \
+ adservices-test-scenarios \
+ ub-uiautomator \
+ launcher-aosp-tapl \
+ handheld-app-helpers \
+ framework-adservices-lib \
+ hamcrest-library
+
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../util/hawkeye/res $(LOCAL_PATH)/res
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_MIN_SDK_VERSION := 24
+
+include $(BUILD_PACKAGE)
diff --git a/adservices/tests/perf/tests/AndroidManifest.xml b/adservices/tests/perf/tests/AndroidManifest.xml
new file mode 100644
index 000000000..9afc49ee0
--- /dev/null
+++ b/adservices/tests/perf/tests/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 Google Inc. All Rights Reserved. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.platform.test.scenario" >
+ <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="30" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
+ <uses-permission android:name="android.permission.DUMP" />
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
+ <uses-permission android:name="android.permission.SET_ORIENTATION" />
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <property android:name="android.adservices.AD_SERVICES_CONFIG"
+ android:resource="@xml/ad_services_config" />
+ <receiver android:name=".ScenarioBroadcastReceiver"></receiver>
+ <activity
+ android:name=".DefaultActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".sysui.theme.DummyForegroundActivity" />
+ <activity android:name=".BubbleTestActivity"
+ android:allowEmbedded="true"
+ android:documentLaunchMode="always"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:resizeableActivity="true" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.platform.test.scenario"
+ android:label="Platform Scenario-Based Tests" />
+</manifest>
diff --git a/adservices/tests/perf/tests/AndroidTest.xml b/adservices/tests/perf/tests/AndroidTest.xml
new file mode 100644
index 000000000..6c7f6b5d6
--- /dev/null
+++ b/adservices/tests/perf/tests/AndroidTest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2019 Google Inc. All Rights Reserved.
+ -->
+<configuration description="Config for integration test scenarios">
+ <option name="test-tag" value="PlatformScenarioTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- N.B. DeviceSetup will root the device. -->
+ <option name="set-test-harness" value="true" />
+ <option name="set-property" key="fw.show_multiuserui" value="1" />
+ <option name="run-command"
+ value="pm grant android.platform.test.scenario android.permission.WRITE_SECURE_SETTINGS" />
+ <option name="run-command" value="am force-stop com.google.android.apps.nexuslauncher" />
+
+ <!-- Prevent the predeferred "wifi required" setupwizard notification which makes
+ notification tests flaky by taking up space at the top of the shade.
+ 10 = USER_SETUP_PERSONALIZATION_COMPLETE -->
+ <option name="set-secure-setting" key="user_setup_personalization_state" value="10" />
+ <!-- Force-stop the setupwizard in case its already running and showing the notification -->
+ <option name="run-command" value="am force-stop com.google.android.setupwizard" />
+ <option name="run-command" value="settings put system pointer_location 1" />
+ <option name="run-command" value="settings put system show_touches 1" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="PlatformScenarioTests.apk"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="am wait-for-broadcast-idle" />
+ </target_preparer>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/android.platform.test.scenario/files" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.platform.test.scenario"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="exclude-filter" value="android.platform.test.HawkeyeUnitTest" />
+ </test>
+</configuration>
diff --git a/adservices/tests/perf/tests/assets/adservices_test_server.crt b/adservices/tests/perf/tests/assets/adservices_test_server.crt
new file mode 100644
index 000000000..2ba0441be
--- /dev/null
+++ b/adservices/tests/perf/tests/assets/adservices_test_server.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDMTCCAhmgAwIBAgIUUGiQfbpxSA7EHA6s8VScLWw9fs4wDQYJKoZIhvcNAQEF
+BQAwGjEYMBYGA1UEAwwPQWRTZXJ2aWNlc1Rlc3RzMB4XDTIyMDUwNjExNTQwNFoX
+DTMyMDUwMzExNTQwNFowGjEYMBYGA1UEAwwPQWRTZXJ2aWNlc1Rlc3RzMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApuGAM68XaPDCyRPjJBRTSmbcGIHe
++XPhUIDwHe8HMEzIyqDNPUvMGh0wAEftdfikzgNpn8Jvocb0CrT996my9XFGd4I1
+m5Cbcy7cMUkuSCnL2lVN6jQ1auSb6Pq9JsQexmwWxhp8e4+lxahepM/1leNTNMpT
+4qNboxDtSd+r6nkk+ECc2n48o84vMIwqBOnZ9taY4wFNSSCVEVRNunCMB+jS8EsL
+XBd/NymkRlt4Lb6iDwiP3HAuwe83OHlUS+YTpQILVGWMLTMHnxExRH0W9IBEM8vV
+Twxhz173adoVU7V4DFCqit7wANM4ifALJbjX96WxmSiHD9LfqUT5bkn3WQIDAQAB
+o28wbTAdBgNVHQ4EFgQUUbIG8KWcbVisSsErzh51ZJwdyM8wHwYDVR0jBBgwFoAU
+UbIG8KWcbVisSsErzh51ZJwdyM8wDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzAR
+gglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEFBQADggEBAGzlq7xedirOgTv1
+E9CVyie3sgre0C1IZvUGsULxX9Gkxy4EpCTXVOPd/5hhHaLRKR95UDJ4b+y5fNzu
+4/M5CbLsKRv2DjbYJQyDQ779m7qsSxwI08eIxO8korHFW0g225D51vFGRDM0tX5Z
+Kgdk94P0rlbo8v+fW0ANJ1ZEuqdwtFc/Sx62tc14j/M/Ju8PUsXYgquPPi4W3lAv
+gLPkgIa1o+1puJ+VKxm4wns464ZZ0DdRGIPZMGBNZIzama6keAyseSTmtG8xRsgF
+9DR4dPUDxe1t8hWSZxBuqACAWykzM/X2L/v96MnBto0gUVDpjtqRahVH4N3Cxz+s
+tJ+0wsY=
+-----END CERTIFICATE-----
diff --git a/adservices/tests/perf/tests/assets/adservices_test_server.p12 b/adservices/tests/perf/tests/assets/adservices_test_server.p12
new file mode 100644
index 000000000..e33e209fe
--- /dev/null
+++ b/adservices/tests/perf/tests/assets/adservices_test_server.p12
Binary files differ
diff --git a/adservices/tests/perf/tests/res/xml/ad_services_config.xml b/adservices/tests/perf/tests/res/xml/ad_services_config.xml
new file mode 100644
index 000000000..d3cc5abf0
--- /dev/null
+++ b/adservices/tests/perf/tests/res/xml/ad_services_config.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Copyright 2022 Google Inc. All Rights Reserved.-->
+<ad-services-config>
+ <includes-sdk-library name="E1" />
+ <topics allowAllToAccess="false" allowAdPartnersToAccess="E1" />
+ <custom-audiences allowAllToAccess="true" />
+ <attribution allowAllToAccess="true" />
+</ad-services-config> \ No newline at end of file
diff --git a/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/MeasurementRegisterCallsMicrobenchmark.java b/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/MeasurementRegisterCallsMicrobenchmark.java
new file mode 100644
index 000000000..2f3be003e
--- /dev/null
+++ b/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/MeasurementRegisterCallsMicrobenchmark.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.adservices.test.scenario.adservices;
+
+import android.platform.test.microbenchmark.Microbenchmark;
+import android.platform.test.rule.DropCachesRule;
+import android.platform.test.rule.KillAppsRule;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+@RunWith(Microbenchmark.class)
+public class MeasurementRegisterCallsMicrobenchmark extends MeasurementRegisterCalls {
+ @Rule
+ public RuleChain rules =
+ RuleChain.outerRule(new KillAppsRule("com.google.android.adservices.api"))
+ .around(new DropCachesRule());
+}
diff --git a/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/fledge/LimitPerfTestsMicroBenchmark.java b/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/fledge/LimitPerfTestsMicroBenchmark.java
new file mode 100644
index 000000000..f5162d386
--- /dev/null
+++ b/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/fledge/LimitPerfTestsMicroBenchmark.java
@@ -0,0 +1,24 @@
+/*
+ * 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 android.adservices.test.scenario.adservices.fledge;
+
+import android.platform.test.microbenchmark.Microbenchmark;
+
+import org.junit.runner.RunWith;
+
+@RunWith(Microbenchmark.class)
+public class LimitPerfTestsMicroBenchmark extends LimitPerfTests {}
diff --git a/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/fledge/SelectAdsLatencyMicroBenchmark.java b/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/fledge/SelectAdsLatencyMicroBenchmark.java
new file mode 100644
index 000000000..62c02fd53
--- /dev/null
+++ b/adservices/tests/perf/tests/src/android/adservices/test/scenario/adservices/fledge/SelectAdsLatencyMicroBenchmark.java
@@ -0,0 +1,26 @@
+/*
+ * 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 android.adservices.test.scenario.adservices.fledge;
+
+import android.platform.test.microbenchmark.Microbenchmark;
+
+import org.junit.runner.RunWith;
+
+import src.android.adservices.test.scenario.adservices.fledge.SelectAdsLatency;
+
+@RunWith(Microbenchmark.class)
+public class SelectAdsLatencyMicroBenchmark extends SelectAdsLatency {}
diff --git a/adservices/tests/unittest/fixtures/java/android/adservices/customaudience/TrustedBiddingDataFixture.java b/adservices/tests/unittest/fixtures/java/android/adservices/customaudience/TrustedBiddingDataFixture.java
index 9a1bda42a..a0fb171b8 100644
--- a/adservices/tests/unittest/fixtures/java/android/adservices/customaudience/TrustedBiddingDataFixture.java
+++ b/adservices/tests/unittest/fixtures/java/android/adservices/customaudience/TrustedBiddingDataFixture.java
@@ -20,15 +20,21 @@ import android.adservices.common.AdTechIdentifier;
import android.adservices.common.CommonFixture;
import android.net.Uri;
+import com.google.common.collect.ImmutableList;
+
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.List;
/** Utility class supporting custom audience API unit tests */
public class TrustedBiddingDataFixture {
public static final String VALID_TRUSTED_BIDDING_URI_PATH = "/trusted/bidding/";
- public static final ArrayList<String> VALID_TRUSTED_BIDDING_KEYS = new ArrayList<String>(
- Arrays.asList("example", "valid", "list", "of", "keys"));
+ public static final ImmutableList<String> VALID_TRUSTED_BIDDING_KEYS =
+ ImmutableList.of("example", "valid", "list", "of", "keys");
+
+ public static List<String> getValidTrustedBiddingKeys() {
+ return new ArrayList<>(VALID_TRUSTED_BIDDING_KEYS);
+ }
public static Uri getValidTrustedBiddingUriByBuyer(AdTechIdentifier buyer) {
return CommonFixture.getUri(buyer, VALID_TRUSTED_BIDDING_URI_PATH);
@@ -36,7 +42,7 @@ public class TrustedBiddingDataFixture {
public static TrustedBiddingData getValidTrustedBiddingDataByBuyer(AdTechIdentifier buyer) {
return new TrustedBiddingData.Builder()
- .setTrustedBiddingKeys(VALID_TRUSTED_BIDDING_KEYS)
+ .setTrustedBiddingKeys(getValidTrustedBiddingKeys())
.setTrustedBiddingUri(getValidTrustedBiddingUriByBuyer(buyer))
.build();
}
diff --git a/adservices/tests/unittest/fixtures/java/com/android/adservices/customaudience/DBTrustedBiddingDataFixture.java b/adservices/tests/unittest/fixtures/java/com/android/adservices/customaudience/DBTrustedBiddingDataFixture.java
index 14428aa4e..77ae1dd7b 100644
--- a/adservices/tests/unittest/fixtures/java/com/android/adservices/customaudience/DBTrustedBiddingDataFixture.java
+++ b/adservices/tests/unittest/fixtures/java/com/android/adservices/customaudience/DBTrustedBiddingDataFixture.java
@@ -21,10 +21,11 @@ import android.adservices.customaudience.TrustedBiddingDataFixture;
import com.android.adservices.data.customaudience.DBTrustedBiddingData;
+
public class DBTrustedBiddingDataFixture {
public static DBTrustedBiddingData.Builder getValidBuilderByBuyer(AdTechIdentifier buyer) {
return new DBTrustedBiddingData.Builder()
.setUri(TrustedBiddingDataFixture.getValidTrustedBiddingUriByBuyer(buyer))
- .setKeys(TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS);
+ .setKeys(TrustedBiddingDataFixture.getValidTrustedBiddingKeys());
}
}
diff --git a/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/AsyncRegistrationFixture.java b/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/AsyncRegistrationFixture.java
new file mode 100644
index 000000000..65a0f05b7
--- /dev/null
+++ b/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/AsyncRegistrationFixture.java
@@ -0,0 +1,62 @@
+/*
+ * 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.measurement;
+
+import android.net.Uri;
+
+import java.util.UUID;
+
+public class AsyncRegistrationFixture {
+ private AsyncRegistrationFixture() {}
+
+ public static AsyncRegistration getValidAsyncRegistration() {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(UUID.randomUUID().toString())
+ .setRegistrationUri(ValidAsyncRegistrationParams.REGISTRATION_URI)
+ .setOsDestination(ValidAsyncRegistrationParams.OS_DESTINATION)
+ .setWebDestination(ValidAsyncRegistrationParams.WEB_DESTINATION)
+ .setVerifiedDestination(ValidAsyncRegistrationParams.VERIFIED_DESTINATION)
+ .setRegistrant(ValidAsyncRegistrationParams.REGISTRANT)
+ .setTopOrigin(ValidAsyncRegistrationParams.TOP_ORIGIN)
+ .setRedirectType(ValidAsyncRegistrationParams.REDIRECT_TYPE)
+ .setRedirectCount(ValidAsyncRegistrationParams.REDIRECT_COUNT)
+ .setSourceType(ValidAsyncRegistrationParams.SOURCE_TYPE)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(ValidAsyncRegistrationParams.RETRY_COUNT)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setType(ValidAsyncRegistrationParams.TYPE.ordinal())
+ .setDebugKeyAllowed(ValidAsyncRegistrationParams.DEBUG_KEY_ALLOWED)
+ .build();
+ }
+
+ public static class ValidAsyncRegistrationParams {
+ public static final Source.SourceType SOURCE_TYPE = Source.SourceType.EVENT;
+ public static final long RETRY_COUNT = 0;
+ public static final Uri REGISTRATION_URI = Uri.parse("android-app://com.example");
+ public static final Uri OS_DESTINATION = Uri.parse("android-app://com.example");
+ public static final Uri WEB_DESTINATION = Uri.parse("https://com.example");
+ public static final Uri VERIFIED_DESTINATION = Uri.parse("android-app://com.example");
+ public static final Uri REGISTRANT = Uri.parse("android-app://com.example");
+ public static final Uri TOP_ORIGIN = Uri.parse("android-app://com.example");
+ public static final @AsyncRegistration.RedirectType int REDIRECT_TYPE = 1;
+ public static final int REDIRECT_COUNT = 0;
+ public static final boolean DEBUG_KEY_ALLOWED = true;
+ public static final AsyncRegistration.RegistrationType TYPE =
+ AsyncRegistration.RegistrationType.APP_SOURCE;
+ }
+}
diff --git a/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/SourceFixture.java b/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/SourceFixture.java
index 310711364..4319eb44e 100644
--- a/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/SourceFixture.java
+++ b/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/SourceFixture.java
@@ -20,7 +20,7 @@ import android.net.Uri;
import com.android.adservices.LogUtil;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionSource;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
@@ -63,14 +63,14 @@ public final class SourceFixture {
.setInstallCooldownWindow(ValidSourceParams.INSTALL_COOLDOWN_WINDOW)
.setAttributionMode(ValidSourceParams.ATTRIBUTION_MODE)
.setAggregateSource(ValidSourceParams.buildAggregateSource())
- .setAggregateFilterData(ValidSourceParams.buildAggregateFilterData())
+ .setFilterData(ValidSourceParams.buildFilterData())
.build();
}
public static class ValidSourceParams {
public static final Long EXPIRY_TIME = 8640000010L;
public static final Long PRIORITY = 100L;
- public static final Long SOURCE_EVENT_ID = 1L;
+ public static final UnsignedLong SOURCE_EVENT_ID = new UnsignedLong(1L);
public static final Long SOURCE_EVENT_TIME = 8640000000L;
public static final Uri ATTRIBUTION_DESTINATION =
Uri.parse("android-app://com.destination");
@@ -81,7 +81,7 @@ public final class SourceFixture {
public static final Source.SourceType SOURCE_TYPE = Source.SourceType.EVENT;
public static final Long INSTALL_ATTRIBUTION_WINDOW = 841839879274L;
public static final Long INSTALL_COOLDOWN_WINDOW = 8418398274L;
- public static final Long DEBUG_KEY = 7834690L;
+ public static final UnsignedLong DEBUG_KEY = new UnsignedLong(7834690L);
public static final @Source.AttributionMode int ATTRIBUTION_MODE =
Source.AttributionMode.TRUTHFULLY;
public static final int AGGREGATE_CONTRIBUTIONS = 0;
@@ -100,7 +100,7 @@ public final class SourceFixture {
return null;
}
- public static final String buildAggregateFilterData() {
+ public static final String buildFilterData() {
try {
JSONObject filterData = new JSONObject();
filterData.put("conversion_subdomain",
@@ -116,8 +116,8 @@ public final class SourceFixture {
public static final AggregatableAttributionSource buildAggregatableAttributionSource() {
return new AggregatableAttributionSource.Builder()
.setAggregatableSource(Map.of("5", new BigInteger("345")))
- .setAggregateFilterData(
- new AggregateFilterData.Builder()
+ .setFilterData(
+ new FilterData.Builder()
.setAttributionFilterMap(
Map.of(
"product", List.of("1234", "4321"),
diff --git a/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/TriggerFixture.java b/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/TriggerFixture.java
index 9018725dc..c3cc91d6a 100644
--- a/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/TriggerFixture.java
+++ b/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/TriggerFixture.java
@@ -19,8 +19,8 @@ package com.android.adservices.service.measurement;
import android.net.Uri;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionTrigger;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
import com.android.adservices.service.measurement.aggregation.AggregateTriggerData;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import java.math.BigInteger;
import java.util.List;
@@ -51,6 +51,7 @@ public final class TriggerFixture {
.setAggregateTriggerData(ValidTriggerParams.AGGREGATE_TRIGGER_DATA)
.setAggregateValues(ValidTriggerParams.AGGREGATE_VALUES)
.setFilters(ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING)
+ .setNotFilters(ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING)
.build();
}
@@ -66,6 +67,9 @@ public final class TriggerFixture {
+ " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ "}\n";
+ public static final String TOP_LEVEL_NOT_FILTERS_JSON_STRING =
+ "{\"geo\": [], \"source_type\": [\"event\"]}";
+
public static final String EVENT_TRIGGERS =
"[\n"
+ "{\n"
@@ -100,11 +104,11 @@ public final class TriggerFixture {
+ "\"geoValue\":1664"
+ "}";
- public static final Long DEBUG_KEY = 27836L;
+ public static final UnsignedLong DEBUG_KEY = new UnsignedLong(27836L);
public static final AggregatableAttributionTrigger buildAggregatableAttributionTrigger() {
- final AggregateFilterData filter =
- new AggregateFilterData.Builder()
+ final FilterData filter =
+ new FilterData.Builder()
.setAttributionFilterMap(
Map.of(
"product",
diff --git a/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/aggregation/AggregateReportFixture.java b/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/aggregation/AggregateReportFixture.java
index a84b61c7e..d481e86d5 100644
--- a/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/aggregation/AggregateReportFixture.java
+++ b/adservices/tests/unittest/fixtures/java/com/android/adservices/service/measurement/aggregation/AggregateReportFixture.java
@@ -20,6 +20,7 @@ import android.net.Uri;
import com.android.adservices.LogUtil;
import com.android.adservices.service.measurement.EventReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONException;
@@ -34,9 +35,9 @@ public final class AggregateReportFixture {
private static final long MIN_TIME_MS = TimeUnit.MINUTES.toMillis(10L);
private static final long MAX_TIME_MS = TimeUnit.MINUTES.toMillis(60L);
- // Assume the field values in this AggregateReport have no relation to the field
+ // Assume the field values in this AggregateReport.Builder have no relation to the field
// values in {@link ValidAggregateReportParams}
- public static AggregateReport getValidAggregateReport() {
+ public static AggregateReport.Builder getValidAggregateReportBuilder() {
return new AggregateReport.Builder()
.setPublisher(ValidAggregateReportParams.PUBLISHER)
.setAttributionDestination(ValidAggregateReportParams.ATTRIBUTION_DESTINATION)
@@ -47,7 +48,11 @@ public final class AggregateReportFixture {
.setTriggerDebugKey(ValidAggregateReportParams.TRIGGER_DEBUG_KEY)
.setDebugCleartextPayload(ValidAggregateReportParams.getDebugPayload())
.setStatus(EventReport.Status.PENDING)
- .build();
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING);
+ }
+
+ public static AggregateReport getValidAggregateReport() {
+ return getValidAggregateReportBuilder().build();
}
public static class ValidAggregateReportParams {
@@ -56,8 +61,8 @@ public final class AggregateReportFixture {
Uri.parse("android-app://com.destination");
public static final long SOURCE_REGISTRATION_TIME = 8640000000L;
public static final long TRIGGER_TIME = 8640000000L;
- public static final long SOURCE_DEBUG_KEY = 43254545L;
- public static final long TRIGGER_DEBUG_KEY = 67878545L;
+ public static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(43254545L);
+ public static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(67878545L);
public static final String ENROLLMENT_ID = "enrollment-id";
public static final String getDebugPayload() {
diff --git a/adservices/tests/unittest/fixtures/java/com/android/adservices/stats/FledgeApiCallStatsMatcher.java b/adservices/tests/unittest/fixtures/java/com/android/adservices/stats/FledgeApiCallStatsMatcher.java
index a2235fa4f..d8cbf3dde 100644
--- a/adservices/tests/unittest/fixtures/java/com/android/adservices/stats/FledgeApiCallStatsMatcher.java
+++ b/adservices/tests/unittest/fixtures/java/com/android/adservices/stats/FledgeApiCallStatsMatcher.java
@@ -19,8 +19,6 @@ package com.android.adservices.stats;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__FLEDGE;
-import static org.mockito.Mockito.argThat;
-
import com.android.adservices.service.stats.ApiCallStats;
import org.mockito.ArgumentMatcher;
@@ -55,9 +53,4 @@ public class FledgeApiCallStatsMatcher implements ArgumentMatcher<ApiCallStats>
mExpectedApiName,
mExpectedResultCode);
}
-
- public static ApiCallStats aCallStatForFledgeApiWithStatus(
- int expectedApiName, int expectedResultCode) {
- return argThat(new FledgeApiCallStatsMatcher(expectedApiName, expectedResultCode));
- }
}
diff --git a/adservices/tests/unittest/framework/Android.bp b/adservices/tests/unittest/framework/Android.bp
index 56cb70e1c..93b60cdd1 100644
--- a/adservices/tests/unittest/framework/Android.bp
+++ b/adservices/tests/unittest/framework/Android.bp
@@ -38,7 +38,7 @@ android_test {
],
sdk_version: "module_current",
min_sdk_version: "Tiramisu",
- target_sdk_version: "current",
+ target_sdk_version: "Tiramisu",
test_suites: [
"general-tests",
"mts-adservices"
diff --git a/adservices/tests/unittest/framework/src/android/adservices/measurement/RegistrationRequestTest.java b/adservices/tests/unittest/framework/src/android/adservices/measurement/RegistrationRequestTest.java
index de26bdde9..59ffad860 100644
--- a/adservices/tests/unittest/framework/src/android/adservices/measurement/RegistrationRequestTest.java
+++ b/adservices/tests/unittest/framework/src/android/adservices/measurement/RegistrationRequestTest.java
@@ -43,7 +43,6 @@ public final class RegistrationRequestTest {
private RegistrationRequest createExampleAttribution() {
return new RegistrationRequest.Builder()
.setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setTopOriginUri(Uri.parse("http://foo.com"))
.setRegistrationUri(Uri.parse("http://baz.com"))
.setPackageName(sContext.getAttributionSource().getPackageName())
.setRequestTime(1000L)
@@ -52,7 +51,6 @@ public final class RegistrationRequestTest {
}
void verifyExampleAttribution(RegistrationRequest request) {
- assertEquals("http://foo.com", request.getTopOriginUri().toString());
assertEquals("http://baz.com", request.getRegistrationUri().toString());
assertEquals(RegistrationRequest.REGISTER_SOURCE,
request.getRegistrationType());
@@ -92,8 +90,6 @@ public final class RegistrationRequestTest {
.setPackageName(sContext.getAttributionSource().getPackageName())
.setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
.build();
- assertEquals("android-app://" + sContext.getAttributionSource().getPackageName(),
- request.getTopOriginUri().toString());
assertEquals("", request.getRegistrationUri().toString());
assertEquals(RegistrationRequest.REGISTER_TRIGGER,
request.getRegistrationType());
diff --git a/adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsParamTest.java b/adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsParamTest.java
index e9372ace1..cb4216554 100644
--- a/adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsParamTest.java
+++ b/adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsParamTest.java
@@ -38,11 +38,13 @@ public final class GetTopicsParamTest {
.setAppPackageName(SOME_PACKAGE_NAME)
.setSdkName(SOME_SDK_NAME)
.setSdkPackageName(SOME_SDK_PACKAGE_NAME)
+ .setShouldRecordObservation(false)
.build();
assertThat(request.getSdkName()).isEqualTo(SOME_SDK_NAME);
assertThat(request.getSdkPackageName()).isEqualTo(SOME_SDK_PACKAGE_NAME);
assertThat(request.getAppPackageName()).isEqualTo(SOME_PACKAGE_NAME);
+ assertThat(request.shouldRecordObservation()).isEqualTo(false);
}
@Test
@@ -82,4 +84,20 @@ public final class GetTopicsParamTest {
.build();
});
}
+
+ @Test
+ public void test_notSettingRecordObservation_returnDefault() {
+ GetTopicsParam request =
+ new GetTopicsParam.Builder()
+ .setAppPackageName(SOME_PACKAGE_NAME)
+ .setSdkName(SOME_SDK_NAME)
+ .setSdkPackageName(SOME_SDK_PACKAGE_NAME)
+ .build();
+
+ assertThat(request.getSdkName()).isEqualTo(SOME_SDK_NAME);
+ assertThat(request.getSdkPackageName()).isEqualTo(SOME_SDK_PACKAGE_NAME);
+ assertThat(request.getAppPackageName()).isEqualTo(SOME_PACKAGE_NAME);
+ // Not setting RecordObservation will get default value.
+ assertThat(request.shouldRecordObservation()).isTrue();
+ }
}
diff --git a/adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsRequestTest.java b/adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsRequestTest.java
index 125002601..d2d788662 100644
--- a/adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsRequestTest.java
+++ b/adservices/tests/unittest/framework/src/android/adservices/topics/GetTopicsRequestTest.java
@@ -18,7 +18,6 @@ package android.adservices.topics;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import androidx.test.filters.SmallTest;
@@ -33,24 +32,49 @@ public final class GetTopicsRequestTest {
private static final String SOME_SDK_NAME = "SomeSDKName";
@Test
- public void testCreate() {
- GetTopicsRequest request = GetTopicsRequest.create();
- assertNull(request.getAdsSdkName());
+ public void testBuilder_notSettingSdkName() {
+ GetTopicsRequest request = new GetTopicsRequest.Builder().build();
+ assertThat(request.getAdsSdkName()).isEmpty();
+ // RecordObservation default value is true
+ assertThat(request.shouldRecordObservation()).isTrue();
}
@Test
- public void testCreateWithAdsSdkName_nullSdkName() {
+ public void testBuilderSetAdsSdkName_nullSdkName() {
assertThrows(
IllegalArgumentException.class,
() -> {
- GetTopicsRequest.createWithAdsSdkName(/* adsSdkName */ null);
+ new GetTopicsRequest.Builder().setAdsSdkName(/* adsSdkName */ null).build();
});
}
@Test
- public void testCreateWithAdsSdkName_nonNullSdkName() {
+ public void testBuilderSetAdsSdkName_nonNullSdkName() {
GetTopicsRequest request =
- GetTopicsRequest.createWithAdsSdkName(/* adsSdkName */ SOME_SDK_NAME);
+ new GetTopicsRequest.Builder()
+ .setAdsSdkName(/* adsSdkName */ SOME_SDK_NAME)
+ .build();
assertThat(request.getAdsSdkName()).isEqualTo(SOME_SDK_NAME);
+ // RecordObservation default value is true
+ assertThat(request.shouldRecordObservation()).isTrue();
+ }
+
+ @Test
+ public void testBuilderSetAdsSdkName_recordObservationFalse() {
+ GetTopicsRequest request =
+ new GetTopicsRequest.Builder()
+ .setAdsSdkName(/* adsSdkName */ SOME_SDK_NAME)
+ .setShouldRecordObservation(false)
+ .build();
+ assertThat(request.getAdsSdkName()).isEqualTo(SOME_SDK_NAME);
+ assertThat(request.shouldRecordObservation()).isFalse();
+ }
+
+ @Test
+ public void testBuilder_recordObservationFalse() {
+ GetTopicsRequest request =
+ new GetTopicsRequest.Builder().setShouldRecordObservation(false).build();
+ assertThat(request.getAdsSdkName()).isEmpty();
+ assertThat(request.shouldRecordObservation()).isFalse();
}
}
diff --git a/adservices/tests/unittest/service-core/Android.bp b/adservices/tests/unittest/service-core/Android.bp
index 028b1cd7c..6447e2b99 100644
--- a/adservices/tests/unittest/service-core/Android.bp
+++ b/adservices/tests/unittest/service-core/Android.bp
@@ -41,11 +41,14 @@ android_test {
"truth-prebuilt",
"ub-uiautomator",
"adservices-assets",
+ "adservices-service-core-schema",
"androidx.room_room-runtime",
"androidx.room_room-testing",
"adservices-test-fixtures",
"mobile_data_downloader_lib",
"tflite_support_classifiers_java",
+ "opencensus-java-api",
+ "opencensus-java-contrib-grpc-metrics",
],
libs: [
"android.test.base",
diff --git a/adservices/tests/unittest/service-core/assets/attribution_service_test.json b/adservices/tests/unittest/service-core/assets/attribution_service_test.json
index 3827439cf..50a46d0ab 100644
--- a/adservices/tests/unittest/service-core/assets/attribution_service_test.json
+++ b/adservices/tests/unittest/service-core/assets/attribution_service_test.json
@@ -12,7 +12,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -50,7 +50,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -77,16 +77,18 @@
],
"event_reports": [{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 101,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T1"
}],
"attributions": [
{
@@ -97,12 +99,93 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
},
{
+ "name": "Expired source",
+ "input": {
+ "sources": [
+ {
+ "id": "S1",
+ "eventId": 1,
+ "sourceType": "navigation",
+ "publisher": "android-app://com.example.abc",
+ "appDestination": "android-app://com.example2.app",
+ "enrollmentId": "enrollment-id-3",
+ "eventTime": 1643235573000,
+ "expiryTime": 1643408373000,
+ "priority": 100,
+ "status": 0,
+ "registrant": "android-app://com.example.abc"
+ }
+ ],
+ "triggers": [
+ {
+ "id": "T1",
+ "attributionDestination": "android-app://com.example2.app",
+ "enrollmentId": "enrollment-id-3",
+ "status": 0,
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 1643408373000,
+ "registrant": "android-app://com.example.xyz"
+ }
+ ],
+ "event_reports": [],
+ "attributions": []
+ },
+ "output": {
+ "sources": [
+ {
+ "id": "S1",
+ "eventId": 1,
+ "sourceType": "navigation",
+ "publisher": "android-app://com.example.abc",
+ "appDestination": "android-app://com.example2.app",
+ "enrollmentId": "enrollment-id-3",
+ "eventTime": 1643235573000,
+ "expiryTime": 1643408373000,
+ "priority": 100,
+ "status": 0,
+ "registrant": "android-app://com.example.abc"
+ }
+ ],
+ "triggers": [
+ {
+ "id": "T1",
+ "attributionDestination": "android-app://com.example2.app",
+ "enrollmentId": "enrollment-id-3",
+ "status": 1,
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 1643408373000,
+ "registrant": "android-app://com.example.xyz"
+ }
+ ],
+ "event_reports": [],
+ "attributions": []
+ }
+ },
+ {
"name": "No matching sources",
"input": {
"sources": [
@@ -194,7 +277,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -208,7 +291,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 200,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -247,7 +330,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 1,
"registrant": "android-app://com.example.abc"
@@ -261,7 +344,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 200,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -288,16 +371,18 @@
],
"event_reports": [{
"id": "unused",
- "sourceId": 2,
- "attributionDestination": "android-app://com.example2.app/d1",
- "reportTime": 8643600030,
+ "sourceEventId": 2,
+ "attributionDestination": "android-app://com.example2.app",
+ "reportTime": 8730000001,
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
"triggerTime": 8640000002,
"triggerPriority": 101,
"status": 0,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S2",
+ "triggerId": "T1"
}],
"attributions": [
{
@@ -308,7 +393,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S2",
+ "triggerId": "T1"
}
]
}
@@ -325,7 +412,7 @@
"appDestination": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -338,7 +425,7 @@
"appDestination": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000002,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400002,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -376,7 +463,7 @@
"appDestination": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 1,
"registrant": "android-app://com.example.abc"
@@ -389,7 +476,7 @@
"appDestination": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000002,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400002,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -416,16 +503,18 @@
],
"event_reports": [{
"id": "unused",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.example.xyz",
- "reportTime": 8643600030,
+ "reportTime": 8730000002,
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
"triggerTime": 8640000003,
"triggerPriority": 101,
"status": 0,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S2",
+ "triggerId": "T1"
}],
"attributions": [
{
@@ -436,7 +525,9 @@
"destinationOrigin": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000003,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S2",
+ "triggerId": "T1"
}
]
}
@@ -550,7 +641,7 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -559,7 +650,9 @@
"status": 0,
"reportTime": 8816400000,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S2",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -571,7 +664,9 @@
"destinationOrigin": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S2",
+ "triggerId": "T1"
}
]
}
@@ -686,7 +781,7 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -695,7 +790,9 @@
"status": 0,
"reportTime": 9248400001,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -707,7 +804,9 @@
"destinationOrigin": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerTime": 9072000001,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
@@ -822,7 +921,7 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerData": 1,
@@ -831,7 +930,9 @@
"status": 0,
"reportTime": 8816400000,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000125
+ "randomizedTriggerRate": 0.0000125,
+ "sourceId": "S2",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -843,7 +944,9 @@
"destinationOrigin": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S2",
+ "triggerId": "T1"
}
]
}
@@ -957,7 +1060,7 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerData": 0,
@@ -966,7 +1069,9 @@
"status": 0,
"reportTime": 10371600000,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -978,7 +1083,9 @@
"destinationOrigin": "android-app://com.example.xyz",
"enrollmentId": "enrollment-id-3",
"triggerTime": 9072000001,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
@@ -997,7 +1104,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1036,7 +1143,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1063,16 +1170,18 @@
],
"event_reports": [{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 1,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 101,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T1"
}],
"attributions": [
{
@@ -1083,7 +1192,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
@@ -1101,7 +1212,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1139,7 +1250,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1166,16 +1277,18 @@
],
"event_reports": [{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 7,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 101,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T1"
}],
"attributions": [
{
@@ -1186,7 +1299,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
@@ -1204,7 +1319,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1255,7 +1370,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1295,16 +1410,18 @@
}],
"event_reports": [{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 0,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 101,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T1"
}],
"attributions": [{
"id": "unused",
@@ -1314,7 +1431,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}]
}
},
@@ -1331,7 +1450,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1382,7 +1501,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1422,16 +1541,18 @@
}],
"event_reports": [{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 1,
"triggerTime": 8640000003,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 102,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T2"
}],
"attributions": [
{
@@ -1442,7 +1563,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "unused2",
@@ -1452,7 +1575,157 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000003,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T2"
+ }]
+ }
+ },
+ {
+ "name": "Replace low priority report - event - shared public suffix + 1",
+ "description": "1 source, 2 triggers. New trigger with destination with a higher priority and a different subdomain but shared public suffix + 1 should be attributed. Event report should only contain public suffix + 1.",
+ "input": {
+ "sources": [{
+ "id": "S1",
+ "eventId": 1,
+ "sourceType": "event",
+ "publisher": "https://www.example1.com",
+ "publisherType": 1,
+ "webDestination": "https://example.com",
+ "enrollmentId": "enrollment-id-3",
+ "eventTime": 8640000001,
+ "expiryTime": 8726400001,
+ "priority": 100,
+ "status": 0,
+ "registrant": "android-app://com.example.abc"
+ }],
+ "triggers": [{
+ "id": "T1",
+ "attributionDestination": "https://example.com/some/path?query=1",
+ "destinationType": 1,
+ "enrollmentId": "enrollment-id-3",
+ "status": 0,
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 8640000002,
+ "registrant": "android-app://com.example.xyz"
+ }, {
+ "id": "T2",
+ "attributionDestination": "https://subdomain.example.com/another/path?queries=2",
+ "destinationType": 1,
+ "enrollmentId": "enrollment-id-3",
+ "status": 0,
+ "eventTriggers": [
+ {
+ "trigger_data": 1,
+ "priority": 102,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 8640000003,
+ "registrant": "android-app://com.example.xyz"
+ }],
+ "event_reports": [],
+ "attributions": []
+ },
+ "output": {
+ "sources": [{
+ "id": "S1",
+ "eventId": 1,
+ "sourceType": "event",
+ "publisher": "https://www.example1.com",
+ "publisherType": 1,
+ "webDestination": "https://example.com",
+ "enrollmentId": "enrollment-id-3",
+ "eventTime": 8640000001,
+ "expiryTime": 8726400001,
+ "priority": 100,
+ "status": 0,
+ "registrant": "android-app://com.example.abc"
+ }],
+ "triggers": [{
+ "id": "T1",
+ "attributionDestination": "https://example.com/some/path?query=1",
+ "destinationType": 1,
+ "enrollmentId": "enrollment-id-3",
+ "status": 2,
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 8640000002,
+ "registrant": "android-app://com.example.xyz"
+ }, {
+ "id": "T2",
+ "attributionDestination": "https://subdomain.example.com/another/path?queries=2",
+ "destinationType": 1,
+ "enrollmentId": "enrollment-id-3",
+ "status": 2,
+ "eventTriggers": [
+ {
+ "trigger_data": 1,
+ "priority": 102,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 8640000003,
+ "registrant": "android-app://com.example.xyz"
+ }],
+ "event_reports": [{
+ "id": "unused",
+ "sourceEventId": 1,
+ "attributionDestination": "https://example.com",
+ "enrollmentId": "enrollment-id-3",
+ "triggerData": 1,
+ "triggerTime": 8640000003,
+ "status": 0,
+ "reportTime": 8730000001,
+ "triggerPriority": 102,
+ "sourceType": "event",
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T2"
+ }],
+ "attributions": [
+ {
+ "id": "unused1",
+ "sourceSite": "https://example1.com",
+ "sourceOrigin": "https://www.example1.com",
+ "destinationSite": "https://example.com",
+ "destinationOrigin": "https://example.com",
+ "enrollmentId": "enrollment-id-3",
+ "triggerTime": 8640000002,
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
+ },
+ {
+ "id": "unused2",
+ "sourceSite": "https://example1.com",
+ "sourceOrigin": "https://www.example1.com",
+ "destinationSite": "https://example.com",
+ "destinationOrigin": "https://subdomain.example.com",
+ "enrollmentId": "enrollment-id-3",
+ "triggerTime": 8640000003,
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T2"
}]
}
},
@@ -1469,7 +1742,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1558,7 +1831,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1637,42 +1910,48 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
"triggerTime": 8640000003,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 102,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 3,
"triggerTime": 8640000004,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 103,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T3"
},
{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 4,
"triggerTime": 8640000005,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 104,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T4"
}
],
"attributions": [
@@ -1684,7 +1963,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "unused2",
@@ -1694,7 +1975,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000003,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "unused3",
@@ -1704,7 +1987,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000004,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T3"
},
{
"id": "unused4",
@@ -1714,7 +1999,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000005,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T4"
}
]
}
@@ -1733,7 +2020,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1823,7 +2110,7 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -1902,42 +2189,48 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 1,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 101,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
"triggerTime": 8640000003,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 101,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "unused",
- "sourceId": 1,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 4,
"triggerTime": 8640000005,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 102,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.0024263
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T4"
}
],
"attributions": [
@@ -1949,7 +2242,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "unused2",
@@ -1959,7 +2254,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000003,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "unused3",
@@ -1969,7 +2266,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000004,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T3"
},
{
"id": "unused4",
@@ -1979,7 +2278,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000005,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T4"
}
]
}
@@ -2049,7 +2350,6 @@
"publisher": "https://www.example1.com",
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
- "enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
"expiryTime": 8640000030,
"priority": 100,
@@ -2219,12 +2519,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"]
}
}
@@ -2263,12 +2563,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"]
}
}
@@ -2296,16 +2596,18 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 2,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 2,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 1,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000000,
"triggerPriority": 101,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -2317,7 +2619,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
@@ -2341,7 +2645,7 @@
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key_11": ["value_11_x", "value_12_x"],
"key_21": ["value_21", "value_22"]
}
@@ -2386,7 +2690,7 @@
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key_11": ["value_11_x", "value_12_x"],
"key_21": ["value_21", "value_22"]
}
@@ -2417,6 +2721,153 @@
}
},
{
+ "name": "Trigger top level filters and not-filters",
+ "description": "1 source, 2 triggers. First trigger filters not matched, generate event report with second trigger",
+ "input": {
+ "sources": [
+ {
+ "id": "S1",
+ "eventId": 2,
+ "sourceType": "navigation",
+ "publisher": "android-app://com.example.abc",
+ "appDestination": "android-app://com.example2.app/d1",
+ "enrollmentId": "enrollment-id-3",
+ "eventTime": 8640000000,
+ "expiryTime": 8640000030,
+ "priority": 101,
+ "status": 0,
+ "registrant": "android-app://com.example.abc",
+ "attribution_mode": 1,
+ "filterData": {
+ "key1": ["val1"],
+ "key2": ["val2"]
+ }
+ }
+ ],
+ "triggers": [
+ {
+ "id": "T1",
+ "attributionDestination": "android-app://com.example2.app/d1",
+ "enrollmentId": "enrollment-id-3",
+ "status": 0,
+ "eventTriggers": [{"trigger_data": 1}],
+ "triggerTime": 8640000002,
+ "registrant": "android-app://com.example.xyz",
+ "filters": {
+ "key1": ["val1", "value_12"]
+ },
+ "not_filters": {
+ "source_type": ["navigation"]
+ }
+ },
+ {
+ "id": "T2",
+ "attributionDestination": "android-app://com.example2.app/d1",
+ "enrollmentId": "enrollment-id-3",
+ "status": 0,
+ "eventTriggers": [{"trigger_data": 2}],
+ "triggerTime": 8640000004,
+ "registrant": "android-app://com.example.xyz",
+ "filters": {
+ "key_11": ["value_11", "value_12"],
+ "key_21": ["value_21", "value_22"]
+ },
+ "not_filters": {
+ "source_type": ["event"]
+ }
+ }
+ ],
+ "event_reports": [],
+ "attributions": []
+ },
+ "output": {
+ "sources": [
+ {
+ "id": "S1",
+ "eventId": 2,
+ "sourceType": "navigation",
+ "publisher": "android-app://com.example.abc",
+ "appDestination": "android-app://com.example2.app/d1",
+ "enrollmentId": "enrollment-id-3",
+ "eventTime": 8640000000,
+ "expiryTime": 8640000030,
+ "priority": 101,
+ "status": 0,
+ "registrant": "android-app://com.example.abc",
+ "attribution_mode": 1,
+ "filterData": {
+ "key1": ["val1"],
+ "key2": ["val2"]
+ }
+ }
+ ],
+ "triggers": [
+ {
+ "id": "T1",
+ "attributionDestination": "android-app://com.example2.app/d1",
+ "enrollmentId": "enrollment-id-3",
+ "status": 1,
+ "eventTriggers": [{"trigger_data": 1}],
+ "triggerTime": 8640000002,
+ "registrant": "android-app://com.example.xyz",
+ "filters": {
+ "key1": ["val1", "value_12"]
+ },
+ "not_filters": {
+ "source_type": ["navigation"]
+ }
+ },
+ {
+ "id": "T2",
+ "attributionDestination": "android-app://com.example2.app/d1",
+ "enrollmentId": "enrollment-id-3",
+ "status": 2,
+ "eventTriggers": [{"trigger_data": 2}],
+ "triggerTime": 8640000004,
+ "registrant": "android-app://com.example.xyz",
+ "filters": {
+ "key_11": ["value_11", "value_12"],
+ "key_21": ["value_21", "value_22"]
+ },
+ "not_filters": {
+ "source_type": ["event"]
+ }
+ }
+ ],
+ "event_reports": [
+ {
+ "id": "unused",
+ "sourceEventId": 2,
+ "attributionDestination": "android-app://com.example2.app",
+ "enrollmentId": "enrollment-id-3",
+ "triggerData": 2,
+ "triggerTime": 8640000004,
+ "status": 0,
+ "reportTime": 8643600030,
+ "triggerPriority": 0,
+ "sourceType": "navigation",
+ "randomizedTriggerRate": 0.0024263,
+ "sourceId": "S1",
+ "triggerId": "T2"
+ }
+ ],
+ "attributions": [
+ {
+ "id": "unused1",
+ "sourceSite": "android-app://com.example.abc",
+ "sourceOrigin": "android-app://com.example.abc",
+ "destinationSite": "android-app://com.example2.app",
+ "destinationOrigin": "android-app://com.example2.app",
+ "enrollmentId": "enrollment-id-3",
+ "triggerTime": 8640000004,
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T2"
+ }
+ ]
+ }
+ },
+ {
"name": "Trigger event level filter - event report generated for first MATCHING event trigger",
"description": "1 source, 1 trigger - 2 event triggers. Ignore first event trigger due to filter mismatch, hence generate event report with second event trigger",
"input": {
@@ -2429,12 +2880,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"]
}
}
@@ -2482,12 +2933,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"]
}
}
@@ -2525,16 +2976,18 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 2,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 2,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 0,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000000,
"triggerPriority": 102,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -2546,7 +2999,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
@@ -2564,12 +3019,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"]
}
}
@@ -2613,12 +3068,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"]
}
}
@@ -2652,16 +3107,18 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 2,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 2,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 1,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000000,
"triggerPriority": 101,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -2673,7 +3130,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
@@ -2692,12 +3151,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"],
"source_type": ["navigation"]
}
@@ -2750,12 +3209,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"],
"source_type": ["navigation"]
}
@@ -2797,16 +3256,18 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 2,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 2,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 0,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000000,
"triggerPriority": 102,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -2818,7 +3279,9 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
@@ -2836,12 +3299,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"]
}
}
@@ -2896,12 +3359,12 @@
"appDestination": "android-app://com.example2.app/d1",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000000,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400000,
"priority": 101,
"status": 0,
"registrant": "android-app://com.example.abc",
"attribution_mode": 1,
- "aggregateFilterData": {
+ "filterData": {
"key1": ["val1"]
}
}
@@ -2946,16 +3409,18 @@
"event_reports": [
{
"id": "unused",
- "sourceId": 2,
- "attributionDestination": "android-app://com.example2.app/d1",
+ "sourceEventId": 2,
+ "attributionDestination": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerData": 1,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000000,
"triggerPriority": 103,
"sourceType": "event",
- "randomizedTriggerRate": 0.0000025
+ "randomizedTriggerRate": 0.0000025,
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -2967,13 +3432,15 @@
"destinationOrigin": "android-app://com.example2.app",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
},
{
- "name": "Simple 1-1 matching Web Destination Aggregate Report",
+ "name": "Simple 1-1 matching Web Destination Aggregate Report, extracts public suffix + 1",
"input": {
"sources": [
{
@@ -2985,22 +3452,19 @@
"webDestination": "https://example2.com",
"enrollmentId": "enrollment-id-3",
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
- "aggregationKeys": [
- {
- "id": "campaignCounts",
- "key_piece": "0x159"
- }
- ],
+ "aggregationKeys": {
+ "campaignCounts": "0x159"
+ },
"registrant": "android-app://com.example.abc"
}
],
"triggers": [
{
"id": "T1",
- "attributionDestination": "https://example2.com",
+ "attributionDestination": "https://subdomain.example2.com/a/path?query=6",
"destinationType": 1,
"enrollmentId": "enrollment-id-3",
"status": 0,
@@ -3039,15 +3503,12 @@
"appDestination": "android-app://com.example2.app/d1",
"webDestination": "https://example2.com",
"enrollmentId": "enrollment-id-3",
- "aggregationKeys": [
- {
- "id": "campaignCounts",
- "key_piece": "0x159"
- }
- ],
+ "aggregationKeys": {
+ "campaignCounts": "0x159"
+ },
"aggregateContributions": 32678,
"eventTime": 8640000001,
- "expiryTime": 8640000030,
+ "expiryTime": 8726400001,
"priority": 100,
"status": 0,
"registrant": "android-app://com.example.abc"
@@ -3056,7 +3517,7 @@
"triggers": [
{
"id": "T1",
- "attributionDestination": "https://example2.com",
+ "attributionDestination": "https://subdomain.example2.com/a/path?query=6",
"destinationType": 1,
"enrollmentId": "enrollment-id-3",
"status": 2,
@@ -3084,16 +3545,18 @@
],
"event_reports": [{
"id": "unused",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://example2.com",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
"triggerTime": 8640000002,
"status": 0,
- "reportTime": 8643600030,
+ "reportTime": 8730000001,
"triggerPriority": 101,
"sourceType": "navigation",
- "randomizedTriggerRate": 0.017022
+ "randomizedTriggerRate": 0.017022,
+ "sourceId": "S1",
+ "triggerId": "T1"
}],
"aggregate_reports": [
{
@@ -3105,7 +3568,9 @@
"enrollmentId": "enrollment-id-3",
"status": 0,
"debugCleartextPayload": "{\"operation\":\"histogram\",\"data\":[{\"bucket\":1369,\"value\":32678}]}",
- "apiVersion": "0.1"
+ "apiVersion": "0.1",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -3114,10 +3579,12 @@
"sourceSite": "android-app://com.example.abc",
"sourceOrigin": "android-app://com.example.abc",
"destinationSite": "https://example2.com",
- "destinationOrigin": "https://example2.com",
+ "destinationOrigin": "https://subdomain.example2.com",
"enrollmentId": "enrollment-id-3",
"triggerTime": 8640000002,
- "registrant": "android-app://com.example.xyz"
+ "registrant": "android-app://com.example.xyz",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
]
}
diff --git a/adservices/tests/unittest/service-core/assets/classifier/classifier_test_assets_metadata.json b/adservices/tests/unittest/service-core/assets/classifier/classifier_test_assets_metadata.json
index fdbaadc68..8a42d8f7e 100644
--- a/adservices/tests/unittest/service-core/assets/classifier/classifier_test_assets_metadata.json
+++ b/adservices/tests/unittest/service-core/assets/classifier/classifier_test_assets_metadata.json
@@ -3,11 +3,12 @@
"property": "version_info",
"taxonomy_type": "chrome_and_mobile_taxonomy",
"taxonomy_version": "12",
+ "build_id" : "8",
"updated_date": "2022-08-10"
},
{
"asset_name": "labels_topics",
- "asset_version": "34",
+ "asset_version": "2",
"path": "assets/classifier/labels_test_topics.txt",
"checksum": "a52edeb53126e08e0fc56e0a4be96ee543739ae90e8da7f803dc228ec25eb107",
"updated_date": "2022-07-29"
diff --git a/adservices/tests/unittest/service-core/assets/classifier/test_model.tflite b/adservices/tests/unittest/service-core/assets/classifier/test_model.tflite
new file mode 100644
index 000000000..56fe4703c
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/classifier/test_model.tflite
Binary files differ
diff --git a/adservices/tests/unittest/service-core/assets/event_report_service_test.json b/adservices/tests/unittest/service-core/assets/event_report_service_test.json
index 0e2fb006c..e373b35fe 100644
--- a/adservices/tests/unittest/service-core/assets/event_report_service_test.json
+++ b/adservices/tests/unittest/service-core/assets/event_report_service_test.json
@@ -9,7 +9,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -29,7 +29,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -58,7 +58,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -78,7 +78,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -107,7 +107,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -127,7 +127,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -156,7 +156,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -169,7 +169,7 @@
},
{
"id": "ER2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -189,7 +189,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -202,7 +202,7 @@
},
{
"id": "ER2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -231,7 +231,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -244,7 +244,7 @@
},
{
"id": "ER2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -257,7 +257,7 @@
},
{
"id": "ER3",
- "sourceId": 3,
+ "sourceEventId": 3,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -277,7 +277,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -290,7 +290,7 @@
},
{
"id": "ER2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -303,7 +303,7 @@
},
{
"id": "ER2",
- "sourceId": 3,
+ "sourceEventId": 3,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -332,7 +332,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -352,7 +352,7 @@
"event_reports": [
{
"id": "ER1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.example2/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
diff --git a/adservices/tests/unittest/service-core/assets/measurement_app_uninstall_deletion_test.json b/adservices/tests/unittest/service-core/assets/measurement_app_uninstall_deletion_test.json
index 105c537a7..d7d5d5889 100644
--- a/adservices/tests/unittest/service-core/assets/measurement_app_uninstall_deletion_test.json
+++ b/adservices/tests/unittest/service-core/assets/measurement_app_uninstall_deletion_test.json
@@ -18,11 +18,29 @@
"registrant": "android-app://com.example.abc"
}
],
- "triggers": [],
+ "triggers": [
+ {
+ "id": "T1",
+ "attributionDestination": "android-app://example2.com",
+ "enrollmentId": "enrollment-id-3",
+ "status": 0,
+ "triggerTime": 8640000002,
+ "registrant": "android-app://not.delete.candidate",
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ]
+ }
+ ],
"event_reports": [
{
"id": "id",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://example2.com",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -30,14 +48,34 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
}
],
"attributions": []
},
"output": {
"sources": [],
- "triggers": [],
+ "triggers": [
+ {
+ "id": "T1",
+ "attributionDestination": "android-app://example2.com",
+ "enrollmentId": "enrollment-id-3",
+ "status": 0,
+ "triggerTime": 8640000002,
+ "registrant": "android-app://not.delete.candidate",
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ]
+ }
+ ],
"event_reports": [],
"attributions": []
},
@@ -78,7 +116,7 @@
"event_reports": [
{
"id": "id",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://no-match.com",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -111,7 +149,7 @@
"event_reports": [
{
"id": "id",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://no-match.com",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -183,7 +221,7 @@
"event_reports": [
{
"id": "id",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://no-match.com",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -234,7 +272,7 @@
"event_reports": [
{
"id": "id",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://no-match.com",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -289,7 +327,7 @@
"event_reports": [
{
"id": "R1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://remove.me",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -301,7 +339,7 @@
},
{
"id": "R2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://remove.me",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -313,7 +351,7 @@
},
{
"id": "R3",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://dont.remove.me",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -364,7 +402,7 @@
"event_reports": [
{
"id": "R3",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://dont.remove.me",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -453,7 +491,7 @@
"event_reports": [
{
"id": "id",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://example2.com",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -517,7 +555,7 @@
"event_reports": [
{
"id": "id",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://example2.com",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
diff --git a/adservices/tests/unittest/service-core/assets/measurement_delete_expired_test.json b/adservices/tests/unittest/service-core/assets/measurement_delete_expired_test.json
index e96bba421..5004c0707 100644
--- a/adservices/tests/unittest/service-core/assets/measurement_delete_expired_test.json
+++ b/adservices/tests/unittest/service-core/assets/measurement_delete_expired_test.json
@@ -138,7 +138,7 @@
"event_reports": [
{
"id": "delivered1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://www.example2.com/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -150,7 +150,7 @@
},
{
"id": "young1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://www.example2.com/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
@@ -169,7 +169,7 @@
"event_reports": [
{
"id": "young1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://www.example2.com/d1",
"enrollmentId": "enrollment-id-3",
"triggerData": 2,
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/registrant_not_found.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/registrant_not_found.json
index c1153ccd6..738d5e5c8 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/registrant_not_found.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/registrant_not_found.json
@@ -68,7 +68,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -80,7 +80,7 @@
},
{
"id": "E2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -182,7 +182,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -194,7 +194,7 @@
},
{
"id": "E2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -229,7 +229,7 @@
]
},
"param": {
- "registrant": "android-app://com.registrant.toBeDeleted",
+ "registrant": "com.registrant.toBeDeleted",
"origins": [],
"domains": []
}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain.json
index 18ab8ccf5..fc28db573 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain.json
@@ -137,7 +137,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -145,11 +145,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -157,11 +159,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://doNotDelete.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -169,11 +173,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -181,11 +187,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -193,11 +201,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -205,7 +215,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T4"
}
],
"attributions": [
@@ -217,7 +229,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -227,7 +241,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T3"
},
{
"id": "A3",
@@ -237,7 +253,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T3"
},
{
"id": "A4",
@@ -247,7 +265,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T4"
}
]
},
@@ -323,7 +343,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://doNotDelete.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -331,11 +351,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -343,7 +365,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
],
"attributions": [
@@ -355,7 +379,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T3"
},
{
"id": "A3",
@@ -365,12 +391,14 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": ["https://deleteCandidate1.site.com", "https://deleteCandidate2.site.com"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain_preserve.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain_preserve.json
index cab136da1..95d779d70 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain_preserve.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_multiple_origin_no_domain_preserve.json
@@ -1,6 +1,6 @@
{
"name": "With multiple origin and no domain - preserve",
- "description": "(S3), (T3), (E3) AND (A1, A3, A4, A5) will be deleted due to mismatched origins. E2 not delete as it is out of range",
+ "description": "(S3), (T3), (E3) AND (A2, A3, A5) will be deleted due to mismatched origins. E2 not delete as it is out of range",
"input": {
"sources": [
{
@@ -137,7 +137,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -145,23 +145,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://doNotDelete.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -169,11 +173,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -181,11 +187,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -193,11 +201,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T4"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -205,7 +215,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S4",
+ "triggerId": "T4"
}
],
"attributions": [
@@ -217,7 +229,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -227,7 +241,9 @@
"destinationOrigin": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T3"
},
{
"id": "A3",
@@ -237,7 +253,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T3"
},
{
"id": "A4",
@@ -247,7 +265,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T4"
},
{
"id": "A5",
@@ -257,7 +277,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T4"
}
]
},
@@ -365,7 +387,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -373,23 +395,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -397,11 +423,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -409,11 +437,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T4"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -421,24 +451,40 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S4",
+ "triggerId": "T4"
}
],
"attributions": [
{
- "id": "A2",
+ "id": "A1",
"sourceSite": "https://site.com",
"sourceOrigin": "https://deleteCandidate1.site.com",
- "destinationSite": "https://site.com",
- "destinationOrigin": "https://deleteCandidate2.site.com",
+ "destinationSite": "https://site2.com",
+ "destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
- "triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "triggerTime": 1648665733601,
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
+ },
+ {
+ "id": "A4",
+ "sourceSite": "https://site.com",
+ "sourceOrigin": "https://deleteCandidate2.site.com",
+ "destinationSite": "https://site2.com",
+ "destinationOrigin": "https://site2.com",
+ "enrollmentId": "enrollment-id",
+ "triggerTime": 1648665733601,
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T4"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": ["https://deleteCandidate1.site.com", "https://deleteCandidate2.site.com"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain.json
index 683075d30..89651845f 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain.json
@@ -137,7 +137,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -145,23 +145,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://delete.sitec.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -169,11 +173,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S4",
+ "triggerId" : "T1"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -181,11 +187,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -193,11 +201,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T2"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -205,7 +215,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T4"
}
],
"attributions": [
@@ -217,7 +229,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "A2",
@@ -227,7 +241,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "A3",
@@ -237,7 +253,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
},
{
"id": "A4",
@@ -247,7 +265,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S4",
+ "triggerId" : "T4"
},
{
"id": "A5",
@@ -257,7 +277,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T2"
}
]
},
@@ -333,7 +355,7 @@
"event_reports": [
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -341,7 +363,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T2"
}
],
"attributions": [
@@ -353,7 +377,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "A3",
@@ -363,7 +389,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
},
{
"id": "A5",
@@ -373,12 +401,14 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": [],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain_preserve.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain_preserve.json
index 8779a4cdf..800b19eae 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain_preserve.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_multiple_domain_preserve.json
@@ -137,7 +137,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -145,23 +145,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://delete.sitec.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -169,11 +173,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T4"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -181,11 +187,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T1"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -193,11 +201,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -205,11 +215,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S4",
+ "triggerId": "T1"
},
{
"id": "E7",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.sitea.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -217,7 +229,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
],
"attributions": [
@@ -229,7 +243,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T3"
},
{
"id": "A2",
@@ -239,7 +255,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "A3",
@@ -249,7 +267,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T2"
},
{
"id": "A4",
@@ -259,7 +279,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T3"
},
{
"id": "A5",
@@ -269,17 +291,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
- },
- {
- "id": "A6",
- "sourceSite": "https://sitec.com",
- "sourceOrigin": "https://a.sitec.com",
- "destinationSite": "https://site.com",
- "destinationOrigin": "https://b.site.com",
- "enrollmentId": "enrollment-id",
- "triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
]
},
@@ -387,7 +401,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -395,23 +409,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://delete.sitec.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -419,11 +437,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T4"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -431,11 +451,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T1"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -443,11 +465,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -455,7 +479,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S4",
+ "triggerId": "T1"
}
],
"attributions": [
@@ -467,22 +493,14 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
- },
- {
- "id": "A6",
- "sourceSite": "https://sitec.com",
- "sourceOrigin": "https://a.sitec.com",
- "destinationSite": "https://site.com",
- "destinationOrigin": "https://b.site.com",
- "enrollmentId": "enrollment-id",
- "triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": [],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_nor_range.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_nor_range.json
index 8b72ad2ff..b3a849ef6 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_nor_range.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_no_origin_nor_range.json
@@ -1,5 +1,6 @@
{
"name": "With no origin nor range",
+ "description": "Delete S1, T1, E1 and A1 because registrant matches.",
"input": {
"sources": [
{
@@ -68,19 +69,21 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site3.toBeDeletedBecauseOfSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site3.toBeDeletedBecauseOfsourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 8640000002,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -88,7 +91,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -100,7 +105,9 @@
"destinationOrigin": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerTime": 10,
- "registrant": "android-app://com.registrant.toBeDeleted"
+ "registrant": "android-app://com.registrant.toBeDeleted",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -110,7 +117,9 @@
"destinationOrigin": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerTime": 10,
- "registrant": "android-app://com.registrant2"
+ "registrant": "android-app://com.registrant2",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
@@ -152,7 +161,7 @@
"event_reports": [
{
"id": "E2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -160,7 +169,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -172,12 +183,14 @@
"destinationOrigin": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerTime": 10,
- "registrant": "android-app://com.registrant2"
+ "registrant": "android-app://com.registrant2",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.toBeDeleted",
+ "registrant": "com.registrant.toBeDeleted",
"origins": [],
"domains": []
}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain.json
index fe3334db7..ee0f6919c 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain.json
@@ -192,7 +192,7 @@
"registrant": "android-app://com.registrant.deleteCandidate"
},
{
- "id": "T6",
+ "id": "Trigger6",
"attributionDestination": "https://deletesite2.com",
"enrollmentId": "enrollment-id",
"priority": 101,
@@ -214,7 +214,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://abc.deletesite1.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -222,23 +222,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://doNotDelete.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -246,11 +250,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -258,11 +264,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T4"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -270,11 +278,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -282,11 +292,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T5"
},
{
"id": "E7",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://qwe.deletesite1.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -294,11 +306,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S5",
+ "triggerId" : "T5"
},
{
"id": "E8",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://qwe.deletesite2.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -306,7 +320,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S6",
+ "triggerId" : "Trigger6"
}
],
"attributions": [
@@ -318,7 +334,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "A2",
@@ -328,7 +346,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "A3",
@@ -338,7 +358,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
},
{
"id": "A4",
@@ -348,7 +370,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S4",
+ "triggerId" : "T1"
},
{
"id": "A5",
@@ -358,7 +382,9 @@
"destinationOrigin": "https://abc.deletesite2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S5",
+ "triggerId" : "Trigger6"
},
{
"id": "A6",
@@ -368,7 +394,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S6",
+ "triggerId" : "Trigger6"
}
]
},
@@ -457,7 +485,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://doNotDelete.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -465,11 +493,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -477,7 +507,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
}
],
"attributions": [
@@ -489,7 +521,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "A3",
@@ -499,12 +533,14 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": ["https://deleteCandidate1.site.com", "https://deleteCandidate2.site.com"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_exclude_rate_limit.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_exclude_rate_limit.json
index cf0f9fd1f..be59247fa 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_exclude_rate_limit.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_exclude_rate_limit.json
@@ -8,7 +8,7 @@
"eventId": 1,
"sourceType": "navigation",
"publisher": "https://deleteCandidate1.site.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733601,
"expiryTime": 1648665733611,
@@ -21,7 +21,7 @@
"eventId": 2,
"sourceType": "navigation",
"publisher": "https://deleteCandidate1.site.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733605,
"expiryTime": 1648665733615,
@@ -34,7 +34,7 @@
"eventId": 3,
"sourceType": "navigation",
"publisher": "https://siteNotMatch.site3.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733601,
"expiryTime": 1648665733611,
@@ -47,7 +47,7 @@
"eventId": 1,
"sourceType": "navigation",
"publisher": "https://deleteCandidate2.site.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733601,
"expiryTime": 1648665733611,
@@ -60,7 +60,7 @@
"eventId": 1,
"sourceType": "navigation",
"publisher": "https://abc.deletesite1.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733601,
"expiryTime": 1648665733611,
@@ -73,7 +73,7 @@
"eventId": 1,
"sourceType": "navigation",
"publisher": "https://abc.deletesite2.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733601,
"expiryTime": 1648665733611,
@@ -86,7 +86,7 @@
"eventId": 1,
"sourceType": "navigation",
"publisher": "https://somedeletesite2.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733601,
"expiryTime": 1648665733611,
@@ -192,7 +192,7 @@
"registrant": "android-app://com.registrant.deleteCandidate"
},
{
- "id": "T6",
+ "id": "Trigger6",
"attributionDestination": "https://deletesite2.com",
"enrollmentId": "enrollment-id",
"priority": 101,
@@ -214,7 +214,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://abc.deletesite1.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -222,23 +222,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://doNotDelete.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -246,11 +250,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -258,11 +264,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T4"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -270,11 +278,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -282,11 +292,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T5"
},
{
"id": "E7",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://qwe.deletesite1.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -294,11 +306,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S5",
+ "triggerId" : "T5"
},
{
"id": "E8",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://qwe.deletesite2.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -306,7 +320,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S6",
+ "triggerId" : "Trigger6"
}
],
"attributions": [
@@ -318,7 +334,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "A2",
@@ -328,7 +346,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "A3",
@@ -338,7 +358,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
},
{
"id": "A4",
@@ -348,7 +370,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S4",
+ "triggerId" : "T1"
},
{
"id": "A5",
@@ -358,7 +382,9 @@
"destinationOrigin": "https://abc.deletesite2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S5",
+ "triggerId" : "Trigger6"
},
{
"id": "A6",
@@ -368,18 +394,33 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S6",
+ "triggerId" : "Trigger6"
}
]
},
"output": {
"sources": [
{
+ "id": "S1",
+ "eventId": 1,
+ "sourceType": "navigation",
+ "publisher": "https://deleteCandidate1.site.com",
+ "appDestination": "android-app://com.app2",
+ "enrollmentId": "enrollment-id",
+ "eventTime": 1648665733601,
+ "expiryTime": 1648665733611,
+ "priority": 100,
+ "status": 2,
+ "registrant": "android-app://com.registrant.deleteCandidate"
+ },
+ {
"id": "S2",
"eventId": 2,
"sourceType": "navigation",
"publisher": "https://deleteCandidate1.site.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733605,
"expiryTime": 1648665733615,
@@ -392,7 +433,7 @@
"eventId": 3,
"sourceType": "navigation",
"publisher": "https://siteNotMatch.site3.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733601,
"expiryTime": 1648665733611,
@@ -401,11 +442,50 @@
"registrant": "android-app://com.registrant.deleteCandidate"
},
{
+ "id": "S4",
+ "eventId": 1,
+ "sourceType": "navigation",
+ "publisher": "https://deleteCandidate2.site.com",
+ "appDestination": "android-app://com.app2",
+ "enrollmentId": "enrollment-id",
+ "eventTime": 1648665733601,
+ "expiryTime": 1648665733611,
+ "priority": 100,
+ "status": 2,
+ "registrant": "android-app://com.registrant.deleteCandidate"
+ },
+ {
+ "id": "S5",
+ "eventId": 1,
+ "sourceType": "navigation",
+ "publisher": "https://abc.deletesite1.com",
+ "appDestination": "android-app://com.app2",
+ "enrollmentId": "enrollment-id",
+ "eventTime": 1648665733601,
+ "expiryTime": 1648665733611,
+ "priority": 100,
+ "status": 2,
+ "registrant": "android-app://com.registrant.deleteCandidate"
+ },
+ {
+ "id": "S6",
+ "eventId": 1,
+ "sourceType": "navigation",
+ "publisher": "https://abc.deletesite2.com",
+ "appDestination": "android-app://com.app2",
+ "enrollmentId": "enrollment-id",
+ "eventTime": 1648665733601,
+ "expiryTime": 1648665733611,
+ "priority": 100,
+ "status": 2,
+ "registrant": "android-app://com.registrant.deleteCandidate"
+ },
+ {
"id": "S7",
"eventId": 1,
"sourceType": "navigation",
"publisher": "https://somedeletesite2.com",
- "appDestination": "android-app://com.site2",
+ "appDestination": "android-app://com.app2",
"enrollmentId": "enrollment-id",
"eventTime": 1648665733601,
"expiryTime": 1648665733611,
@@ -416,6 +496,25 @@
],
"triggers": [
{
+ "id": "T1",
+ "attributionDestination": "https://deleteCandidate1.site.com",
+ "enrollmentId": "enrollment-id",
+ "priority": 101,
+ "status": 3,
+ "triggerData": 2,
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 1648665733601,
+ "registrant": "android-app://com.registrant.deleteCandidate"
+ },
+ {
"id": "T2",
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
@@ -452,12 +551,97 @@
],
"triggerTime": 1648665733601,
"registrant": "android-app://com.registrant.deleteCandidate"
+ },
+ {
+ "id": "T4",
+ "attributionDestination": "https://deleteCandidate2.site.com",
+ "enrollmentId": "enrollment-id",
+ "priority": 101,
+ "status": 3,
+ "triggerData": 2,
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 1648665733601,
+ "registrant": "android-app://com.registrant.deleteCandidate"
+ },
+ {
+ "id": "T5",
+ "attributionDestination": "https://deletesite1.com",
+ "enrollmentId": "enrollment-id",
+ "priority": 101,
+ "status": 3,
+ "triggerData": 2,
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 1648665733601,
+ "registrant": "android-app://com.registrant.deleteCandidate"
+ },
+ {
+ "id": "Trigger6",
+ "attributionDestination": "https://deletesite2.com",
+ "enrollmentId": "enrollment-id",
+ "priority": 101,
+ "status": 3,
+ "triggerData": 2,
+ "eventTriggers": [
+ {
+ "trigger_data": 2,
+ "priority": 101,
+ "filters": {
+ "key_1": ["value_1"]
+ }
+ }
+ ],
+ "triggerTime": 1648665733601,
+ "registrant": "android-app://com.registrant.deleteCandidate"
}
],
"event_reports": [
{
+ "id": "E1",
+ "sourceEventId": 2,
+ "attributionDestination": "https://abc.deletesite1.com",
+ "enrollmentId": "enrollment-id",
+ "triggerData": 2,
+ "triggerTime": 1648665733601,
+ "status": 2,
+ "reportTime": -1,
+ "triggerPriority": 101,
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
+ },
+ {
+ "id": "E2",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
+ "enrollmentId": "enrollment-id",
+ "triggerData": 2,
+ "triggerTime": 1648665733605,
+ "status": 2,
+ "reportTime": -1,
+ "triggerPriority": 101,
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T2"
+ },
+ {
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://doNotDelete.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -465,11 +649,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
+ },
+ {
+ "id": "E4",
+ "sourceEventId": 2,
+ "attributionDestination": "https://deleteCandidate1.site.com",
+ "enrollmentId": "enrollment-id",
+ "triggerData": 2,
+ "triggerTime": 1648665733601,
+ "status": 2,
+ "reportTime": -1,
+ "triggerPriority": 101,
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T4"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -477,7 +677,51 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
+ },
+ {
+ "id": "E6",
+ "sourceEventId": 2,
+ "attributionDestination": "https://deleteCandidate2.site.com",
+ "enrollmentId": "enrollment-id",
+ "triggerData": 2,
+ "triggerTime": 1648665733601,
+ "status": 2,
+ "reportTime": -1,
+ "triggerPriority": 101,
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T5"
+ },
+ {
+ "id": "E7",
+ "sourceEventId": 2,
+ "attributionDestination": "https://qwe.deletesite1.com",
+ "enrollmentId": "enrollment-id",
+ "triggerData": 2,
+ "triggerTime": 1648665733601,
+ "status": 2,
+ "reportTime": -1,
+ "triggerPriority": 101,
+ "sourceType": "navigation",
+ "sourceId" : "S5",
+ "triggerId" : "T5"
+ },
+ {
+ "id": "E8",
+ "sourceEventId": 2,
+ "attributionDestination": "https://qwe.deletesite2.com",
+ "enrollmentId": "enrollment-id",
+ "triggerData": 2,
+ "triggerTime": 1648665733601,
+ "status": 2,
+ "reportTime": -1,
+ "triggerPriority": 101,
+ "sourceType": "navigation",
+ "sourceId" : "S6",
+ "triggerId" : "Trigger6"
}
],
"attributions": [
@@ -489,7 +733,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "A2",
@@ -499,7 +745,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "A3",
@@ -509,7 +757,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T3"
},
{
"id": "A4",
@@ -519,7 +769,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S4",
+ "triggerId" : "T1"
},
{
"id": "A5",
@@ -529,7 +781,9 @@
"destinationOrigin": "https://abc.deletesite2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S5",
+ "triggerId" : "Trigger6"
},
{
"id": "A6",
@@ -539,12 +793,14 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S6",
+ "triggerId" : "Trigger6"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": ["https://deleteCandidate1.site.com", "https://deleteCandidate2.site.com"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_preserve.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_preserve.json
index b195522c2..6779044c9 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_preserve.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_and_domain_preserve.json
@@ -192,7 +192,7 @@
"registrant": "android-app://com.registrant.deleteCandidate"
},
{
- "id": "T6",
+ "id": "Trigger6",
"attributionDestination": "https://deletesite2.com",
"enrollmentId": "enrollment-id",
"priority": 101,
@@ -214,7 +214,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://abc.deletesite1.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -222,23 +222,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S3",
+ "triggerId" : "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://doNotDelete.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -246,11 +250,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T3"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -258,11 +264,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T4"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -270,11 +278,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T2"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -282,11 +292,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T1"
},
{
"id": "E7",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://qwe.deletesite1.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -294,11 +306,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S5",
+ "triggerId" : "T5"
},
{
"id": "E8",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://qwe.deletesite2.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -306,7 +320,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S4",
+ "triggerId" : "Trigger6"
}
],
"attributions": [
@@ -318,7 +334,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "T1"
},
{
"id": "A2",
@@ -328,7 +346,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "A3",
@@ -338,7 +358,9 @@
"destinationOrigin": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T1"
},
{
"id": "A4",
@@ -348,7 +370,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S1",
+ "triggerId" : "T3"
},
{
"id": "A5",
@@ -358,7 +382,9 @@
"destinationOrigin": "https://abc.deletesite2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S7",
+ "triggerId" : "Trigger6"
},
{
"id": "A6",
@@ -368,17 +394,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
- },
- {
- "id": "A7",
- "sourceSite": "https://site1.com",
- "sourceOrigin": "https://site1.com",
- "destinationSite": "https://site2.com",
- "destinationOrigin": "https://site2.com",
- "enrollmentId": "enrollment-id",
- "triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S3",
+ "triggerId" : "Trigger6"
}
]
},
@@ -528,7 +546,7 @@
"registrant": "android-app://com.registrant.deleteCandidate"
},
{
- "id": "T6",
+ "id": "Trigger6",
"attributionDestination": "https://deletesite2.com",
"enrollmentId": "enrollment-id",
"priority": 101,
@@ -550,7 +568,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://abc.deletesite1.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -558,11 +576,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T1"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -570,11 +590,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T4"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate1.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -582,11 +604,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S1",
+ "triggerId" : "T2"
},
{
"id": "E6",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -594,11 +618,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S2",
+ "triggerId" : "T1"
},
{
"id": "E7",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://qwe.deletesite1.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -606,11 +632,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S5",
+ "triggerId" : "T5"
},
{
"id": "E8",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "https://qwe.deletesite2.com",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -618,7 +646,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId" : "S4",
+ "triggerId" : "Trigger6"
}
],
"attributions": [
@@ -630,7 +660,9 @@
"destinationOrigin": "https://site2.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T2"
},
{
"id": "A3",
@@ -640,12 +672,14 @@
"destinationOrigin": "https://deleteCandidate2.site.com",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId" : "S2",
+ "triggerId" : "T1"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": ["https://deleteCandidate1.site.com", "https://deleteCandidate2.site.com"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_but_no_range.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_but_no_range.json
index b08551d70..9007a3f50 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_but_no_range.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_origin_but_no_range.json
@@ -68,19 +68,21 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.toBeDeletedDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.toBeDeletedDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 8640000002,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.toBeDeleted",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -88,11 +90,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 3,
+ "sourceEventId": 3,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -100,7 +104,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -112,7 +118,9 @@
"destinationOrigin": "android-app://com.site3",
"enrollmentId": "enrollment-id",
"triggerTime": 10,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -122,7 +130,9 @@
"destinationOrigin": "android-app://com.site.toBeDeleted",
"enrollmentId": "enrollment-id",
"triggerTime": 10,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "A3",
@@ -132,7 +142,9 @@
"destinationOrigin": "android-app://com.site2.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerTime": 10,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
@@ -174,7 +186,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 3,
+ "sourceEventId": 3,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -182,7 +194,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -194,12 +208,14 @@
"destinationOrigin": "android-app://com.site2.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerTime": 10,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"origins": ["android-app://com.site.toBeDeleted"],
"domains": []
}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_and_origin.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_and_origin.json
index 80c738302..f0a2b6217 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_and_origin.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_and_origin.json
@@ -104,7 +104,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -112,23 +112,27 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
- "attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
+ "sourceEventId": 1,
+ "attributionDestination": "android-app://com.site.notInRangeButDeleteDueTosourceEventId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -136,11 +140,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -148,11 +154,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T1"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -160,7 +168,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
],
"attributions": [
@@ -172,7 +182,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -182,7 +194,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "A3",
@@ -192,7 +206,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
]
},
@@ -268,7 +284,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -276,11 +292,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -288,7 +306,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
],
"attributions": [
@@ -300,7 +320,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "A3",
@@ -310,12 +332,14 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": ["android-app://com.site.deleteCandidate"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_but_no_origin.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_but_no_origin.json
index 7e651ffd4..4049d0cf8 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_but_no_origin.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_but_no_origin.json
@@ -72,7 +72,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site2.toBeDeletedDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -80,11 +80,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site2.toBeDeletedDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -92,11 +94,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -104,7 +108,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -116,7 +122,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733601,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -126,7 +134,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
@@ -170,7 +180,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -178,7 +188,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -190,12 +202,14 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": [],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_and_origin.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_and_origin.json
index 3ab4b09a6..aeff08483 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_and_origin.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_and_origin.json
@@ -104,7 +104,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -112,11 +112,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -124,11 +126,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -136,11 +140,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -148,11 +154,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T1"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -160,7 +168,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
],
"attributions": [
@@ -172,7 +182,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733602,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -182,7 +194,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "A3",
@@ -192,7 +206,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733602,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
]
},
@@ -268,7 +284,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -276,11 +292,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -288,7 +306,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
],
"attributions": [
@@ -300,7 +320,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "A3",
@@ -310,12 +332,14 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733602,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S3",
+ "triggerId": "T3"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": ["android-app://com.site.deleteCandidate"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_no_origin.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_no_origin.json
index 708db5af9..a8095ee28 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_no_origin.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_ending_edge_no_origin.json
@@ -72,7 +72,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site2.toBeDeletedDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -80,11 +80,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site2.toBeDeletedDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -92,11 +94,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -104,7 +108,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -116,7 +122,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733602,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -126,7 +134,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
@@ -170,7 +180,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -178,7 +188,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -190,12 +202,14 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": [],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_and_origin.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_and_origin.json
index 4fbdc711c..f4adcc7f7 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_and_origin.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_and_origin.json
@@ -104,7 +104,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -112,11 +112,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -124,11 +126,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -136,11 +140,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -148,11 +154,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T1"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -160,7 +168,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -172,7 +182,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733600,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -182,17 +194,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
- },
- {
- "id": "A3",
- "sourceSite": "android-app://com.site3",
- "sourceOrigin": "android-app://com.site3",
- "destinationSite": "android-app://com.site2",
- "destinationOrigin": "android-app://com.site2",
- "enrollmentId": "enrollment-id",
- "triggerTime": 1648665733600,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
@@ -268,7 +272,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -276,11 +280,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -288,7 +294,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -300,22 +308,14 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
- },
- {
- "id": "A3",
- "sourceSite": "android-app://com.site3",
- "sourceOrigin": "android-app://com.site3",
- "destinationSite": "android-app://com.site2",
- "destinationOrigin": "android-app://com.site2",
- "enrollmentId": "enrollment-id",
- "triggerTime": 1648665733600,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": ["android-app://com.site.deleteCandidate"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_no_origin.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_no_origin.json
index d162d2fed..1bf656c55 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_no_origin.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_range_on_starting_edge_no_origin.json
@@ -72,31 +72,35 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site2.toBeDeletedDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733600,
- "status": 0,
+ "status": 2,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site2.toBeDeletedDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
"triggerTime": 1648665733605,
- "status": 0,
+ "status": 2,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -104,7 +108,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -116,7 +122,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733600,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -126,7 +134,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
@@ -170,7 +180,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -178,7 +188,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -190,12 +202,14 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733602,
"origins": [],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_and_origin.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_and_origin.json
index 91f25ba4e..242f3bbb6 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_and_origin.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_and_origin.json
@@ -104,7 +104,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -112,11 +112,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site.notInRangeButDeleteDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -124,11 +126,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -136,11 +140,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E4",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -148,11 +154,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T1"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -160,7 +168,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -172,7 +182,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733600,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -182,17 +194,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
- },
- {
- "id": "A3",
- "sourceSite": "android-app://com.site3",
- "sourceOrigin": "android-app://com.site3",
- "destinationSite": "android-app://com.site2",
- "destinationOrigin": "android-app://com.site2",
- "enrollmentId": "enrollment-id",
- "triggerTime": 1648665733600,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
@@ -268,7 +272,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.doNotDelete",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -276,11 +280,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
},
{
"id": "E5",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site.deleteCandidate",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -288,7 +294,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -300,22 +308,14 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
- },
- {
- "id": "A3",
- "sourceSite": "android-app://com.site3",
- "sourceOrigin": "android-app://com.site3",
- "destinationSite": "android-app://com.site2",
- "destinationOrigin": "android-app://com.site2",
- "enrollmentId": "enrollment-id",
- "triggerTime": 1648665733600,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733600,
"origins": ["android-app://com.site.deleteCandidate"],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_no_origin.json b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_no_origin.json
index a85586a9a..62a8b900c 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_no_origin.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_browser_deletion_tests/with_start_equals_end_no_origin.json
@@ -72,7 +72,7 @@
"event_reports": [
{
"id": "E1",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site2.toBeDeletedDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -80,11 +80,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "E2",
- "sourceId": 1,
+ "sourceEventId": 1,
"attributionDestination": "android-app://com.site2.toBeDeletedDueToSourceId1",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -92,11 +94,13 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S1",
+ "triggerId": "T2"
},
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -104,7 +108,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -116,7 +122,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733600,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S1",
+ "triggerId": "T1"
},
{
"id": "A2",
@@ -126,7 +134,9 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
@@ -170,7 +180,7 @@
"event_reports": [
{
"id": "E3",
- "sourceId": 2,
+ "sourceEventId": 2,
"attributionDestination": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerData": 2,
@@ -178,7 +188,9 @@
"status": 0,
"reportTime": -1,
"triggerPriority": 101,
- "sourceType": "navigation"
+ "sourceType": "navigation",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
],
"attributions": [
@@ -190,12 +202,14 @@
"destinationOrigin": "android-app://com.site2",
"enrollmentId": "enrollment-id",
"triggerTime": 1648665733605,
- "registrant": "android-app://com.registrant.deleteCandidate"
+ "registrant": "android-app://com.registrant.deleteCandidate",
+ "sourceId": "S2",
+ "triggerId": "T2"
}
]
},
"param": {
- "registrant": "android-app://com.registrant.deleteCandidate",
+ "registrant": "com.registrant.deleteCandidate",
"start": 1648665733600,
"end": 1648665733600,
"origins": [],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/basic_use_of_redirect.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/basic_use_of_redirect.json
index b8bd06d29..a743c0e7b 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/basic_use_of_redirect.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/basic_use_of_redirect.json
@@ -3,34 +3,33 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [
{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
"Location": null,
"Attribution-Reporting-Redirect": [
- "https://www.ad-tech2.com",
- "https://www.ad-tech3.com"
+ "https://www.ad-tech2.test",
+ "https://www.ad-tech3.test"
]
}
},
{
- "url": "https://www.ad-tech2.com",
+ "url": "https://www.ad-tech2.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -39,11 +38,11 @@
}
},
{
- "url": "https://www.ad-tech3.com",
+ "url": "https://www.ad-tech3.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "3",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "800172800002"
},
@@ -56,13 +55,12 @@
}],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [
{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -74,13 +72,13 @@
},
"Location": null,
"Attribution-Reporting-Redirect": [
- "https://www.ad-tech2.com",
- "https://www.ad-tech3.com"
+ "https://www.ad-tech2.test",
+ "https://www.ad-tech3.test"
]
}
},
{
- "url": "https://www.ad-tech2.com",
+ "url": "https://www.ad-tech2.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -95,7 +93,7 @@
}
},
{
- "url": "https://www.ad-tech3.com",
+ "url": "https://www.ad-tech3.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -117,9 +115,9 @@
"event_level_results": [
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
@@ -128,9 +126,9 @@
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech2.com",
+ "report_url": "https://www.ad-tech2.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "2",
"source_type": "navigation",
@@ -139,9 +137,9 @@
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech3.com",
+ "report_url": "https://www.ad-tech3.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "3",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/limit_num_reports_for_click.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/limit_num_reports_for_click.json
index a837c1cfd..7703d92fc 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/limit_num_reports_for_click.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/limit_num_reports_for_click.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -26,12 +25,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -49,12 +47,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -72,12 +69,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -95,12 +91,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -122,9 +117,9 @@
"event_level_results": [
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
@@ -133,9 +128,9 @@
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -144,9 +139,9 @@
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/simple_1-1_matching.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/simple_1-1_matching.json
index 4dcfa2c74..f797c5aa8 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/simple_1-1_matching.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_noise_tests/simple_1-1_matching.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -25,12 +24,11 @@
}],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -50,9 +48,9 @@
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_many_trigger_data.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_many_trigger_data.json
index 54acfa226..4c114f61b 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_many_trigger_data.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_many_trigger_data.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801",
"filter_data": {
@@ -28,16 +27,10 @@
"id"
]
},
- "aggregation_keys": [
- {
- "id": "campaignCounts",
- "key_piece": "0x159"
- },
- {
- "id": "geoValue",
- "key_piece": "0x5"
- }
- ]
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
},
"Location": null,
"Attribution-Reporting-Redirect": null
@@ -47,12 +40,11 @@
}],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -116,15 +108,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -132,14 +124,14 @@
}
}],
"aggregatable_results": [{
- "report_time": 800000000002,
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800000600001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
- "source_site": "android-app://example.1s1.com",
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
"histograms": [
- {"key": "1369", "value": 32768},
- {"key": "2949", "value": 1664}
+ {"key": "0x559", "value": 32768},
+ {"key": "0xb85", "value": 1664}
]
}
}]
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_over_contributions_limit.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_over_contributions_limit.json
index 84d38b2a9..1617c590d 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_over_contributions_limit.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_over_contributions_limit.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801",
"filter_data": {
@@ -28,16 +27,10 @@
"id"
]
},
- "aggregation_keys": [
- {
- "id": "campaignCounts",
- "key_piece": "0x159"
- },
- {
- "id": "geoValue",
- "key_piece": "0x5"
- }
- ]
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
},
"Location": null,
"Attribution-Reporting-Redirect": null
@@ -48,12 +41,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -99,16 +91,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -154,7 +145,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800001200001"
}
]
},
@@ -162,20 +153,21 @@
"event_level_results": [
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
- "randomized_trigger_rate": 0.0024263
+ "randomized_trigger_rate": 0.0024263,
+ "source_debug_key" : "347982378"
}
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "3",
"source_type": "navigation",
@@ -184,14 +176,14 @@
}
],
"aggregatable_results": [{
- "report_time": 800000000002,
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800001200001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
- "source_site": "android-app://example.1s1.com",
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
"histograms": [
- {"key": "1369", "value": 32768},
- {"key": "2693", "value": 1664}
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664}
]
}
}]
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_of_three_entries.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_three_of_three_entries.json
index c2c3b6d32..5014d7d06 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_of_three_entries.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_three_of_three_entries.json
@@ -1,19 +1,18 @@
{
- "description": "Given three entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions. One entry doesn't generate a contribution because it doesn't exist in the \"Attribution-Reporting-Register-Aggregatable-Trigger-Data\" header.",
+ "description": "Given three entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with three contributions. One contribution entry has no representation in filters.",
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801",
"filter_data": {
@@ -28,20 +27,11 @@
"id"
]
},
- "aggregation_keys": [
- {
- "id": "campaignCounts",
- "key_piece": "0x159"
- },
- {
- "id": "geoValue",
- "key_piece": "0x5"
- },
- {
- "id": "thirdSource",
- "key_piece": "0x100"
- }
- ]
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5",
+ "thirdSource": "0x100"
+ }
},
"Location": null,
"Attribution-Reporting-Redirect": null
@@ -51,12 +41,11 @@
}],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -103,15 +92,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -119,14 +108,15 @@
}
}],
"aggregatable_results": [{
- "report_time": 800000000002,
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800000600001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
- "source_site": "android-app://example.1s1.com",
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
"histograms": [
- {"key": "1369", "value": 32768},
- {"key": "2693", "value": 1664}
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664},
+ {"key":"0x100", "value":"100"}
]
}
}]
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions.json
index 4dab78c34..c5335acc5 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions.json
@@ -1,19 +1,18 @@
{
- "description": "Given two entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions.",
+ "description": "Given two entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions",
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801",
"filter_data": {
@@ -28,16 +27,10 @@
"id"
]
},
- "aggregation_keys": [
- {
- "id": "campaignCounts",
- "key_piece": "0x159"
- },
- {
- "id": "geoValue",
- "key_piece": "0x5"
- }
- ]
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
},
"Location": null,
"Attribution-Reporting-Redirect": null
@@ -47,12 +40,11 @@
}],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -98,15 +90,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -114,14 +106,14 @@
}
}],
"aggregatable_results": [{
- "report_time": 800000000002,
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800000600001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
- "source_site": "android-app://example.1s1.com",
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
"histograms": [
- {"key": "1369", "value": 32768},
- {"key": "2693", "value": 1664}
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664}
]
}
}]
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api.json
new file mode 100644
index 000000000..4973aec82
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api.json
@@ -0,0 +1,127 @@
+{
+ "description": "Given two entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions with debug keys",
+ "input": {
+ "sources": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "1",
+ "destination": "android-app://example.2d1.test",
+ "priority": "100",
+ "expiry": "172801",
+ "debug_key": "347982378",
+ "filter_data": {
+ "conversion_subdomain": [
+ "electronics.megastore"
+ ],
+ "product": [
+ "1234",
+ "234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000001"
+ }],
+ "triggers": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2",
+ "priority": "101"
+ }
+ ],
+ "debug_key": "8971346783",
+ "aggregatable_trigger_data": [
+ {
+ "key_piece": "0x400",
+ "source_keys": [
+ "campaignCounts"
+ ],
+ "filters": {
+ "product": [
+ "1234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "not_filters": {
+ "product": [
+ "100"
+ ]
+ }
+ },
+ {
+ "key_piece": "0xA80",
+ "source_keys": [
+ "geoValue",
+ "nonMatch"
+ ]
+ }
+ ],
+ "aggregatable_values": {
+ "campaignCounts": 32768,
+ "geoValue": 1664
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000002"
+ }]
+ },
+ "output": {
+ "event_level_results": [{
+ "report_time": "800176400001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "2",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263,
+ "source_debug_key" : "347982378",
+ "trigger_debug_key" : "8971346783"
+ }
+ }],
+ "aggregatable_results": [{
+ "report_time": "800000000002",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
+ "histograms": [
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664}
+ ],
+ "source_debug_key" : "347982378",
+ "trigger_debug_key" : "8971346783"
+ }
+ }]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api_source.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api_source.json
new file mode 100644
index 000000000..01e852825
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api_source.json
@@ -0,0 +1,124 @@
+{
+ "description": "Given two entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions, with source debug key only.",
+ "input": {
+ "sources": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "1",
+ "destination": "android-app://example.2d1.test",
+ "priority": "100",
+ "expiry": "172801",
+ "debug_key": "347982378",
+ "filter_data": {
+ "conversion_subdomain": [
+ "electronics.megastore"
+ ],
+ "product": [
+ "1234",
+ "234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000001"
+ }],
+ "triggers": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2",
+ "priority": "101"
+ }
+ ],
+ "aggregatable_trigger_data": [
+ {
+ "key_piece": "0x400",
+ "source_keys": [
+ "campaignCounts"
+ ],
+ "filters": {
+ "product": [
+ "1234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "not_filters": {
+ "product": [
+ "100"
+ ]
+ }
+ },
+ {
+ "key_piece": "0xA80",
+ "source_keys": [
+ "geoValue",
+ "nonMatch"
+ ]
+ }
+ ],
+ "aggregatable_values": {
+ "campaignCounts": 32768,
+ "geoValue": 1664
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000002"
+ }]
+ },
+ "output": {
+ "event_level_results": [{
+ "report_time": "800176400001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "2",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263,
+ "source_debug_key" : "347982378"
+ }
+ }],
+ "aggregatable_results": [{
+ "report_time": "800000000002",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
+ "histograms": [
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664}
+ ],
+ "source_debug_key" : "347982378"
+ }
+ }]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api_trigger.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api_trigger.json
new file mode 100644
index 000000000..aafad901f
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_debug_api_trigger.json
@@ -0,0 +1,124 @@
+{
+ "description": "Given two entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions, with trigger debug key",
+ "input": {
+ "sources": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "1",
+ "destination": "android-app://example.2d1.test",
+ "priority": "100",
+ "expiry": "172801",
+ "filter_data": {
+ "conversion_subdomain": [
+ "electronics.megastore"
+ ],
+ "product": [
+ "1234",
+ "234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000001"
+ }],
+ "triggers": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2",
+ "priority": "101"
+ }
+ ],
+ "debug_key": "8971346783",
+ "aggregatable_trigger_data": [
+ {
+ "key_piece": "0x400",
+ "source_keys": [
+ "campaignCounts"
+ ],
+ "filters": {
+ "product": [
+ "1234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "not_filters": {
+ "product": [
+ "100"
+ ]
+ }
+ },
+ {
+ "key_piece": "0xA80",
+ "source_keys": [
+ "geoValue",
+ "nonMatch"
+ ]
+ }
+ ],
+ "aggregatable_values": {
+ "campaignCounts": 32768,
+ "geoValue": 1664
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000002"
+ }]
+ },
+ "output": {
+ "event_level_results": [{
+ "report_time": "800176400001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "2",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263,
+ "trigger_debug_key" : "8971346783"
+ }
+ }],
+ "aggregatable_results": [{
+ "report_time": "800000000002",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
+ "histograms": [
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664}
+ ],
+ "trigger_debug_key" : "8971346783"
+ }
+ }]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api.json
new file mode 100644
index 000000000..ce057c327
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api.json
@@ -0,0 +1,127 @@
+{
+ "description": "Given two entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions with debug keys and without adid permission",
+ "input": {
+ "sources": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_origin": "android-app://example.1s1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test",
+ "is_adid_permission_granted": false
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "1",
+ "destination": "android-app://example.2d1.test",
+ "priority": "100",
+ "expiry": "172801",
+ "debug_key": "347982378",
+ "filter_data": {
+ "conversion_subdomain": [
+ "electronics.megastore"
+ ],
+ "product": [
+ "1234",
+ "234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000001"
+ }],
+ "triggers": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "destination_origin": "android-app://example.2d1.test",
+ "registrant": "example.2d1.test",
+ "is_adid_permission_granted": false
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2",
+ "priority": "101"
+ }
+ ],
+ "debug_key": "8971346783",
+ "aggregatable_trigger_data": [
+ {
+ "key_piece": "0x400",
+ "source_keys": [
+ "campaignCounts"
+ ],
+ "filters": {
+ "product": [
+ "1234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "not_filters": {
+ "product": [
+ "100"
+ ]
+ }
+ },
+ {
+ "key_piece": "0xA80",
+ "source_keys": [
+ "geoValue",
+ "nonMatch"
+ ]
+ }
+ ],
+ "aggregatable_values": {
+ "campaignCounts": 32768,
+ "geoValue": 1664
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000002"
+ }]
+ },
+ "output": {
+ "event_level_results": [{
+ "report_time": "800176400001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "2",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263
+ }
+ }],
+ "aggregatable_results": [{
+ "report_time": "800000000002",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
+ "histograms": [
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664}
+ ]
+ }
+ }]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api_source.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api_source.json
new file mode 100644
index 000000000..ad8955513
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api_source.json
@@ -0,0 +1,126 @@
+{
+ "description": "Given two entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions, with source debug key only and without adid permission",
+ "input": {
+ "sources": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_origin": "android-app://example.1s1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test",
+ "is_adid_permission_granted": false
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "1",
+ "destination": "android-app://example.2d1.test",
+ "priority": "100",
+ "expiry": "172801",
+ "debug_key": "347982378",
+ "filter_data": {
+ "conversion_subdomain": [
+ "electronics.megastore"
+ ],
+ "product": [
+ "1234",
+ "234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000001"
+ }],
+ "triggers": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "destination_origin": "android-app://example.2d1.test",
+ "registrant": "example.2d1.test",
+ "is_adid_permission_granted": false
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2",
+ "priority": "101"
+ }
+ ],
+ "aggregatable_trigger_data": [
+ {
+ "key_piece": "0x400",
+ "source_keys": [
+ "campaignCounts"
+ ],
+ "filters": {
+ "product": [
+ "1234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "not_filters": {
+ "product": [
+ "100"
+ ]
+ }
+ },
+ {
+ "key_piece": "0xA80",
+ "source_keys": [
+ "geoValue",
+ "nonMatch"
+ ]
+ }
+ ],
+ "aggregatable_values": {
+ "campaignCounts": 32768,
+ "geoValue": 1664
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000002"
+ }]
+ },
+ "output": {
+ "event_level_results": [{
+ "report_time": "800176400001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "2",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263
+ }
+ }],
+ "aggregatable_results": [{
+ "report_time": "800000000002",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
+ "histograms": [
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664}
+ ]
+ }
+ }]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api_trigger.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api_trigger.json
new file mode 100644
index 000000000..faedaba3f
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/aggregatable_two_contributions_no_adid_debug_api_trigger.json
@@ -0,0 +1,126 @@
+{
+ "description": "Given two entries in the \"Attribution-Reporting-Register-Aggregatable-Source\" header, generate an aggregate report with two contributions, with trigger debug key and without adid permission",
+ "input": {
+ "sources": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_origin": "android-app://example.1s1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test",
+ "is_adid_permission_granted": false
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "1",
+ "destination": "android-app://example.2d1.test",
+ "priority": "100",
+ "expiry": "172801",
+ "filter_data": {
+ "conversion_subdomain": [
+ "electronics.megastore"
+ ],
+ "product": [
+ "1234",
+ "234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "aggregation_keys": {
+ "campaignCounts": "0x159",
+ "geoValue": "0x5"
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000001"
+ }],
+ "triggers": [{
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "destination_origin": "android-app://example.2d1.test",
+ "registrant": "example.2d1.test",
+ "is_adid_permission_granted": false
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2",
+ "priority": "101"
+ }
+ ],
+ "debug_key": "8971346783",
+ "aggregatable_trigger_data": [
+ {
+ "key_piece": "0x400",
+ "source_keys": [
+ "campaignCounts"
+ ],
+ "filters": {
+ "product": [
+ "1234"
+ ],
+ "ctid": [
+ "id"
+ ]
+ },
+ "not_filters": {
+ "product": [
+ "100"
+ ]
+ }
+ },
+ {
+ "key_piece": "0xA80",
+ "source_keys": [
+ "geoValue",
+ "nonMatch"
+ ]
+ }
+ ],
+ "aggregatable_values": {
+ "campaignCounts": 32768,
+ "geoValue": 1664
+ }
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000002"
+ }]
+ },
+ "output": {
+ "event_level_results": [{
+ "report_time": "800176400001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "2",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263
+ }
+ }],
+ "aggregatable_results": [{
+ "report_time": "800000000002",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_site": "android-app://example.1s1.test",
+ "histograms": [
+ {"key": "0x559", "value": 32768},
+ {"key": "0xa85", "value": 1664}
+ ]
+ }
+ }]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/basic_use_of_redirect.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/basic_use_of_redirect.json
index 83e8126b5..77cd4fe88 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/basic_use_of_redirect.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/basic_use_of_redirect.json
@@ -3,34 +3,33 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [
{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
"Location": null,
"Attribution-Reporting-Redirect": [
- "https://www.ad-tech2.com",
- "https://www.ad-tech3.com"
+ "https://www.ad-tech2.test",
+ "https://www.ad-tech3.test"
]
}
},
{
- "url": "https://www.ad-tech2.com",
+ "url": "https://www.ad-tech2.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -40,11 +39,11 @@
}
},
{
- "url": "https://www.ad-tech3.com",
+ "url": "https://www.ad-tech3.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "3",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "800172800002"
@@ -58,13 +57,12 @@
}],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [
{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -76,13 +74,13 @@
},
"Location": null,
"Attribution-Reporting-Redirect": [
- "https://www.ad-tech2.com",
- "https://www.ad-tech3.com"
+ "https://www.ad-tech2.test",
+ "https://www.ad-tech3.test"
]
}
},
{
- "url": "https://www.ad-tech2.com",
+ "url": "https://www.ad-tech2.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -97,7 +95,7 @@
}
},
{
- "url": "https://www.ad-tech3.com",
+ "url": "https://www.ad-tech3.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -112,16 +110,16 @@
}
}
],
- "timestamp": "800000000003"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
@@ -130,9 +128,9 @@
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech2.com",
+ "report_url": "https://www.ad-tech2.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "2",
"source_type": "navigation",
@@ -141,9 +139,9 @@
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech3.com",
+ "report_url": "https://www.ad-tech3.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "3",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_default_expiry.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_default_expiry.json
index 2d6c17aa1..948e17fb4 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_default_expiry.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_default_expiry.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100"
},
"Location": null,
@@ -25,12 +24,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -44,16 +42,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800172800000"
+ "timestamp": "800172200000"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -67,16 +64,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800172800002"
+ "timestamp": "800173400001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -90,7 +86,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800000"
+ "timestamp": "800604200001"
}
]
},
@@ -98,9 +94,9 @@
"event_level_results": [
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
@@ -109,9 +105,9 @@
},
{
"report_time": "800608400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -120,9 +116,9 @@
},
{
"report_time": "800608400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_provided_expiry.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_provided_expiry.json
index 1645008b0..e0168e39e 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_provided_expiry.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/click_report_schedule_2d_7d_provided_expiry.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "1728000"
},
@@ -26,12 +25,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -45,16 +43,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800172800003"
+ "timestamp": "800173400001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -68,16 +65,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800000"
+ "timestamp": "800604200001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -91,7 +87,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800002"
+ "timestamp": "800605400001"
}
]
},
@@ -99,9 +95,9 @@
"event_level_results": [
{
"report_time": "800608400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
@@ -110,9 +106,9 @@
},
{
"report_time": "800608400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -121,9 +117,9 @@
},
{
"report_time": "801731600001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/event_time_based_source_selection.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/event_time_based_source_selection.json
index 15c3ed762..8a08bcd4c 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/event_time_based_source_selection.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/event_time_based_source_selection.json
@@ -4,17 +4,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -26,17 +25,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -44,17 +42,16 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}
],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -68,15 +65,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001200001"
}]
},
"output": {
"event_level_results": [{
- "report_time": "800176400002",
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800177000001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/expired_source.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/expired_source.json
new file mode 100644
index 000000000..156c86de9
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/expired_source.json
@@ -0,0 +1,54 @@
+{
+ "description": "trigger not attributed to an expired source",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "android-app://example.2d1.test",
+ "source_event_id": "123",
+ "expiry": "172800"
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643408373000",
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [],
+ "aggregatable_results": []
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_no_cooldown.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_no_cooldown.json
index 8874501bf..a8124f878 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_no_cooldown.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_no_cooldown.json
@@ -4,17 +4,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"expiry": "1728000"
@@ -27,17 +26,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "0",
"install_attribution_window": "172800",
"expiry": "1728000"
@@ -50,17 +48,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "3",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"expiry": "1728000"
@@ -75,12 +72,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -97,12 +93,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -119,12 +114,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -141,7 +135,7 @@
}
],
"installs": [{
- "uri": "android-app://example.2d1.com",
+ "uri": "android-app://example.2d1.test",
"timestamp": "800172800001"
}]
},
@@ -149,9 +143,9 @@
"event_level_results": [
{
"report_time": "800435600001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "3",
"trigger_data": "1",
"source_type": "navigation",
@@ -160,9 +154,9 @@
},
{
"report_time": "800867600001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "3",
"trigger_data": "2",
"source_type": "navigation",
@@ -171,9 +165,9 @@
},
{
"report_time": "801990800001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "3",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_outside_window.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_outside_window.json
index 83ae33558..0dd0508d1 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_outside_window.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_outside_window.json
@@ -4,17 +4,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -29,17 +28,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -55,12 +53,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -77,7 +74,7 @@
}
],
"installs": [{
- "uri": "android-app://example.2d1.com",
+ "uri": "android-app://example.2d1.test",
"timestamp": "800432000001"
}]
},
@@ -85,9 +82,9 @@
"event_level_results": [
{
"report_time": "800694800001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "1",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_reinstall.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_reinstall.json
index 8dc6321a3..f5d866f15 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_reinstall.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_reinstall.json
@@ -4,17 +4,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -28,17 +27,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -54,12 +52,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -76,12 +73,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -99,16 +95,16 @@
],
"installs": [
{
- "uri": "android-app://example.2d1.com",
+ "uri": "android-app://example.2d1.test",
"timestamp": "800086400001"
},
{
- "uri": "android-app://example.2d1.com",
+ "uri": "android-app://example.2d1.test",
"timestamp": "800432000001"
}
],
"uninstalls": [{
- "uri": "android-app://example.2d1.com",
+ "uri": "android-app://example.2d1.test",
"timestamp": "800259200001"
}]
},
@@ -116,9 +112,9 @@
"event_level_results": [
{
"report_time": "800954000001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown.json
index 8ea51177e..63f237853 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown.json
@@ -4,17 +4,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -28,17 +27,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "0",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -52,17 +50,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "3",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -78,12 +75,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -100,12 +96,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -122,12 +117,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -144,7 +138,7 @@
}
],
"installs": [{
- "uri": "android-app://example.2d1.com",
+ "uri": "android-app://example.2d1.test",
"timestamp": "800172800001"
}]
},
@@ -152,9 +146,9 @@
"event_level_results": [
{
"report_time": "800608400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
@@ -163,9 +157,9 @@
},
{
"report_time": "800608400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -174,9 +168,9 @@
},
{
"report_time": "801731600001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown_1_day_window.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown_1_day_window.json
new file mode 100644
index 000000000..79216e275
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_simple_with_cooldown_1_day_window.json
@@ -0,0 +1,183 @@
+{
+ "description": "Two sources with 1-day install attribution windows, followed by an install, a source and three triggers. The triggers are all attributed to the first source, whose priority matches the first and is higher than the second source's. All three sources have an install cool-down window of 10 days, which overrides the third source.",
+ "input": {
+ "sources": [
+ {
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "1",
+ "destination": "android-app://example.2d1.test",
+ "priority": "1",
+ "install_attribution_window": "86400",
+ "post_install_exclusivity_window": "864000",
+ "expiry": "1728000"
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800000000001"
+ },
+ {
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_type": "event",
+ "registrant": "example.1s1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "2",
+ "destination": "android-app://example.2d1.test",
+ "priority": "0",
+ "install_attribution_window": "86400",
+ "post_install_exclusivity_window": "864000",
+ "expiry": "1728000"
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800043200001"
+ },
+ {
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "source_type": "navigation",
+ "registrant": "example.1s1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "source_event_id": "3",
+ "destination": "android-app://example.2d1.test",
+ "priority": "1",
+ "install_attribution_window": "172800",
+ "post_install_exclusivity_window": "864000",
+ "expiry": "1728000"
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800259200001"
+ }
+ ],
+ "triggers": [
+ {
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1"
+ }
+ ]
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800345600001"
+ },
+ {
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2"
+ }
+ ]
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800518400001"
+ },
+ {
+ "registration_request": {
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
+ },
+ "responses": [{
+ "url": "https://www.ad-tech1.test",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "3"
+ }
+ ]
+ },
+ "Location": null,
+ "Attribution-Reporting-Redirect": null
+ }
+ }],
+ "timestamp": "800950400001"
+ }
+ ],
+ "installs": [{
+ "uri": "android-app://example.2d1.test",
+ "timestamp": "800086400001"
+ }]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "report_time": "800608400001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "1",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263
+ }
+ },
+ {
+ "report_time": "800608400001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "2",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263
+ }
+ },
+ {
+ "report_time": "801731600001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+ "payload": {
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
+ "trigger_data": "3",
+ "source_type": "navigation",
+ "randomized_trigger_rate": 0.0024263
+ }
+ }
+ ],
+ "aggregatable_results": []
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_vtc_with_lose_once_lose_always.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_vtc_with_lose_once_lose_always.json
index d524c426c..55109fac8 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_vtc_with_lose_once_lose_always.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/install_vtc_with_lose_once_lose_always.json
@@ -4,17 +4,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "0",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -28,17 +27,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "1",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -52,17 +50,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "3",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "2",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -76,17 +73,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "4",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "2",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -100,17 +96,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "5",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "2",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -124,17 +119,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "6",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "2",
"install_attribution_window": "172800",
"post_install_exclusivity_window": "864000",
@@ -150,12 +144,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -172,12 +165,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -194,12 +186,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -216,12 +207,11 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -238,7 +228,7 @@
}
],
"installs": [{
- "uri": "android-app://example.2d1.com",
+ "uri": "android-app://example.2d1.test",
"timestamp": "800172800001"
}]
},
@@ -246,9 +236,9 @@
"event_level_results": [
{
"report_time": "801386000001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "1",
"source_type": "event",
@@ -257,9 +247,9 @@
},
{
"report_time": "801386000001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "1",
"source_type": "event",
@@ -268,9 +258,9 @@
},
{
"report_time": "802250000001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "6",
"trigger_data": "1",
"source_type": "event",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_click.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_click.json
index a837c1cfd..d4bd54236 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_click.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_click.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -26,12 +25,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -45,16 +43,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -68,16 +65,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001200001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -91,16 +87,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000004"
+ "timestamp": "800001800001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -114,7 +109,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000005"
+ "timestamp": "800002400001"
}
]
},
@@ -122,9 +117,9 @@
"event_level_results": [
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
@@ -133,9 +128,9 @@
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -144,9 +139,9 @@
},
{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_view.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_view.json
index e65aa7fbf..879fdc063 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_view.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/limit_num_reports_for_view.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -26,12 +25,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -45,16 +43,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -68,16 +65,16 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001200001"
}
]
},
"output": {
"event_level_results": [{
- "report_time": "800176400002",
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800177000001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "event",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/no_matching_sources.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/no_matching_sources.json
index cc474a0c4..dbc5bd4f3 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/no_matching_sources.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/no_matching_sources.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -25,12 +24,11 @@
}],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.3s3.com",
- "registrant": "example.3s3.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.3s3.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -44,7 +42,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/only_one_source_of_multiple_matches_can_be_attributed.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/only_one_source_of_multiple_matches_can_be_attributed.json
index 1e2151103..38f031f22 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/only_one_source_of_multiple_matches_can_be_attributed.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/only_one_source_of_multiple_matches_can_be_attributed.json
@@ -5,17 +5,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -27,17 +26,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172802"
},
@@ -45,18 +43,17 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}
],
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -70,16 +67,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001200001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -93,17 +89,17 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000004"
+ "timestamp": "800001800001"
}
]
},
"output": {
"event_level_results": [
{
- "report_time": "800176400002",
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800177000001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "1",
"source_type": "navigation",
@@ -111,10 +107,10 @@
}
},
{
- "report_time": "800176400002",
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800177000001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/priority_based_source_selection.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/priority_based_source_selection.json
index 168af5368..ba32c9a33 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/priority_based_source_selection.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/priority_based_source_selection.json
@@ -1,20 +1,19 @@
{
- "description": "Two sources with differing priorities and equal event-time, and one trigger that matches both sources. Event report task within the expiry window sends the data corresponding to the source with higher priority.",
+ "description": "Two sources with the earlier one having higher priority, and one trigger that matches both sources. Event report task within the expiry window sends the data corresponding to the source with higher priority.",
"input": {
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -26,35 +25,33 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
- "priority": "200",
+ "destination": "android-app://example.2d1.test",
+ "priority": "20",
"expiry": "172801"
},
"Location": null,
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000001"
+ "timestamp": "800000600001"
}
],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -68,16 +65,16 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001200001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
- "source_event_id": "2",
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
"randomized_trigger_rate": 0.0024263
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWebSource_3_sources_3_triggers_3_reports.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWebSource_3_sources_3_triggers_3_reports.json
index 219315a5f..e00b2c9e0 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWebSource_3_sources_3_triggers_3_reports.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWebSource_3_sources_3_triggers_3_reports.json
@@ -3,26 +3,26 @@
"input": {
"web_sources": [{
"registration_request": {
- "source_origin": "android-app://example.1s1.com",
+ "source_origin": "android-app://example.1s1.test",
"source_type": "navigation",
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"app_destination": null,
- "registrant": "example.1s1.com",
+ "registrant": "example.1s1.test",
"source_params": [{
- "attribution_src_url": "https://www.ad-tech1.com"
+ "attribution_src_url": "https://www.ad-tech1.test"
}, {
- "attribution_src_url": "https://www.ad-tech2.com"
+ "attribution_src_url": "https://www.ad-tech2.test"
}, {
- "attribution_src_url": "https://www.ad-tech3.com"
+ "attribution_src_url": "https://www.ad-tech3.test"
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
"destination": null,
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -30,12 +30,12 @@
"Attribution-Reporting-Redirect": null
}
}, {
- "url": "https://www.ad-tech2.com",
+ "url": "https://www.ad-tech2.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
"destination": null,
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"priority": "101",
"expiry": "172801"
},
@@ -43,12 +43,12 @@
"Attribution-Reporting-Redirect": null
}
},{
- "url": "https://www.ad-tech3.com",
+ "url": "https://www.ad-tech3.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "3",
"destination": null,
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"priority": "102",
"expiry": "172801"
},
@@ -60,18 +60,18 @@
}],
"web_triggers": [{
"registration_request": {
- "destination_origin": "https://example.1d1.com",
- "registrant": "example.2d1.com",
+ "destination_origin": "https://example.1d1.test",
+ "registrant": "example.2d1.test",
"trigger_params": [{
- "attribution_src_url": "https://www.ad-tech1.com"
+ "attribution_src_url": "https://www.ad-tech1.test"
}, {
- "attribution_src_url": "https://www.ad-tech2.com"
+ "attribution_src_url": "https://www.ad-tech2.test"
}, {
- "attribution_src_url": "https://www.ad-tech3.com"
+ "attribution_src_url": "https://www.ad-tech3.test"
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -83,11 +83,11 @@
},
"Location": null,
"Attribution-Reporting-Redirect": [
- "https://www.ad-tech2.com"
+ "https://www.ad-tech2.test"
]
}
}, {
- "url": "https://www.ad-tech2.com",
+ "url": "https://www.ad-tech2.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -101,7 +101,7 @@
"Attribution-Reporting-Redirect": null
}
}, {
- "url": "https://www.ad-tech3.com",
+ "url": "https://www.ad-tech3.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -115,15 +115,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "https://example.1d1.com",
+ "attribution_destination": "https://1d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
@@ -131,9 +131,9 @@
}
}, {
"report_time": "800176400001",
- "report_url": "https://www.ad-tech2.com",
+ "report_url": "https://www.ad-tech2.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "https://example.1d1.com",
+ "attribution_destination": "https://1d1.test",
"source_event_id": "2",
"trigger_data": "2",
"source_type": "navigation",
@@ -141,9 +141,9 @@
}
}, {
"report_time": "800176400001",
- "report_url": "https://www.ad-tech3.com",
+ "report_url": "https://www.ad-tech3.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "https://example.1d1.com",
+ "attribution_destination": "https://1d1.test",
"source_event_id": "3",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-1_web-web_matching.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-1_web-web_matching.json
index f18997a15..9bc0c89ce 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-1_web-web_matching.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-1_web-web_matching.json
@@ -3,21 +3,21 @@
"input": {
"web_sources": [{
"registration_request": {
- "source_origin": "android-app://example.1s1.com",
+ "source_origin": "android-app://example.1s1.test",
"source_type": "navigation",
- "web_destination": "https://example.2d1.com",
- "registrant": "example.1s1.com",
+ "web_destination": "https://example.2d1.test",
+ "registrant": "example.1s1.test",
"source_params": [{
- "attribution_src_url": "https://www.ad-tech1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"debug_key" : false
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "web_destination": "https://example.2d1.com",
+ "web_destination": "https://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -29,15 +29,15 @@
}],
"web_triggers": [{
"registration_request": {
- "destination_origin": "https://example.2d1.com",
- "registrant": "example.2d1.com",
+ "destination_origin": "https://example.2d1.test",
+ "registrant": "example.2d1.test",
"trigger_params": [{
- "attribution_src_url": "https://www.ad-tech1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"debug_key" : false
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -51,15 +51,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "https://example.2d1.com",
+ "attribution_destination": "https://2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_app_and_web_matching.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_app_and_web_matching.json
index d737fec84..17fd1738d 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_app_and_web_matching.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_app_and_web_matching.json
@@ -3,22 +3,22 @@
"input": {
"web_sources": [{
"registration_request": {
- "source_origin": "android-app://example.1s1.com",
+ "source_origin": "android-app://example.1s1.test",
"source_type": "navigation",
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"app_destination": "android-app://com.example.2d1",
- "registrant": "example.1s1.com",
+ "registrant": "example.1s1.test",
"source_params": [{
- "attribution_src_url": "https://www.ad-tech1.com"
+ "attribution_src_url": "https://www.ad-tech1.test"
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
"destination": "android-app://com.example.2d1",
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -30,14 +30,14 @@
}],
"web_triggers": [{
"registration_request": {
- "destination_origin": "https://example.1d1.com",
- "registrant": "example.2d1.com",
+ "destination_origin": "https://example.1d1.test",
+ "registrant": "example.2d1.test",
"trigger_params": [{
- "attribution_src_url": "https://www.ad-tech1.com"
+ "attribution_src_url": "https://www.ad-tech1.test"
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -50,16 +50,15 @@
"Location": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001200001"
}],
"triggers" : [{
"registration_request": {
- "destination_origin": "android-app://com.example.2d1",
- "registrant": "example.2d1.com",
- "attribution_src_url": "https://www.ad-tech1.com"
+ "registrant": "com.example.2d1",
+ "attribution_src_url": "https://www.ad-tech1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -72,13 +71,13 @@
"Location": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
"attribution_destination": "android-app://com.example.2d1",
"source_event_id": "1",
@@ -88,9 +87,9 @@
}
}, {
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "https://example.1d1.com",
+ "attribution_destination": "https://1d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_web_only_matching.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_web_only_matching.json
index ac15c7b27..747d5bd41 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_web_only_matching.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_1-2_web_only_matching.json
@@ -3,21 +3,21 @@
"input": {
"web_sources": [{
"registration_request": {
- "source_origin": "android-app://example.1s1.com",
+ "source_origin": "android-app://example.1s1.test",
"source_type": "navigation",
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"app_destination": null,
- "registrant": "example.1s1.com",
+ "registrant": "example.1s1.test",
"source_params": [{
- "attribution_src_url": "https://www.ad-tech1.com"
+ "attribution_src_url": "https://www.ad-tech1.test"
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -29,14 +29,14 @@
}],
"web_triggers": [{
"registration_request": {
- "destination_origin": "https://example.1d1.com",
- "registrant": "example.2d1.com",
+ "destination_origin": "https://example.1d1.test",
+ "registrant": "example.2d1.test",
"trigger_params": [{
- "attribution_src_url": "https://www.ad-tech1.com"
+ "attribution_src_url": "https://www.ad-tech1.test"
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -49,16 +49,15 @@
"Location": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001200001"
}],
"triggers": [{
"registration_request": {
- "destination_origin": "android-app://com.example.2d1",
- "registrant": "example.2d1.com",
- "attribution_src_url": "https://www.ad-tech1.com"
+ "registrant": "example.2d1.test",
+ "attribution_src_url": "https://www.ad-tech1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -71,15 +70,15 @@
"Location": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "https://example.1d1.com",
+ "attribution_destination": "https://1d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_redirects_ignored.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_redirects_ignored.json
index 9adcbbd37..ae0ba7de5 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_redirects_ignored.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/registerWeb_redirects_ignored.json
@@ -3,44 +3,44 @@
"input": {
"web_sources": [{
"registration_request": {
- "source_origin": "android-app://example.1s1.com",
+ "source_origin": "android-app://example.1s1.test",
"source_type": "navigation",
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"app_destination": "android-app://com.example.2d1",
- "registrant": "example.1s1.com",
+ "registrant": "example.1s1.test",
"source_params": [{
- "attribution_src_url": "https://www.ad-tech1.com"
+ "attribution_src_url": "https://www.ad-tech1.test"
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
"destination": "android-app://com.example.2d1",
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"priority": "100",
"expiry": "172801"
},
"Location": null,
"Attribution-Reporting-Redirect": [
- "https://www.ad-tech2.com",
+ "https://www.ad-tech2.test",
"some_random_url_which_will_be_ignored_anyway"
]
}
}, {
- "url": "https://www.ad-tech2.com",
+ "url": "https://www.ad-tech2.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
"destination": "android-app://com.example.2d1",
- "web_destination": "https://example.1d1.com",
+ "web_destination": "https://example.1d1.test",
"priority": "101",
"expiry": "172802"
},
"Location": null,
"Attribution-Reporting-Redirect": [
- "https://www.ad-tech2.com",
+ "https://www.ad-tech2.test",
"some_random_url_which_will_be_ignored_anyway"
]
}
@@ -49,14 +49,14 @@
}],
"web_triggers": [{
"registration_request": {
- "destination_origin": "https://example.1d1.com",
- "registrant": "example.2d1.com",
+ "destination_origin": "https://example.1d1.test",
+ "registrant": "example.2d1.test",
"trigger_params": [{
- "attribution_src_url": "https://www.ad-tech1.com"
+ "attribution_src_url": "https://www.ad-tech1.test"
}]
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -69,12 +69,12 @@
},
"Location": null,
"Attribution-Reporting-Redirect": [
- "https://www.ad-tech2.com",
+ "https://www.ad-tech2.test",
"some_random_url_which_will_be_ignored_anyway"
]
}
}, {
- "url": "https://www.ad-tech2.com",
+ "url": "https://www.ad-tech2.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -89,15 +89,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "https://example.1d1.com",
+ "attribution_destination": "https://1d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/register_1-1_app-app_matching.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/register_1-1_app-app_matching.json
index 4dcfa2c74..eff76d95b 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/register_1-1_app-app_matching.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/register_1-1_app-app_matching.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -25,12 +24,11 @@
}],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -44,15 +42,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_one_trigger.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_one_trigger.json
index c30225ce7..cc972f02d 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_one_trigger.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_one_trigger.json
@@ -1,20 +1,19 @@
{
- "description": "Three sources with differing priorities and equal event-time, and one trigger that matches all three sources. Event report task within the expiry window sends the data corresponding to the source with highest priority.",
+ "description": "Three sources with different order of priority than event-time, and one trigger that matches all three sources. Event report task within the expiry window sends the data corresponding to the source with highest priority.",
"input": {
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -26,17 +25,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "200",
"expiry": "172801"
},
@@ -44,39 +42,37 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000001"
+ "timestamp": "800000600001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "3",
- "destination": "android-app://example.2d1.com",
- "priority": "300",
+ "destination": "android-app://example.2d1.test",
+ "priority": "30",
"expiry": "172801"
},
"Location": null,
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000001"
+ "timestamp": "800001200001"
}
],
"triggers": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -90,16 +86,16 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001800001"
}]
},
"output": {
"event_level_results": [{
"report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
- "source_event_id": "3",
+ "attribution_destination": "android-app://example.2d1.test",
+ "source_event_id": "2",
"trigger_data": "2",
"source_type": "navigation",
"randomized_trigger_rate": 0.0024263
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_three_triggers_one_dedup.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_three_triggers_one_dedup.json
index 97a7243d6..7a2b1c683 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_three_triggers_one_dedup.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/three_sources_three_triggers_one_dedup.json
@@ -4,17 +4,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "20",
"expiry": "172801"
},
@@ -26,17 +25,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "2",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "20",
"expiry": "172801"
},
@@ -44,21 +42,20 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000002"
+ "timestamp": "800000600001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "3",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "10",
"expiry": "172801"
},
@@ -66,18 +63,17 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000003"
+ "timestamp": "800001200001"
}
],
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -92,16 +88,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000004"
+ "timestamp": "800001800001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -116,16 +111,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000005"
+ "timestamp": "800002400001"
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -140,17 +134,17 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000000006"
+ "timestamp": "800003000001"
}
]
},
"output": {
"event_level_results": [
{
- "report_time": "800176400002",
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800177000001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "1",
"source_type": "navigation",
@@ -158,10 +152,10 @@
}
},
{
- "report_time": "800176400002",
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800177000001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "2",
"trigger_data": "0",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/two_simple_matches_testing_multiplicity.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/two_simple_matches_testing_multiplicity.json
index d64267a1c..75afd0d0b 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/two_simple_matches_testing_multiplicity.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/two_simple_matches_testing_multiplicity.json
@@ -4,17 +4,16 @@
"sources": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -26,17 +25,16 @@
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.3d1.com",
+ "destination": "android-app://example.3d1.test",
"priority": "100",
"expiry": "172801"
},
@@ -50,12 +48,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -69,16 +66,15 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": 800000000002
+ "timestamp": 800000600001
},
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.3d1.com",
- "registrant": "example.3d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.3d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -92,7 +88,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": 800000000002
+ "timestamp": 800000600001
}
]
},
@@ -100,9 +96,9 @@
"event_level_results": [
{
"report_time": 800176400001,
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
@@ -111,9 +107,9 @@
},
{
"report_time": 800176400001,
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.3d1.com",
+ "attribution_destination": "android-app://example.3d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_filters.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_filters.json
index ca2dbd2ec..54150733a 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_filters.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_filters.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"filter_data": {
"top_key_1": [
@@ -35,12 +34,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -85,7 +83,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800000"
+ "timestamp": "800604200001"
}
]
},
@@ -93,9 +91,9 @@
"event_level_results": [
{
"report_time": "800608400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "2",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_notfilters.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_notfilters.json
index 67acf09e4..c90eb250b 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_notfilters.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_event_level_notfilters.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "navigation",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"filter_data": {
"top_key_1": [
@@ -35,12 +34,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -85,7 +83,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800000"
+ "timestamp": "800604200001"
}
]
},
@@ -93,9 +91,9 @@
"event_level_results": [
{
"report_time": "800608400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "3",
"source_type": "navigation",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_default_expiry.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_default_expiry.json
index 2de3205fa..1643cddb2 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_default_expiry.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_default_expiry.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100"
},
"Location": null,
@@ -25,12 +24,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -44,7 +42,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800000"
+ "timestamp": "800604200001"
}
]
},
@@ -52,9 +50,9 @@
"event_level_results": [
{
"report_time": "802595600001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "event",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_early.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_early.json
index f1b1e88f2..2e622be04 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_early.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_early.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "17280"
},
@@ -26,12 +25,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -45,17 +43,17 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800000162800"
+ "timestamp": "800000600001"
}
]
},
"output": {
"event_level_results": [
{
- "report_time": "800176400001",
- "report_url": "https://www.ad-tech1.com",
+ "report_time": "800090000001",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "event",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_late.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_late.json
index 0f7d85e40..a01961fe3 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_late.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_expiry_too_late.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "3456000"
},
@@ -26,12 +25,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -45,7 +43,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800000"
+ "timestamp": "800604200001"
}
]
},
@@ -53,9 +51,9 @@
"event_level_results": [
{
"report_time": "802595600001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "event",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_provided_expiry.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_provided_expiry.json
index 50df6f0de..07f96d331 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_provided_expiry.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_schedule_provided_expiry.json
@@ -3,17 +3,16 @@
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"expiry": "1728001"
},
@@ -26,12 +25,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -45,7 +43,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800002"
+ "timestamp": "800605400001"
}
]
},
@@ -53,9 +51,9 @@
"event_level_results": [
{
"report_time": "801731600001",
- "report_url": "https://www.ad-tech1.com",
+ "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
"payload": {
- "attribution_destination": "android-app://example.2d1.com",
+ "attribution_destination": "android-app://example.2d1.test",
"source_event_id": "1",
"trigger_data": "1",
"source_type": "event",
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_top_level_filters_mismatch.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_top_level_filters_mismatch.json
index 1b6e9be12..963886168 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_top_level_filters_mismatch.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/view_report_top_level_filters_mismatch.json
@@ -1,19 +1,18 @@
{
- "description": "One source with filter data. Trigger also has filters. Trigger's top level filters don't match with source's, so no report is generated.",
+ "description": "One source with filter data. Trigger also has filters. Trigger's top level filters do not match with source's, so no report is generated.",
"input": {
"sources": [{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "source_origin": "android-app://example.1s1.com",
+ "attribution_src_url": "https://www.ad-tech1.test",
"source_type": "event",
- "registrant": "example.1s1.com"
+ "registrant": "example.1s1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Source": {
"source_event_id": "1",
- "destination": "android-app://example.2d1.com",
+ "destination": "android-app://example.2d1.test",
"priority": "100",
"filter_data": {
"key_1": [
@@ -35,12 +34,11 @@
"triggers": [
{
"registration_request": {
- "attribution_src_url": "https://www.ad-tech1.com",
- "destination_origin": "android-app://example.2d1.com",
- "registrant": "example.2d1.com"
+ "attribution_src_url": "https://www.ad-tech1.test",
+ "registrant": "example.2d1.test"
},
"responses": [{
- "url": "https://www.ad-tech1.com",
+ "url": "https://www.ad-tech1.test",
"response": {
"Attribution-Reporting-Register-Trigger": {
"event_trigger_data": [
@@ -64,7 +62,7 @@
"Attribution-Reporting-Redirect": null
}
}],
- "timestamp": "800604800000"
+ "timestamp": "800604200001"
}
]
},
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/aggregatable_budget.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/aggregatable_budget.json
new file mode 100644
index 000000000..2ab61774c
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/aggregatable_budget.json
@@ -0,0 +1,214 @@
+{
+ "description": "Max aggregatable budget per source",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123",
+ "aggregation_keys": {
+ "a": "0x159",
+ "b": "0x5"
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235583000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123",
+ "aggregation_keys": {
+ "a": "0x159",
+ "b": "0x5"
+ }
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["a"],
+ "key_piece": "0x400"
+ },
+ {
+ "source_keys": ["b"],
+ "key_piece": "0xA80"
+ }
+ ],
+ "aggregatable_values": {
+ "a": 65530,
+ "b": 7
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["a"],
+ "key_piece": "0x400"
+ }
+ ],
+ "aggregatable_values": {
+ "a": 65535
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235576000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["b"],
+ "key_piece": "0xa80"
+ }
+ ],
+ "aggregatable_values": {
+ "a": 2
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235577000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["b"],
+ "key_piece": "0xa80"
+ }
+ ],
+ "aggregatable_values": {
+ "b": 1
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235584000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["b"],
+ "key_piece": "0xa80"
+ }
+ ],
+ "aggregatable_values": {
+ "b": 65536
+ }
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "aggregatable_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "histograms": [
+ {
+ "key": "0x559",
+ "value": 65535
+ }
+ ]
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "report_time": "1643239175000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "histograms": [
+ {
+ "key": "0xa85",
+ "value": 1
+ }
+ ]
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "report_time": "1643239177000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "histograms": [
+ {
+ "key": "0xa85",
+ "value": 65536
+ }
+ ]
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "report_time": "1643239184000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/aggregatable_contributions_creation.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/aggregatable_contributions_creation.json
new file mode 100644
index 000000000..04d202436
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/aggregatable_contributions_creation.json
@@ -0,0 +1,233 @@
+{
+ "description": "Aggregatable contributions creation",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123",
+ "aggregation_keys": {
+ "a": "0x1",
+ "b": "0x2",
+ "c": "0x4"
+ },
+ "filter_data": {
+ "product": ["123", "456"],
+ "geo": []
+ }
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["a", "b","c"],
+ "key_piece": "0x8",
+ "filters": {
+ "product": [],
+ "source_type": ["navigation"]
+ }
+ },
+ {
+ "source_keys": ["a", "b", "c"],
+ "key_piece": "0x8",
+ "filters": {
+ "geo": [],
+ "source_type": ["event"]
+ }
+ },
+ {
+ "source_keys": ["a", "b", "c'"],
+ "key_piece": "0x10",
+ "filters": {
+ "product": ["123"],
+ "geo": [],
+ "source_type": ["navigation"],
+ "campaign": ["example"]
+ }
+ }
+ ],
+ "aggregatable_values": {
+ "a": 123,
+ "b": 456
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["a"],
+ "key_piece": "0x8",
+ "not_filters": {
+ "geo": [],
+ "source_type": ["event"]
+ }
+ },
+ {
+ "source_keys": ["a"],
+ "key_piece": "0x8",
+ "not_filters": {
+ "product": [],
+ "source_type": ["navigation"]
+ }
+ },
+ {
+ "source_keys": ["a"],
+ "key_piece": "0x10",
+ "not_filters": {
+ "product": [],
+ "geo": ["US"],
+ "source_type": ["event"],
+ "campaign": []
+ }
+ }
+ ],
+ "aggregatable_values": {
+ "a": 321,
+ "b": 654
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235576000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["b", "d"],
+ "key_piece": "0x8",
+ "filters": {
+ "source_type": ["navigation"]
+ },
+ "not_filters": {
+ "product": ["123"]
+ }
+ },
+ {
+ "source_keys": ["b", "d"],
+ "key_piece": "0x8",
+ "filters": {
+ "source_type": ["event"]
+ },
+ "not_filters": {
+ "product": ["789"]
+ }
+ },
+ {
+ "source_keys": ["b", "d"],
+ "key_piece": "0x20",
+ "filters": {
+ "source_type": ["navigation"]
+ },
+ "not_filters": {
+ "product": ["789"]
+ }
+ }
+ ],
+ "aggregatable_values": {
+ "a": 456,
+ "b": 789,
+ "d": 123
+ }
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "aggregatable_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "histograms": [
+ {
+ "key": "0x11",
+ "value": 123
+ },
+ {
+ "key": "0x12",
+ "value": 456
+ }
+ ]
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "report_time": "1643239174000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "histograms": [
+ {
+ "key": "0x11",
+ "value": 321
+ },
+ {
+ "key": "0x2",
+ "value": 654
+ }
+ ]
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "report_time": "1643239175000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "histograms": [
+ {
+ "key": "0x1",
+ "value": 456
+ },
+ {
+ "key": "0x22",
+ "value": 789
+ }
+ ]
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "report_time": "1643239176000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/basic.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/basic.json
new file mode 100644
index 000000000..9d2583750
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/basic.json
@@ -0,0 +1,60 @@
+{
+ "description": "1 navigation source and 1 trigger produce 1 report",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "7"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/basic_aggregatable.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/basic_aggregatable.json
new file mode 100644
index 000000000..e885c0db1
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/basic_aggregatable.json
@@ -0,0 +1,87 @@
+{
+ "description": "1 navigation source and 1 trigger produce 1 report",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123",
+ "aggregation_keys": {
+ "a": "0x159"
+ }
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ],
+ "aggregatable_trigger_data": [
+ {
+ "source_keys": ["a"],
+ "key_piece": "0x400"
+ }
+ ],
+ "aggregatable_values": {
+ "a": 123
+ }
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "7"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ }
+ ],
+ "aggregatable_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "histograms": [
+ {
+ "key": "0x559",
+ "value": 123
+ }
+ ]
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-aggregate-attribution",
+ "report_time": "1643239174000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/config_trigger_data_cardinality.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/config_trigger_data_cardinality.json
new file mode 100644
index 000000000..6050a74f9
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/config_trigger_data_cardinality.json
@@ -0,0 +1,63 @@
+{
+ "description": "Configure trigger data cardinality",
+ "api_config": {
+ "navigation_source_trigger_data_cardinality": "4"
+ },
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "3"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/dedup_key.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/dedup_key.json
new file mode 100644
index 000000000..9dca2d363
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/dedup_key.json
@@ -0,0 +1,112 @@
+{
+ "description": "2nd trigger with the same deduplication key is not attributed",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1",
+ "deduplication_key": "1"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2",
+ "deduplication_key": "1"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235576000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "3",
+ "deduplication_key": "2"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "1"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "3"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/event_level_trigger_filter_data.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/event_level_trigger_filter_data.json
new file mode 100644
index 000000000..516188142
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/event_level_trigger_filter_data.json
@@ -0,0 +1,271 @@
+{
+ "description": "First matched event trigger data is attributed",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123",
+ "filter_data": {
+ "product": ["123", "456"],
+ "geo": []
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "event"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://another-destination.test",
+ "source_event_id": "456"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1",
+ "filters": {
+ "product": [],
+ "source_type": ["navigation"]
+ }
+ },
+ {
+ "trigger_data": "2",
+ "filters": {
+ "geo": [],
+ "source_type": ["event"]
+ }
+ },
+ {
+ "trigger_data": "3",
+ "filters": {
+ "product": ["123"],
+ "geo": [],
+ "source_type": ["navigation"],
+ "campaign": ["example"]
+ }
+ },
+ {
+ "trigger_data": "4",
+ "filters": {
+ "product": ["123"],
+ "geo": [],
+ "source_type": ["navigation"],
+ "campaign": ["example"]
+ }
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1",
+ "not_filters": {
+ "geo": [],
+ "source_type": ["event"]
+ }
+ },
+ {
+ "trigger_data": "2",
+ "not_filters": {
+ "product": [],
+ "source_type": ["navigation"]
+ }
+ },
+ {
+ "trigger_data": "5",
+ "not_filters": {
+ "product": [],
+ "geo": ["US"],
+ "source_type": ["event"],
+ "campaign": []
+ }
+ },
+ {
+ "trigger_data": "4",
+ "not_filters": {
+ "product": [],
+ "geo": ["US"],
+ "source_type": ["event"],
+ "campaign": []
+ }
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235576000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1",
+ "filters": {
+ "source_type": ["navigation"]
+ },
+ "not_filters": {
+ "product": ["123"]
+ }
+ },
+ {
+ "trigger_data": "2",
+ "filters": {
+ "source_type": ["event"]
+ },
+ "not_filters": {
+ "product": ["789"]
+ }
+ },
+ {
+ "trigger_data": "6",
+ "filters": {
+ "source_type": ["navigation"]
+ },
+ "not_filters": {
+ "product": ["789"]
+ }
+ },
+ {
+ "trigger_data": "7",
+ "filters": {
+ "source_type": ["navigation"]
+ },
+ "not_filters": {
+ "product": ["789"]
+ }
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235577000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://another-destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "0",
+ "filters": {
+ "source_type": ["navigation"]
+ }
+ },
+ {
+ "trigger_data": "1",
+ "filters": {
+ "source_type": ["event"]
+ }
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "3"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "5"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "6"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://another-destination.test",
+ "randomized_trigger_rate": 0.0000025,
+ "source_event_id": "456",
+ "source_type": "event",
+ "trigger_data": "1"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1645831173000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/event_source.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/event_source.json
new file mode 100644
index 000000000..2abd77f24
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/event_source.json
@@ -0,0 +1,60 @@
+{
+ "description": "1 event source and 1 trigger produce 1 report",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "event"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0000025,
+ "source_event_id": "123",
+ "source_type": "event",
+ "trigger_data": "1"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1645831173000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/expired_source.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/expired_source.json
new file mode 100644
index 000000000..716b30e61
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/expired_source.json
@@ -0,0 +1,47 @@
+{
+ "description": "trigger not attributed to an expired source",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123",
+ "expiry": "86400"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643321973000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {}
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/most_recent_source.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/most_recent_source.json
new file mode 100644
index 000000000..37d3c001d
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/most_recent_source.json
@@ -0,0 +1,77 @@
+{
+ "description": "trigger attributed to most recent source for the same priority",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://example.destination.test",
+ "source_event_id": "456"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "456",
+ "source_type": "navigation",
+ "trigger_data": "7"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408374000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/rate_limit_max_attribution_reporting_endpoints.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/rate_limit_max_attribution_reporting_endpoints.json
new file mode 100644
index 000000000..d7030c904
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/rate_limit_max_attribution_reporting_endpoints.json
@@ -0,0 +1,223 @@
+{
+ "description": "Max attribution reporting endpoints per rate-limit window",
+ "api_config": {
+ "rate_limit_max_attribution_reporting_origins": "1"
+ },
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "111"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "source_origin": "https://another-source.test",
+ "attribution_src_url": "https://another-reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://another-reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "222"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235577000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://another-reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://another-reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://another-destination.test",
+ "source_event_id": "333"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235579000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://another-reporter.test/register-source",
+ "source_type": "event"
+ },
+ "responses": [{
+ "url": "https://another-reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "444"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235576000",
+ "registration_request": {
+ "attribution_src_url": "https://another-reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://another-reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "6"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235578000",
+ "registration_request": {
+ "attribution_src_url": "https://another-reporter.test/register-trigger",
+ "destination_origin": "https://another-destination.test"
+ },
+ "responses": [{
+ "url": "https://another-reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "5"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235580000",
+ "registration_request": {
+ "attribution_src_url": "https://another-reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://another-reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "4"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1645827574000",
+ "registration_request": {
+ "attribution_src_url": "https://another-reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://another-reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "3"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "111",
+ "source_type": "navigation",
+ "trigger_data": "7"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "222",
+ "source_type": "navigation",
+ "trigger_data": "6"
+ },
+ "report_url": "https://another-reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408375000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://another-destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "333",
+ "source_type": "navigation",
+ "trigger_data": "5"
+ },
+ "report_url": "https://another-reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408377000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0000025,
+ "source_event_id": "444",
+ "source_type": "event",
+ "trigger_data": "1"
+ },
+ "report_url": "https://another-reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1645831179000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/rate_limit_max_source_registration_reporting_origin_endpoints.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/rate_limit_max_source_registration_reporting_origin_endpoints.json
new file mode 100644
index 000000000..c301d1021
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/rate_limit_max_source_registration_reporting_origin_endpoints.json
@@ -0,0 +1,240 @@
+{
+ "description": "Max source registration reporting endpoints per rate limit window",
+ "api_config": {
+ "rate_limit_max_source_registration_reporting_origins": "1"
+ },
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source1.test",
+ "attribution_src_url": "https://reporter1.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter1.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination1.test",
+ "source_event_id": "111"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "source_origin": "https://source2.test",
+ "attribution_src_url": "https://reporter2.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter2.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination1.test",
+ "source_event_id": "222"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "source_origin": "https://source1.test",
+ "attribution_src_url": "https://reporter2.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter2.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination2.test",
+ "source_event_id": "333"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235576000",
+ "registration_request": {
+ "source_origin": "https://source1.test",
+ "attribution_src_url": "https://reporter3.test/register-source",
+ "source_type": "event"
+ },
+ "responses": [{
+ "url": "https://reporter3.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination1.test",
+ "source_event_id": "444"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1645827573000",
+ "registration_request": {
+ "source_origin": "https://source1.test",
+ "attribution_src_url": "https://reporter3.test/register-source",
+ "source_type": "event"
+ },
+ "responses": [{
+ "url": "https://reporter3.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination1.test",
+ "source_event_id": "555"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235583000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter1.test/register-trigger",
+ "destination_origin": "https://destination1.test"
+ },
+ "responses": [{
+ "url": "https://reporter1.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235584000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter2.test/register-trigger",
+ "destination_origin": "https://destination1.test"
+ },
+ "responses": [{
+ "url": "https://reporter2.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "6"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235585000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter2.test/register-trigger",
+ "destination_origin": "https://destination2.test"
+ },
+ "responses": [{
+ "url": "https://reporter2.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "5"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235585000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter3.test/register-trigger",
+ "destination_origin": "https://destination1.test"
+ },
+ "responses": [{
+ "url": "https://reporter3.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "4"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1645827574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter3.test/register-trigger",
+ "destination_origin": "https://destination1.test"
+ },
+ "responses": [{
+ "url": "https://reporter3.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "3"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination1.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "111",
+ "source_type": "navigation",
+ "trigger_data": "7"
+ },
+ "report_url": "https://reporter1.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination1.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "222",
+ "source_type": "navigation",
+ "trigger_data": "6"
+ },
+ "report_url": "https://reporter2.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408374000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination2.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "333",
+ "source_type": "navigation",
+ "trigger_data": "5"
+ },
+ "report_url": "https://reporter2.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408375000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination1.test",
+ "randomized_trigger_rate": 0.0000025,
+ "source_event_id": "555",
+ "source_type": "event",
+ "trigger_data": "1"
+ },
+ "report_url": "https://reporter3.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1648423173000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/same_destination_site.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/same_destination_site.json
new file mode 100644
index 000000000..48ce0e510
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/same_destination_site.json
@@ -0,0 +1,79 @@
+{
+ "description": "source matching is based on the same destination site",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://example.destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://another-destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "6"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "7"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/same_reporting_origin.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/same_reporting_origin.json
new file mode 100644
index 000000000..14331de77
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/same_reporting_origin.json
@@ -0,0 +1,79 @@
+{
+ "description": "source matching is based on the same reporting origin",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "attribution_src_url": "https://another-reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://another-reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "6"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "7"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/source_priority.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/source_priority.json
new file mode 100644
index 000000000..514ebfad7
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/source_priority.json
@@ -0,0 +1,97 @@
+{
+ "description": "trigger attributed to source with highest priority",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123",
+ "priority": "1"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "456",
+ "priority": "3"
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "789",
+ "priority": "2"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235576000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "7"
+ }
+ ]
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "456",
+ "source_type": "navigation",
+ "trigger_data": "7"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408374000"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_interop_tests/top_level_filter_data.json b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/top_level_filter_data.json
new file mode 100644
index 000000000..84a4fda87
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_interop_tests/top_level_filter_data.json
@@ -0,0 +1,356 @@
+{
+ "description": "Top-level filter data",
+ "input": {
+ "sources": [
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "navigation"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://destination.test",
+ "source_event_id": "123",
+ "filter_data": {
+ "product": ["123", "456"],
+ "geo": []
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235573000",
+ "registration_request": {
+ "source_origin": "https://source.test",
+ "attribution_src_url": "https://reporter.test/register-source",
+ "source_type": "event"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-source",
+ "response": {
+ "Attribution-Reporting-Register-Source": {
+ "destination": "https://another-destination.test",
+ "source_event_id": "456"
+ }
+ }
+ }]
+ }
+ ],
+ "triggers": [
+ {
+ "timestamp": "1643235574000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1"
+ }
+ ],
+ "filters": {
+ "product": [],
+ "source_type": ["navigation"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235575000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2"
+ }
+ ],
+ "filters": {
+ "geo": [],
+ "source_type": ["event"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235576000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "3"
+ }
+ ],
+ "filters": {
+ "product": ["123"],
+ "geo": [],
+ "source_type": ["navigation"],
+ "campaign": ["example"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235577000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1"
+ }
+ ],
+ "not_filters": {
+ "geo": [],
+ "source_type": ["event"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235578000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2"
+ }
+ ],
+ "not_filters": {
+ "product": [],
+ "source_type": ["navigation"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235578000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "4"
+ }
+ ],
+ "not_filters": {
+ "product": [],
+ "geo": ["US"],
+ "source_type": ["event"],
+ "campaign": []
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235579000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1"
+ }
+ ],
+ "filters": {
+ "source_type": ["navigation"]
+ },
+ "not_filters": {
+ "product": ["123"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235580000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "2"
+ }
+ ],
+ "filters": {
+ "source_type": ["event"]
+ },
+ "not_filters": {
+ "product": ["789"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235580000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "5"
+ }
+ ],
+ "filters": {
+ "source_type": ["navigation"]
+ },
+ "not_filters": {
+ "product": ["789"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235581000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://another-destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "0"
+ }
+ ],
+ "filters": {
+ "source_type": ["navigation"]
+ }
+ }
+ }
+ }]
+ },
+ {
+ "timestamp": "1643235582000",
+ "registration_request": {
+ "attribution_src_url": "https://reporter.test/register-trigger",
+ "destination_origin": "https://another-destination.test"
+ },
+ "responses": [{
+ "url": "https://reporter.test/register-trigger",
+ "response": {
+ "Attribution-Reporting-Register-Trigger": {
+ "event_trigger_data": [
+ {
+ "trigger_data": "1"
+ }
+ ],
+ "filters": {
+ "source_type": ["event"]
+ }
+ }
+ }
+ }]
+ }
+ ]
+ },
+ "output": {
+ "event_level_results": [
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "3"
+ },
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
+ "report_time": "1643408373000"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "4"
+ },
+ "report_time": "1643408373000",
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://destination.test",
+ "randomized_trigger_rate": 0.0024,
+ "source_event_id": "123",
+ "source_type": "navigation",
+ "trigger_data": "5"
+ },
+ "report_time": "1643408373000",
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution"
+ },
+ {
+ "payload": {
+ "attribution_destination": "https://another-destination.test",
+ "randomized_trigger_rate": 0.0000025,
+ "source_event_id": "456",
+ "source_type": "event",
+ "trigger_data": "1"
+ },
+ "report_time": "1645831173000",
+ "report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution"
+ }
+ ]
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/concurrency/AdServicesExecutorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/concurrency/AdServicesExecutorTest.java
new file mode 100644
index 000000000..e5d62771e
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/concurrency/AdServicesExecutorTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.concurrency;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class AdServicesExecutorTest {
+
+ // Command to kill the adservices process
+ public static final String KILL_ADSERVICES_CMD =
+ "su 0 killall -9 com.google.android.adservices.api";
+
+ @Before
+ public void setup() {
+ ShellUtils.runShellCommand(KILL_ADSERVICES_CMD);
+ }
+
+ @Test
+ public void testCreateLightWeightThreadSuccess() throws Exception {
+ String threadName =
+ AdServicesExecutors.getLightWeightExecutor()
+ .submit(() -> Thread.currentThread().getName())
+ .get();
+ // Expecting 1-19 digits since the thread number is a positive long
+ assertTrue(threadName.matches("lightweight-\\d{1,19}$"));
+ }
+
+ @Test
+ public void testCreateBackgroundThreadSuccess() throws Exception {
+ String threadName =
+ AdServicesExecutors.getBackgroundExecutor()
+ .submit(() -> Thread.currentThread().getName())
+ .get();
+ assertTrue(threadName.matches("background-\\d{1,19}$"));
+ }
+
+ @Test
+ public void testCreateScheduledThreadSuccess() throws Exception {
+ String threadName =
+ AdServicesExecutors.getScheduler()
+ .submit(() -> Thread.currentThread().getName())
+ .get();
+ assertTrue(threadName.matches("scheduled-\\d{1,19}$"));
+ }
+
+ @Test
+ public void testCreateBlockingThreadSuccess() throws Exception {
+ String threadName =
+ AdServicesExecutors.getBlockingExecutor()
+ .submit(() -> Thread.currentThread().getName())
+ .get();
+ assertTrue(threadName.matches("blocking-\\d{1,19}$"));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/DbHelperTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/DbHelperTest.java
index 671560297..8c9d27897 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/DbHelperTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/DbHelperTest.java
@@ -16,23 +16,72 @@
package com.android.adservices.data;
+import static com.android.adservices.data.DbHelper.CURRENT_DATABASE_VERSION;
+import static com.android.adservices.data.DbHelper.DATABASE_VERSION_V3;
import static com.android.adservices.data.DbTestUtil.doesIndexExist;
import static com.android.adservices.data.DbTestUtil.doesTableExistAndColumnCountMatch;
+import static com.android.adservices.data.DbTestUtil.getDatabaseNameForTest;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import androidx.test.core.app.ApplicationProvider;
+import com.android.adservices.data.measurement.DbHelperV1;
+import com.android.adservices.data.topics.migration.TopicDbMigratorV3;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
public class DbHelperTest {
protected static final Context sContext = ApplicationProvider.getApplicationContext();
+ private MockitoSession mStaticMockSession;
+
+ @Mock private Flags mMockFlags;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+ }
+
+ @After
+ public void teardown() {
+ mStaticMockSession.finishMocking();
+ }
+
@Test
public void testOnCreate() {
SQLiteDatabase db = DbTestUtil.getDbHelperForTest().safeGetReadableDatabase();
@@ -44,20 +93,7 @@ public class DbHelperTest {
assertTrue(doesTableExistAndColumnCountMatch(db, "topics_returned_topics", 7));
assertTrue(doesTableExistAndColumnCountMatch(db, "topics_usage_history", 3));
assertTrue(doesTableExistAndColumnCountMatch(db, "topics_app_usage_history", 3));
- assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_source", 22));
- assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_trigger", 12));
- assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_async_registration_contract", 13));
- assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_event_report", 14));
- assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_attribution", 8));
- assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_aggregate_report", 11));
- assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_aggregate_encryption_key", 4));
- assertTrue(doesTableExistAndColumnCountMatch(db, "enrollment_data", 8));
- assertTrue(doesIndexExist(db, "idx_msmt_source_ad_ei_et"));
- assertTrue(doesIndexExist(db, "idx_msmt_source_p_ad_wd_s_et"));
- assertTrue(doesIndexExist(db, "idx_msmt_trigger_ad_ei_tt"));
- assertTrue(doesIndexExist(db, "idx_msmt_source_et"));
- assertTrue(doesIndexExist(db, "idx_msmt_trigger_tt"));
- assertTrue(doesIndexExist(db, "idx_msmt_attribution_ss_so_ds_do_ei_tt"));
+ assertMeasurementSchema(db);
}
@Test
@@ -77,4 +113,86 @@ public class DbHelperTest {
// Verify database does not exist anymore
Assert.assertEquals(-1, dbHelper.getDbFileSize());
}
+
+ @Test
+ public void onOpen_appliesForeignKeyConstraint() {
+ // dbHelper.onOpen gets called implicitly
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().safeGetReadableDatabase();
+ try (Cursor cursor = db.rawQuery("PRAGMA foreign_keys", null)) {
+ cursor.moveToNext();
+ assertEquals(1, cursor.getLong(0));
+ }
+ }
+
+ @Test
+ public void testOnUpgrade_topicsV3Migration() {
+ DbHelper dbHelper = spy(DbTestUtil.getDbHelperForTest());
+ SQLiteDatabase db = mock(SQLiteDatabase.class);
+
+ // Do not actually perform queries but verify the invocation.
+ TopicDbMigratorV3 topicDbMigratorV3 = Mockito.spy(new TopicDbMigratorV3());
+ Mockito.doNothing().when(topicDbMigratorV3).performMigration(db);
+
+ // Ignore Measurement Migrators
+ doReturn(List.of()).when(dbHelper).getOrderedDbMigrators();
+ doReturn(List.of(topicDbMigratorV3)).when(dbHelper).topicsGetOrderedDbMigrators();
+
+ // Negative case - target version 3 is not in (oldVersion, newVersion]
+ dbHelper.onUpgrade(db, /* oldVersion */ 1, /* new Version */ 2);
+ Mockito.verify(topicDbMigratorV3, Mockito.never()).performMigration(db);
+
+ // Positive case - target version 3 is in (oldVersion, newVersion]
+ dbHelper.onUpgrade(db, /* oldVersion */ 1, /* new Version */ 3);
+ Mockito.verify(topicDbMigratorV3).performMigration(db);
+ }
+
+ @Test
+ public void testSupportsTopicContributorsTable() {
+ DbHelper dbHelperV2 = new DbHelper(sContext, getDatabaseNameForTest(), /* dbVersion*/ 2);
+ assertThat(dbHelperV2.supportsTopicContributorsTable()).isFalse();
+
+ DbHelper dbHelperV3 = new DbHelper(sContext, getDatabaseNameForTest(), /* dbVersion*/ 3);
+ assertThat(dbHelperV3.supportsTopicContributorsTable()).isTrue();
+ }
+
+ @Test
+ public void testGetDatabaseVersionToCreate() {
+ // Test feature flag is off
+ when(mMockFlags.getEnableDatabaseSchemaVersion3()).thenReturn(false);
+ assertThat(DbHelper.getDatabaseVersionToCreate()).isEqualTo(CURRENT_DATABASE_VERSION);
+
+ // Test feature flag is on
+ when(mMockFlags.getEnableDatabaseSchemaVersion3()).thenReturn(true);
+ assertThat(DbHelper.getDatabaseVersionToCreate()).isEqualTo(DATABASE_VERSION_V3);
+ }
+
+ @Test
+ public void testOnUpgrade_measurementMigration() {
+ String dbName = "test_db";
+ DbHelperV1 dbHelperV1 = new DbHelperV1(sContext, dbName, 1);
+ SQLiteDatabase db = dbHelperV1.safeGetWritableDatabase();
+
+ assertEquals(1, db.getVersion());
+
+ DbHelper dbHelperForV3 = new DbHelper(sContext, dbName, 3);
+ dbHelperForV3.onUpgrade(db, 1, DATABASE_VERSION_V3);
+ assertMeasurementSchema(db);
+ }
+
+ private void assertMeasurementSchema(SQLiteDatabase db) {
+ assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_source", 22));
+ assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_trigger", 13));
+ assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_async_registration_contract", 16));
+ assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_event_report", 17));
+ assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_attribution", 10));
+ assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_aggregate_report", 14));
+ assertTrue(doesTableExistAndColumnCountMatch(db, "msmt_aggregate_encryption_key", 4));
+ assertTrue(doesTableExistAndColumnCountMatch(db, "enrollment_data", 8));
+ assertTrue(doesIndexExist(db, "idx_msmt_source_ad_ei_et"));
+ assertTrue(doesIndexExist(db, "idx_msmt_source_p_ad_wd_s_et"));
+ assertTrue(doesIndexExist(db, "idx_msmt_trigger_ad_ei_tt"));
+ assertTrue(doesIndexExist(db, "idx_msmt_source_et"));
+ assertTrue(doesIndexExist(db, "idx_msmt_trigger_tt"));
+ assertTrue(doesIndexExist(db, "idx_msmt_attribution_ss_so_ds_do_ei_tt"));
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/DbTestUtil.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/DbTestUtil.java
index e560e9981..0c3f06b86 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/DbTestUtil.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/DbTestUtil.java
@@ -52,7 +52,9 @@ public final class DbTestUtil {
if (sSingleton == null) {
sSingleton =
new DbHelper(
- sContext, DATABASE_NAME_FOR_TEST, DbHelper.LATEST_DATABASE_VERSION);
+ sContext,
+ DATABASE_NAME_FOR_TEST,
+ DbHelper.CURRENT_DATABASE_VERSION);
}
return sSingleton;
}
@@ -92,4 +94,9 @@ public final class DbTestUtil {
Cursor cursor = db.rawQuery(query, null);
return cursor != null && cursor.getCount() > 0;
}
+
+ /** Return test database name */
+ public static String getDatabaseNameForTest() {
+ return DATABASE_NAME_FOR_TEST;
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/RoomSchemaMigrationGuardrailTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/RoomSchemaMigrationGuardrailTest.java
new file mode 100644
index 000000000..9e6c5724e
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/RoomSchemaMigrationGuardrailTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.data;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+import androidx.room.RoomDatabase;
+import androidx.room.migration.bundle.EntityBundle;
+import androidx.room.migration.bundle.FieldBundle;
+import androidx.room.migration.bundle.SchemaBundle;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.adservices.data.adselection.AdSelectionDatabase;
+import com.android.adservices.data.customaudience.CustomAudienceDatabase;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** This UT is a guardrail to schema migration managed by Room. */
+public class RoomSchemaMigrationGuardrailTest {
+ private static final Context CONTEXT =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private static final List<Class<? extends RoomDatabase>> DATABASE_CLASSES =
+ ImmutableList.of(CustomAudienceDatabase.class, AdSelectionDatabase.class);
+ private static final List<DatabaseWithVersion> BYPASS_DATABASE_VERSIONS_NEW_FIELD_ONLY =
+ ImmutableList.of(new DatabaseWithVersion(CustomAudienceDatabase.class, 2));
+
+ @Test
+ public void validateDatabaseMigration() throws IOException {
+ List<String> errors = new ArrayList<>();
+ List<DatabaseWithVersion> databaseClassesWithNewestVersion =
+ validateAndGetDatabaseClassesWithNewestVersionNumber(errors);
+ for (DatabaseWithVersion databaseWithVersion : databaseClassesWithNewestVersion) {
+ validateNewFieldOnly(databaseWithVersion, errors);
+ }
+ if (!errors.isEmpty()) {
+ throw new RuntimeException(
+ String.format(
+ "Finish validating room databases with error \n %s",
+ String.join("\n", errors)));
+ }
+ }
+
+ private List<DatabaseWithVersion> validateAndGetDatabaseClassesWithNewestVersionNumber(
+ List<String> errors) throws IOException {
+ ImmutableList.Builder<DatabaseWithVersion> result = new ImmutableList.Builder<>();
+ for (Class<? extends RoomDatabase> clazz : DATABASE_CLASSES) {
+ try {
+ final int newestDatabaseVersion = getNewestDatabaseVersion(clazz);
+ result.add(new DatabaseWithVersion(clazz, newestDatabaseVersion));
+ } catch (Exception e) {
+ errors.add(
+ String.format(
+ "Fail to get database schema for %s, with error %s.",
+ clazz.getCanonicalName(), e.getMessage()));
+ }
+ }
+ return result.build();
+ }
+
+ private int getNewestDatabaseVersion(Class<? extends RoomDatabase> database)
+ throws IOException {
+ return Arrays.stream(CONTEXT.getAssets().list(database.getCanonicalName()))
+ .map(p -> p.split("\\.")[0])
+ .mapToInt(Integer::parseInt)
+ .max()
+ .orElseThrow();
+ }
+
+ private void validateNewFieldOnly(DatabaseWithVersion databaseWithVersion, List<String> errors)
+ throws IOException {
+ // Custom audience table v1 to v2 is violating the policy. Skip it.
+ if (BYPASS_DATABASE_VERSIONS_NEW_FIELD_ONLY.contains(databaseWithVersion)) {
+ return;
+ }
+ int newestDatabaseVersion = databaseWithVersion.mVersion;
+ Class<? extends RoomDatabase> roomDatabaseClass = databaseWithVersion.mRoomDatabaseClass;
+ if (databaseWithVersion.mVersion == 1) {
+ return;
+ }
+
+ SchemaBundle oldSchemaBundle = loadSchema(roomDatabaseClass, newestDatabaseVersion - 1);
+ SchemaBundle newSchemaBundle = loadSchema(roomDatabaseClass, newestDatabaseVersion);
+
+ Map<String, EntityBundle> oldTables =
+ oldSchemaBundle.getDatabase().getEntitiesByTableName();
+ Map<String, EntityBundle> newTables =
+ newSchemaBundle.getDatabase().getEntitiesByTableName();
+
+ // We don't care new table in a new DB version. So iterate through the old version.
+ for (Map.Entry<String, EntityBundle> e : oldTables.entrySet()) {
+ String tableName = e.getKey();
+
+ // table in old version must show in new.
+ if (!newTables.containsKey(tableName)) {
+ errors.add(
+ String.format(
+ "New version DB is missing table %s present in old version",
+ tableName));
+ continue;
+ }
+
+ EntityBundle oldEntityBundle = e.getValue();
+ EntityBundle newEntityBundle = newTables.get(tableName);
+
+ for (FieldBundle oldFieldBundle : oldEntityBundle.getFields()) {
+ if (!newEntityBundle.getFields().contains(oldFieldBundle)) {
+ errors.add(
+ String.format(
+ "Table %s and field %s: Missing field in new version or"
+ + " mismatch field in new and old version.",
+ tableName, oldEntityBundle));
+ }
+ }
+ }
+ }
+
+ private SchemaBundle loadSchema(Class<? extends RoomDatabase> database, int version)
+ throws IOException {
+ InputStream input =
+ CONTEXT.getAssets().open(database.getCanonicalName() + "/" + version + ".json");
+ return SchemaBundle.deserialize(input);
+ }
+
+ private static class DatabaseWithVersion {
+ @NonNull private final Class<? extends RoomDatabase> mRoomDatabaseClass;
+ private final int mVersion;
+
+ DatabaseWithVersion(@NonNull Class<? extends RoomDatabase> roomDatabaseClass, int version) {
+ mRoomDatabaseClass = roomDatabaseClass;
+ mVersion = version;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DatabaseWithVersion)) return false;
+ DatabaseWithVersion that = (DatabaseWithVersion) o;
+ return mVersion == that.mVersion && mRoomDatabaseClass.equals(that.mRoomDatabaseClass);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRoomDatabaseClass, mVersion);
+ }
+
+ @Override
+ public String toString() {
+ return "DatabaseWithVersion{"
+ + "mRoomDatabaseClass="
+ + mRoomDatabaseClass
+ + ", mVersion="
+ + mVersion
+ + '}';
+ }
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/AdSelectionEntryDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/AdSelectionEntryDaoTest.java
index 7359d0f6d..2ce00fcc9 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/AdSelectionEntryDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/AdSelectionEntryDaoTest.java
@@ -56,6 +56,7 @@ public class AdSelectionEntryDaoTest {
private static final long AD_SELECTION_ID_1 = 1;
private static final long AD_SELECTION_ID_2 = 2;
private static final long AD_SELECTION_ID_3 = 3;
+ private static final long AD_SELECTION_ID_4 = 4;
private static final String CONTEXTUAL_SIGNALS = "contextual_signals";
private static final double BID = 5;
@@ -554,6 +555,88 @@ public class AdSelectionEntryDaoTest {
assertEquals(adSelectionEntry, expected);
}
+ @Test
+ public void testRemoveExpiredAdSelection() {
+ DBAdSelection expiredDBAdSelection =
+ new DBAdSelection.Builder()
+ .setAdSelectionId(AD_SELECTION_ID_4)
+ .setCustomAudienceSignals(CUSTOM_AUDIENCE_SIGNALS)
+ .setContextualSignals(CONTEXTUAL_SIGNALS)
+ .setBiddingLogicUri(BIDDING_LOGIC_URI_1)
+ .setWinningAdRenderUri(RENDER_URI)
+ .setWinningAdBid(BID)
+ .setCreationTimestamp(ACTIVATION_TIME.minusSeconds(10))
+ .setCallerPackageName(CALLER_PACKAGE_NAME_1)
+ .build();
+
+ mAdSelectionEntryDao.persistAdSelection(DB_AD_SELECTION_1);
+ mAdSelectionEntryDao.persistAdSelection(expiredDBAdSelection);
+
+ assertTrue(
+ mAdSelectionEntryDao.doesAdSelectionIdExist(DB_AD_SELECTION_1.getAdSelectionId()));
+ assertTrue(
+ mAdSelectionEntryDao.doesAdSelectionIdExist(
+ expiredDBAdSelection.getAdSelectionId()));
+
+ mAdSelectionEntryDao.removeExpiredAdSelection(ACTIVATION_TIME.minusSeconds(5));
+
+ assertTrue(
+ mAdSelectionEntryDao.doesAdSelectionIdExist(DB_AD_SELECTION_1.getAdSelectionId()));
+ assertFalse(
+ mAdSelectionEntryDao.doesAdSelectionIdExist(
+ expiredDBAdSelection.getAdSelectionId()));
+ }
+
+ @Test
+ public void testDoesBuyerDecisionLogicExist() {
+ assertFalse(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC_1.getBiddingLogicUri()));
+
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(DB_BUYER_DECISION_LOGIC_1);
+
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC_1.getBiddingLogicUri()));
+ }
+
+ @Test
+ public void testRemoveExpiredBuyerDecisionLogic() {
+ assertFalse(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC_1.getBiddingLogicUri()));
+ assertFalse(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC_2.getBiddingLogicUri()));
+
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(DB_BUYER_DECISION_LOGIC_1);
+ mAdSelectionEntryDao.persistAdSelection(DB_AD_SELECTION_1);
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(DB_BUYER_DECISION_LOGIC_2);
+
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC_1.getBiddingLogicUri()));
+ assertTrue(
+ mAdSelectionEntryDao.doesAdSelectionIdExist(DB_AD_SELECTION_1.getAdSelectionId()));
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC_2.getBiddingLogicUri()));
+
+ mAdSelectionEntryDao.removeExpiredBuyerDecisionLogic();
+
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC_1.getBiddingLogicUri()));
+ assertTrue(
+ mAdSelectionEntryDao.doesAdSelectionIdExist(DB_AD_SELECTION_1.getAdSelectionId()));
+
+ // DB_BUYER_DECISION_LOGIC_2 will be removed because there is no instance of
+ // DB_BUYER_DECISION_LOGIC_2.getBiddingLogicUri() in the DBAdSelection table
+ assertFalse(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC_2.getBiddingLogicUri()));
+ }
+
/**
* Creates expected DBAdSelectionEntry to be used for testing from DBAdSelection and
* DBBuyerDecisionLogic. Remarketing Case
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/DBAdSelectionTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/DBAdSelectionTest.java
index f561ab61b..807be001c 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/DBAdSelectionTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/adselection/DBAdSelectionTest.java
@@ -35,7 +35,7 @@ public class DBAdSelectionTest {
private static final Uri BIDDING_LOGIC_URI = Uri.parse("http://www.domain.com/logic");
private static final Uri RENDER_URI = Uri.parse("http://www.domain.com/advert");
private static final Instant ACTIVATION_TIME = CLOCK.instant().truncatedTo(ChronoUnit.MILLIS);
- ;
+
private static final long AD_SELECTION_ID = 1;
private static final String CONTEXTUAL_SIGNALS = "contextual_signals";
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/consent/AppConsentDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/consent/AppConsentDaoTest.java
index 74630e6cb..3eb8bd627 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/consent/AppConsentDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/consent/AppConsentDaoTest.java
@@ -30,9 +30,11 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import androidx.test.core.app.ApplicationProvider;
+import androidx.test.core.content.pm.ApplicationInfoBuilder;
import com.android.adservices.data.common.BooleanFileDatastore;
@@ -46,16 +48,16 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Set;
public class AppConsentDaoTest {
+ @Rule public final MockitoRule rule = MockitoJUnit.rule();
private final Context mContext = ApplicationProvider.getApplicationContext();
-
- private AppConsentDao mAppConsentDao;
private final PackageManager mPackageManager = mContext.getPackageManager();
-
- @Rule public final MockitoRule rule = MockitoJUnit.rule();
+ private AppConsentDao mAppConsentDao;
@Spy
private BooleanFileDatastore mDatastoreSpy =
@@ -370,6 +372,20 @@ public class AppConsentDaoTest {
mDatastoreSpy.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, false);
mDatastoreSpy.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, false);
mDatastoreSpy.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, true);
+ List<ApplicationInfo> applicationsInstalled =
+ Arrays.asList(
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP10_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP20_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP30_PACKAGE_NAME)
+ .build());
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
final Set<String> knownAppsWithConsent = mAppConsentDao.getKnownAppsWithConsent();
@@ -385,10 +401,39 @@ public class AppConsentDaoTest {
}
@Test
+ public void testGetKnownAppsWithConsentNotExistentApp() throws IOException {
+ mDatastoreSpy.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, true);
+ List<ApplicationInfo> applicationsInstalled = new ArrayList<>();
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
+
+ final Set<String> knownAppsWithConsent = mAppConsentDao.getKnownAppsWithConsent();
+
+ assertEquals(0, knownAppsWithConsent.size());
+
+ verify(mDatastoreSpy).initialize();
+ }
+
+ @Test
public void testGetAppsWithRevokedConsent() throws IOException {
mDatastoreSpy.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, true);
mDatastoreSpy.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, true);
mDatastoreSpy.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, false);
+ List<ApplicationInfo> applicationsInstalled =
+ Arrays.asList(
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP10_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP20_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP30_PACKAGE_NAME)
+ .build());
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
final Set<String> appsWithRevokedConsent = mAppConsentDao.getAppsWithRevokedConsent();
@@ -404,6 +449,21 @@ public class AppConsentDaoTest {
}
@Test
+ public void testGetAppsWithRevokedConsentNonExistentApp() throws IOException {
+ mDatastoreSpy.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, true);
+ List<ApplicationInfo> applicationsInstalled = new ArrayList<>();
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
+
+ final Set<String> appsWithRevokedConsent = mAppConsentDao.getAppsWithRevokedConsent();
+
+ assertEquals(0, appsWithRevokedConsent.size());
+
+ verify(mDatastoreSpy).initialize();
+ }
+
+ @Test
public void testClearAllConsentData() throws IOException {
mDatastoreSpy.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, true);
mDatastoreSpy.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, true);
@@ -423,6 +483,20 @@ public class AppConsentDaoTest {
mDatastoreSpy.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, true);
mDatastoreSpy.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, true);
mDatastoreSpy.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, false);
+ List<ApplicationInfo> applicationsInstalled =
+ Arrays.asList(
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP10_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP20_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP30_PACKAGE_NAME)
+ .build());
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
mAppConsentDao.clearKnownAppsWithConsent();
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceDaoTest.java
index 9b366a84f..f7df421cb 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceDaoTest.java
@@ -16,7 +16,10 @@
package com.android.adservices.data.customaudience;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
@@ -29,6 +32,8 @@ import static org.junit.Assert.assertTrue;
import android.adservices.common.AdSelectionSignals;
import android.adservices.common.AdTechIdentifier;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.net.Uri;
import androidx.room.Room;
@@ -36,8 +41,10 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.customaudience.DBTrustedBiddingDataFixture;
import com.android.adservices.data.common.DBAdData;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.AllowLists;
import com.android.adservices.service.customaudience.BackgroundFetchRunner;
import com.android.adservices.service.customaudience.CustomAudienceUpdatableData;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -45,6 +52,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
import org.mockito.MockitoSession;
import java.time.Clock;
@@ -55,10 +63,15 @@ import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class CustomAudienceDaoTest {
private static final Flags TEST_FLAGS = FlagsFactory.getFlagsForTest();
+ @Mock private PackageManager mPackageManagerMock;
+ @Mock private EnrollmentDao mEnrollmentDaoMock;
+
private static final Uri DAILY_UPDATE_URI_1 = Uri.parse("https://www.example.com/d1");
private static final AdSelectionSignals USER_BIDDING_SIGNALS_1 =
AdSelectionSignals.fromString("{\"ExampleBiddingSignal1\":1}");
@@ -491,7 +504,10 @@ public class CustomAudienceDaoTest {
// Test applications don't have the required permissions to read config P/H flags, and
// injecting mocked flags everywhere is annoying and non-trivial for static methods
mStaticMockSession =
- ExtendedMockito.mockitoSession().spyStatic(FlagsFactory.class).startMocking();
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .initMocks(this)
+ .startMocking();
mCustomAudienceDao =
Room.inMemoryDatabaseBuilder(CONTEXT, CustomAudienceDatabase.class)
@@ -1068,6 +1084,58 @@ public class CustomAudienceDaoTest {
}
@Test
+ public void testGetAllCustomAudienceOwners() {
+ doReturn(TEST_FLAGS).when(FlagsFactory::getFlags);
+
+ // Prepopulate with three CAs belonging to two owners
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_1, DAILY_UPDATE_URI_1);
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_2, DAILY_UPDATE_URI_2);
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(
+ CUSTOM_AUDIENCE_EXPIRED, DAILY_UPDATE_URI_2);
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_2,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_2,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_EXPIRED,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_EXPIRED.getOwner(),
+ CUSTOM_AUDIENCE_EXPIRED.getBuyer(),
+ CUSTOM_AUDIENCE_EXPIRED.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_EXPIRED,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_EXPIRED.getOwner(),
+ CUSTOM_AUDIENCE_EXPIRED.getBuyer(),
+ CUSTOM_AUDIENCE_EXPIRED.getName()));
+
+ List<String> owners = mCustomAudienceDao.getAllCustomAudienceOwners();
+ assertThat(owners).hasSize(2);
+ assertThat(owners)
+ .containsExactly(CUSTOM_AUDIENCE_1.getOwner(), CUSTOM_AUDIENCE_2.getOwner());
+ }
+
+ @Test
public void testDeleteAllExpiredCustomAudienceData() {
doReturn(TEST_FLAGS).when(FlagsFactory::getFlags);
@@ -1152,6 +1220,420 @@ public class CustomAudienceDaoTest {
}
@Test
+ public void testDeleteUninstalledOwnerCustomAudienceData() {
+ // All owners are allowed
+ class FlagsThatAllowAllApps implements Flags {
+ @Override
+ public String getPpapiAppAllowList() {
+ return AllowLists.ALLOW_ALL;
+ }
+ }
+ Flags flagsThatAllowAllApps = new FlagsThatAllowAllApps();
+
+ doReturn(flagsThatAllowAllApps).when(FlagsFactory::getFlags);
+
+ // Only CA1's owner is installed
+ ApplicationInfo installed_owner_1 = new ApplicationInfo();
+ installed_owner_1.packageName = CUSTOM_AUDIENCE_1.getOwner();
+ doReturn(Arrays.asList(installed_owner_1))
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any());
+
+ // Prepopulate with data
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_1, DAILY_UPDATE_URI_1);
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_2, DAILY_UPDATE_URI_2);
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_2,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_2,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+
+ // Clear the uninstalled app data
+ CustomAudienceStats expectedDisallowedOwnerStats =
+ CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(1)
+ .setTotalOwnerCount(1)
+ .build();
+ assertEquals(
+ expectedDisallowedOwnerStats,
+ mCustomAudienceDao.deleteAllDisallowedOwnerCustomAudienceData(
+ mPackageManagerMock, flagsThatAllowAllApps));
+
+ // Verify only the uninstalled app data is deleted
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ }
+
+ @Test
+ public void testDeleteNotAllowedOwnerCustomAudienceData() {
+ // Only CA1's owner is allowed
+ class FlagsThatAllowOneApp implements Flags {
+ @Override
+ public String getPpapiAppAllowList() {
+ return CUSTOM_AUDIENCE_1.getOwner();
+ }
+ }
+ Flags flagsThatAllowOneApp = new FlagsThatAllowOneApp();
+
+ doReturn(flagsThatAllowOneApp).when(FlagsFactory::getFlags);
+
+ // Both owners are installed
+ ApplicationInfo installed_owner_1 = new ApplicationInfo();
+ installed_owner_1.packageName = CUSTOM_AUDIENCE_1.getOwner();
+ ApplicationInfo installed_owner_2 = new ApplicationInfo();
+ installed_owner_2.packageName = CUSTOM_AUDIENCE_2.getOwner();
+ doReturn(Arrays.asList(installed_owner_1, installed_owner_2))
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any());
+
+ // Prepopulate with data
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_1, DAILY_UPDATE_URI_1);
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_2, DAILY_UPDATE_URI_2);
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_2,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_2,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+
+ // Clear the data for the app not in the allowlist
+ CustomAudienceStats expectedDisallowedOwnerStats =
+ CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(1)
+ .setTotalOwnerCount(1)
+ .build();
+ assertEquals(
+ expectedDisallowedOwnerStats,
+ mCustomAudienceDao.deleteAllDisallowedOwnerCustomAudienceData(
+ mPackageManagerMock, flagsThatAllowOneApp));
+
+ // Verify only the uninstalled app data is deleted
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ }
+
+ @Test
+ public void testDeleteUninstalledAndNotAllowedOwnerCustomAudienceData() {
+ // Only CA1's owner is allowed
+ class FlagsThatAllowOneApp implements Flags {
+ @Override
+ public String getPpapiAppAllowList() {
+ return CUSTOM_AUDIENCE_1.getOwner();
+ }
+ }
+ Flags flagsThatAllowOneApp = new FlagsThatAllowOneApp();
+
+ doReturn(flagsThatAllowOneApp).when(FlagsFactory::getFlags);
+
+ // Only CA2's owner is installed
+ ApplicationInfo installed_owner_2 = new ApplicationInfo();
+ installed_owner_2.packageName = CUSTOM_AUDIENCE_2.getOwner();
+ doReturn(Arrays.asList(installed_owner_2))
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any());
+
+ // Prepopulate with data
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_1, DAILY_UPDATE_URI_1);
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_2, DAILY_UPDATE_URI_2);
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_2,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_2,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+
+ // All data should be cleared because neither owner is both allowlisted and installed
+ CustomAudienceStats expectedDisallowedOwnerStats =
+ CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(2)
+ .setTotalOwnerCount(2)
+ .build();
+ assertEquals(
+ expectedDisallowedOwnerStats,
+ mCustomAudienceDao.deleteAllDisallowedOwnerCustomAudienceData(
+ mPackageManagerMock, flagsThatAllowOneApp));
+
+ // Verify both owners' app data are deleted
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ }
+
+ @Test
+ public void testDeleteAllDisallowedBuyerCustomAudienceData_enrollmentEnabled() {
+ class FlagsThatEnforceEnrollment implements Flags {
+ @Override
+ public boolean getDisableFledgeEnrollmentCheck() {
+ return false;
+ }
+ }
+ Flags flagsThatEnforceEnrollment = new FlagsThatEnforceEnrollment();
+
+ doReturn(flagsThatEnforceEnrollment).when(FlagsFactory::getFlags);
+
+ // Only CA2's buyer is enrolled
+ doReturn(Stream.of(CUSTOM_AUDIENCE_2.getBuyer()).collect(Collectors.toSet()))
+ .when(mEnrollmentDaoMock)
+ .getAllFledgeEnrolledAdTechs();
+
+ // Prepopulate with data
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_1, DAILY_UPDATE_URI_1);
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_2, DAILY_UPDATE_URI_2);
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_2,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_2,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+
+ // CA1's data should be deleted because it is not enrolled
+ CustomAudienceStats expectedDisallowedBuyerStats =
+ CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(1)
+ .setTotalBuyerCount(1)
+ .build();
+ assertEquals(
+ expectedDisallowedBuyerStats,
+ mCustomAudienceDao.deleteAllDisallowedBuyerCustomAudienceData(
+ mEnrollmentDaoMock, flagsThatEnforceEnrollment));
+
+ // Verify only CA1's app data is deleted
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertNull(
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_2,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_2,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ }
+
+ @Test
+ public void testDeleteAllDisallowedBuyerCustomAudienceData_enrollmentDisabledNoDeletion() {
+ class FlagsThatDisableEnrollment implements Flags {
+ @Override
+ public boolean getDisableFledgeEnrollmentCheck() {
+ return true;
+ }
+ }
+ Flags flagsThatDisableEnrollment = new FlagsThatDisableEnrollment();
+
+ doReturn(flagsThatDisableEnrollment).when(FlagsFactory::getFlags);
+
+ // Prepopulate with data
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_1, DAILY_UPDATE_URI_1);
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(CUSTOM_AUDIENCE_2, DAILY_UPDATE_URI_2);
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_2,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_2,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+
+ // CA1's data should be deleted because it is not enrolled, but enrollment is disabled, so
+ // nothing is cleared
+ CustomAudienceStats expectedDisallowedBuyerStats =
+ CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(0)
+ .setTotalBuyerCount(0)
+ .build();
+ assertEquals(
+ expectedDisallowedBuyerStats,
+ mCustomAudienceDao.deleteAllDisallowedBuyerCustomAudienceData(
+ mEnrollmentDaoMock, flagsThatDisableEnrollment));
+
+ // Verify no data is deleted
+ assertEquals(
+ CUSTOM_AUDIENCE_1,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_1,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_1.getOwner(),
+ CUSTOM_AUDIENCE_1.getBuyer(),
+ CUSTOM_AUDIENCE_1.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_2,
+ mCustomAudienceDao.getCustomAudienceByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+ assertEquals(
+ CUSTOM_AUDIENCE_BGF_DATA_2,
+ mCustomAudienceDao.getCustomAudienceBackgroundFetchDataByPrimaryKey(
+ CUSTOM_AUDIENCE_2.getOwner(),
+ CUSTOM_AUDIENCE_2.getBuyer(),
+ CUSTOM_AUDIENCE_2.getName()));
+
+ verify(mEnrollmentDaoMock, never()).getAllFledgeEnrolledAdTechs();
+ }
+
+ @Test
public void testDeleteAllCustomAudienceData() {
doReturn(TEST_FLAGS).when(FlagsFactory::getFlags);
@@ -1341,14 +1823,14 @@ public class CustomAudienceDaoTest {
}
private void verifyCustomAudienceStats(
- CustomAudienceDao.CustomAudienceStats customAudienceStats,
+ CustomAudienceStats customAudienceStats,
String owner,
int totalCount,
int perOwnerCount,
int ownerCount) {
assertEquals(owner, customAudienceStats.getOwner());
- assertEquals(totalCount, customAudienceStats.getTotalCount());
- assertEquals(perOwnerCount, customAudienceStats.getPerOwnerCount());
- assertEquals(ownerCount, customAudienceStats.getOwnerCount());
+ assertEquals(totalCount, customAudienceStats.getTotalCustomAudienceCount());
+ assertEquals(perOwnerCount, customAudienceStats.getPerOwnerCustomAudienceCount());
+ assertEquals(ownerCount, customAudienceStats.getTotalOwnerCount());
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceStatsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceStatsTest.java
new file mode 100644
index 000000000..86c742db2
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/CustomAudienceStatsTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.data.customaudience;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+
+import org.junit.Test;
+
+public class CustomAudienceStatsTest {
+ private static final long TOTAL_CUSTOM_AUDIENCE_COUNT = 1L;
+ private static final long PER_OWNER_CUSTOM_AUDIENCE_COUNT = 2L;
+ private static final long TOTAL_OWNER_COUNT = 3L;
+ private static final long PER_BUYER_CUSTOM_AUDIENCE_COUNT = 4L;
+ private static final long TOTAL_BUYER_COUNT = 5L;
+
+ @Test
+ public void testBuildDefault() {
+ CustomAudienceStats stats = CustomAudienceStats.builder().build();
+
+ assertThat(stats).isNotNull();
+ assertThat(stats.getOwner()).isNull();
+ assertThat(stats.getBuyer()).isNull();
+ assertThat(stats.getTotalCustomAudienceCount()).isEqualTo(CustomAudienceStats.UNSET_COUNT);
+ assertThat(stats.getPerOwnerCustomAudienceCount())
+ .isEqualTo(CustomAudienceStats.UNSET_COUNT);
+ assertThat(stats.getTotalOwnerCount()).isEqualTo(CustomAudienceStats.UNSET_COUNT);
+ assertThat(stats.getPerBuyerCustomAudienceCount())
+ .isEqualTo(CustomAudienceStats.UNSET_COUNT);
+ assertThat(stats.getTotalBuyerCount()).isEqualTo(CustomAudienceStats.UNSET_COUNT);
+ }
+
+ @Test
+ public void testBuildWithValues() {
+ CustomAudienceStats stats =
+ CustomAudienceStats.builder()
+ .setOwner(CustomAudienceFixture.VALID_OWNER)
+ .setBuyer(CommonFixture.VALID_BUYER_1)
+ .setTotalCustomAudienceCount(TOTAL_CUSTOM_AUDIENCE_COUNT)
+ .setPerOwnerCustomAudienceCount(PER_OWNER_CUSTOM_AUDIENCE_COUNT)
+ .setTotalOwnerCount(TOTAL_OWNER_COUNT)
+ .setPerBuyerCustomAudienceCount(PER_BUYER_CUSTOM_AUDIENCE_COUNT)
+ .setTotalBuyerCount(TOTAL_BUYER_COUNT)
+ .build();
+
+ assertThat(stats).isNotNull();
+ assertThat(stats.getOwner()).isEqualTo(CustomAudienceFixture.VALID_OWNER);
+ assertThat(stats.getBuyer()).isEqualTo(CommonFixture.VALID_BUYER_1);
+ assertThat(stats.getTotalCustomAudienceCount()).isEqualTo(TOTAL_CUSTOM_AUDIENCE_COUNT);
+ assertThat(stats.getPerOwnerCustomAudienceCount())
+ .isEqualTo(PER_OWNER_CUSTOM_AUDIENCE_COUNT);
+ assertThat(stats.getTotalOwnerCount()).isEqualTo(TOTAL_OWNER_COUNT);
+ assertThat(stats.getPerBuyerCustomAudienceCount())
+ .isEqualTo(PER_BUYER_CUSTOM_AUDIENCE_COUNT);
+ assertThat(stats.getTotalBuyerCount()).isEqualTo(TOTAL_BUYER_COUNT);
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/enrollment/EnrollmentDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/enrollment/EnrollmentDaoTest.java
index 0b81c51a2..259405d82 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/enrollment/EnrollmentDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/enrollment/EnrollmentDaoTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.data.enrollment;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -26,10 +28,12 @@ import static org.junit.Assert.assertTrue;
import android.adservices.common.AdTechIdentifier;
import android.content.Context;
import android.database.DatabaseUtils;
+import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.data.DbHelper;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.service.enrollment.EnrollmentData;
import org.junit.After;
@@ -38,6 +42,7 @@ import org.junit.Test;
import org.mockito.Mockito;
import java.util.Arrays;
+import java.util.Set;
public class EnrollmentDaoTest {
@@ -66,7 +71,9 @@ public class EnrollmentDaoTest {
.setAttributionSourceRegistrationUrl(
Arrays.asList("https://2test.com/source", "https://2test2.com/source"))
.setAttributionTriggerRegistrationUrl(
- Arrays.asList("https://2test.com/trigger"))
+ Arrays.asList(
+ "https://2test.com/trigger",
+ "https://2test.com/trigger/extra/path"))
.setAttributionReportingUrl(Arrays.asList("https://2test.com"))
.setRemarketingResponseBasedRegistrationUrl(Arrays.asList("https://2test.com"))
.setEncryptionKeyUrl(Arrays.asList("https://2test.com/keys"))
@@ -86,6 +93,19 @@ public class EnrollmentDaoTest {
.setEncryptionKeyUrl(Arrays.asList("https://2test.com/keys"))
.build();
+ private static final EnrollmentData ENROLLMENT_DATA4 =
+ new EnrollmentData.Builder()
+ .setEnrollmentId("4")
+ .setCompanyId("1004")
+ .setSdkNames("4sdk 41sdk")
+ .setAttributionSourceRegistrationUrl(
+ Arrays.asList("https://4test.com", "https://prefix.test-prefix.com"))
+ .setAttributionTriggerRegistrationUrl(Arrays.asList("https://4test.com"))
+ .setAttributionReportingUrl(Arrays.asList("https://4test.com"))
+ .setRemarketingResponseBasedRegistrationUrl(Arrays.asList("https://4test.com"))
+ .setEncryptionKeyUrl(Arrays.asList("https://4test.com/keys"))
+ .build();
+
private static final EnrollmentData DUPLICATE_ID_ENROLLMENT_DATA =
new EnrollmentData.Builder()
.setEnrollmentId("1")
@@ -101,12 +121,16 @@ public class EnrollmentDaoTest {
@Before
public void setup() {
- mDbHelper = DbHelper.getInstance(sContext);
+ mDbHelper = DbTestUtil.getDbHelperForTest();
mEnrollmentDao = new EnrollmentDao(sContext, mDbHelper);
}
@After
public void cleanup() {
+ clearAllTables();
+ }
+
+ private void clearAllTables() {
for (String table : EnrollmentTables.ENROLLMENT_TABLES) {
mDbHelper.safeGetWritableDatabase().delete(table, null, null);
}
@@ -185,17 +209,208 @@ public class EnrollmentDaoTest {
}
@Test
- public void testGetEnrollmentDataFromMeasurementUrl() {
+ public void getEnrollmentDataFromMeasurementUrl_forSameSourceUri_isMatch() {
mEnrollmentDao.insert(ENROLLMENT_DATA2);
- EnrollmentData e = mEnrollmentDao.getEnrollmentDataFromMeasurementUrl("2test.com/source");
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/source"));
assertNotNull(e);
assertEquals(e, ENROLLMENT_DATA2);
- EnrollmentData e2 = mEnrollmentDao.getEnrollmentDataFromMeasurementUrl("2test2.com/source");
+ EnrollmentData e2 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test2.com/source"));
assertNotNull(e2);
assertEquals(e, e2);
}
@Test
+ public void getEnrollmentDataFromMeasurementUrl_forSameTriggerUri_isMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA2);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/trigger"));
+ assertNotNull(e);
+ assertEquals(e, ENROLLMENT_DATA2);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forSubdomainInSourceUri_isMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA2);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://prefix.2test.com/source"));
+ assertNotNull(e);
+ assertEquals(e, ENROLLMENT_DATA2);
+
+ EnrollmentData e1 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://prefix.2test2.com/source"));
+ assertNotNull(e1);
+ assertEquals(e1, ENROLLMENT_DATA2);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forSubdomainInTriggerUri_isMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA2);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://prefix.2test.com/trigger"));
+ assertNotNull(e);
+ assertEquals(e, ENROLLMENT_DATA2);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forDifferentDomain_doesNotMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA2);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://abc2test.com/source"));
+ assertNull(e);
+ EnrollmentData e1 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://abc2test.com/trigger"));
+ assertNull(e1);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forDifferentPath_doesNotMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA2);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/so"));
+ assertNull(e);
+
+ EnrollmentData e2 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test2.com/so"));
+ assertNull(e2);
+ EnrollmentData e3 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/tri"));
+ assertNull(e3);
+
+ EnrollmentData e4 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/trigger/extra"));
+ assertNull(e4);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forDifferentScheme_doesNotMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA2);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("http://2test.com/source"));
+ assertNull(e);
+ EnrollmentData e1 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("http://2test.com/trigger"));
+ assertNull(e1);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forPathNotInEnrollmentUri_doesNotMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA4);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://4test.com/path"));
+ assertNull(e);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forPathAsPrefix_matchesCorrectPath() {
+ EnrollmentData enrollmentData =
+ new EnrollmentData.Builder()
+ .setEnrollmentId("21")
+ .setCompanyId("1002")
+ .setSdkNames(Arrays.asList("2sdk", "anotherSdk"))
+ .setAttributionSourceRegistrationUrl(
+ Arrays.asList("https://2test.com/sourceanotherone"))
+ .setAttributionTriggerRegistrationUrl(
+ Arrays.asList("https://2test.com/triggeranotherone"))
+ .setAttributionReportingUrl(Arrays.asList("https://2test.com"))
+ .setRemarketingResponseBasedRegistrationUrl(
+ Arrays.asList("https://2test.com"))
+ .setEncryptionKeyUrl(Arrays.asList("https://2test.com/keys"))
+ .build();
+ mEnrollmentDao.insert(enrollmentData);
+ mEnrollmentDao.insert(ENROLLMENT_DATA2);
+
+ EnrollmentData e1 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/source"));
+
+ EnrollmentData e2 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/sourceanotherone"));
+
+ EnrollmentData e3 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/trigger"));
+
+ EnrollmentData e4 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://2test.com/triggeranotherone"));
+
+ assertNotNull(e1);
+ assertNotNull(e2);
+ assertNotNull(e3);
+ assertNotNull(e4);
+ assertEquals(e1, ENROLLMENT_DATA2);
+ assertEquals(e2, enrollmentData);
+ assertEquals(e3, ENROLLMENT_DATA2);
+ assertEquals(e4, enrollmentData);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forSubdomainChild_isMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA4);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://test-prefix.com"));
+ assertNotNull(e);
+ assertEquals(e, ENROLLMENT_DATA4);
+
+ EnrollmentData e1 =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://other-prefix.test-prefix.com"));
+ assertNotNull(e1);
+ assertEquals(e1, ENROLLMENT_DATA4);
+ }
+
+ @Test
+ public void getEnrollmentDataFromMeasurementUrl_forInvalidPublicSuffix_isNoMatch() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA4);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://4test.invalid"));
+ assertNull(e);
+ }
+
+ @Test
+ public void
+ getEnrollmentDataFromMeasurementUrl_forInvalidPublicSuffixInEnrollmentUri_isNoMatch() {
+ EnrollmentData enrollmentData =
+ new EnrollmentData.Builder()
+ .setEnrollmentId("4")
+ .setCompanyId("1004")
+ .setSdkNames("4sdk 41sdk")
+ .setAttributionSourceRegistrationUrl(Arrays.asList("https://4test.invalid"))
+ .setAttributionTriggerRegistrationUrl(
+ Arrays.asList("https://4test.invalid"))
+ .setAttributionReportingUrl(Arrays.asList("https://4test.invalid"))
+ .setRemarketingResponseBasedRegistrationUrl(
+ Arrays.asList("https://4test.invalid"))
+ .setEncryptionKeyUrl(Arrays.asList("https://4test.invalid/keys"))
+ .build();
+ mEnrollmentDao.insert(enrollmentData);
+ EnrollmentData e =
+ mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(
+ Uri.parse("https://4test.invalid"));
+ assertNull(e);
+ }
+
+ @Test
public void testGetEnrollmentDataForFledgeByAdTechIdentifier() {
mEnrollmentDao.insert(ENROLLMENT_DATA2);
AdTechIdentifier adtechIdentifier = AdTechIdentifier.fromString("2test.com", false);
@@ -206,6 +421,30 @@ public class EnrollmentDaoTest {
}
@Test
+ public void testGetAllFledgeEnrolledAdTechs_noEntries() {
+ // Delete any entries in the database
+ clearAllTables();
+
+ assertThat(mEnrollmentDao.getAllFledgeEnrolledAdTechs()).isEmpty();
+ }
+
+ @Test
+ public void testGetAllFledgeEnrolledAdTechs_multipleEntries() {
+ mEnrollmentDao.insert(ENROLLMENT_DATA1);
+ mEnrollmentDao.insert(ENROLLMENT_DATA2);
+ mEnrollmentDao.insert(ENROLLMENT_DATA3);
+
+ Set<AdTechIdentifier> enrolledFledgeAdTechIdentifiers =
+ mEnrollmentDao.getAllFledgeEnrolledAdTechs();
+
+ assertThat(enrolledFledgeAdTechIdentifiers).hasSize(2);
+ assertThat(enrolledFledgeAdTechIdentifiers)
+ .containsExactly(
+ AdTechIdentifier.fromString("1test.com"),
+ AdTechIdentifier.fromString("2test.com"));
+ }
+
+ @Test
public void testGetEnrollmentDataFromSdkName() {
mEnrollmentDao.insert(ENROLLMENT_DATA2);
EnrollmentData e = mEnrollmentDao.getEnrollmentDataFromSdkName("2sdk");
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AbstractDbIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AbstractDbIntegrationTest.java
index 4140c4828..0e12ed79e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AbstractDbIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AbstractDbIntegrationTest.java
@@ -23,13 +23,16 @@ import android.database.sqlite.SQLiteException;
import androidx.test.core.app.ApplicationProvider;
-import com.android.adservices.data.DbHelper;
+import com.android.adservices.data.DbTestUtil;
+import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.measurement.Attribution;
import com.android.adservices.service.measurement.EventReport;
import com.android.adservices.service.measurement.Source;
import com.android.adservices.service.measurement.Trigger;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import org.json.JSONArray;
import org.json.JSONException;
@@ -38,6 +41,8 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.io.IOException;
import java.io.InputStream;
@@ -47,6 +52,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
@@ -62,17 +68,28 @@ public abstract class AbstractDbIntegrationTest {
public final DbState mInput;
public final DbState mOutput;
+ private MockitoSession mStaticMockSession;
+
@Before
public void before() {
- SQLiteDatabase db = DbHelper.getInstance(sContext).getWritableDatabase();
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().getWritableDatabase();
emptyTables(db);
seedTables(db, mInput);
}
@After
public void after() {
- SQLiteDatabase db = DbHelper.getInstance(sContext).getWritableDatabase();
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().getWritableDatabase();
emptyTables(db);
+
+ mStaticMockSession.finishMocking();
}
public AbstractDbIntegrationTest(DbState input, DbState output) {
@@ -88,7 +105,7 @@ public abstract class AbstractDbIntegrationTest {
@Test
public void runTest() throws DatastoreException, JSONException {
runActionToTest();
- SQLiteDatabase readerDb = DbHelper.getInstance(sContext).getReadableDatabase();
+ SQLiteDatabase readerDb = DbTestUtil.getDbHelperForTest().getReadableDatabase();
DbState dbState = new DbState(readerDb);
mOutput.sortAll();
dbState.sortAll();
@@ -274,7 +291,8 @@ public abstract class AbstractDbIntegrationTest {
public static void insertToDb(Source source, SQLiteDatabase db) throws SQLiteException {
ContentValues values = new ContentValues();
values.put(MeasurementTables.SourceContract.ID, source.getId());
- values.put(MeasurementTables.SourceContract.EVENT_ID, source.getEventId());
+ values.put(MeasurementTables.SourceContract.EVENT_ID,
+ getNullableUnsignedLong(source.getEventId()));
values.put(MeasurementTables.SourceContract.SOURCE_TYPE, source.getSourceType().toString());
values.put(MeasurementTables.SourceContract.PUBLISHER,
source.getPublisher().toString());
@@ -282,7 +300,7 @@ public abstract class AbstractDbIntegrationTest {
source.getPublisherType());
values.put(
MeasurementTables.SourceContract.APP_DESTINATION,
- source.getAppDestination().toString());
+ source.getAppDestination() == null ? null : source.getAppDestination().toString());
values.put(
MeasurementTables.SourceContract.WEB_DESTINATION,
source.getWebDestination() == null ? null : source.getWebDestination().toString());
@@ -303,18 +321,15 @@ public abstract class AbstractDbIntegrationTest {
values.put(
MeasurementTables.SourceContract.AGGREGATE_CONTRIBUTIONS,
source.getAggregateContributions());
- values.put(MeasurementTables.SourceContract.FILTER_DATA, source.getAggregateFilterData());
+ values.put(MeasurementTables.SourceContract.FILTER_DATA, source.getFilterData());
long row = db.insert(MeasurementTables.SourceContract.TABLE, null, values);
if (row == -1) {
throw new SQLiteException("Source insertion failed");
}
}
- /**
- * Inserts a Trigger record into the given database.
- */
- private static void insertToDb(Trigger trigger, SQLiteDatabase db)
- throws SQLiteException {
+ /** Inserts a Trigger record into the given database. */
+ public static void insertToDb(Trigger trigger, SQLiteDatabase db) throws SQLiteException {
ContentValues values = new ContentValues();
values.put(MeasurementTables.TriggerContract.ID, trigger.getId());
values.put(MeasurementTables.TriggerContract.ATTRIBUTION_DESTINATION,
@@ -333,45 +348,46 @@ public abstract class AbstractDbIntegrationTest {
values.put(MeasurementTables.TriggerContract.REGISTRANT,
trigger.getRegistrant().toString());
values.put(MeasurementTables.TriggerContract.FILTERS, trigger.getFilters());
+ values.put(MeasurementTables.TriggerContract.NOT_FILTERS, trigger.getNotFilters());
long row = db.insert(MeasurementTables.TriggerContract.TABLE, null, values);
if (row == -1) {
throw new SQLiteException("Trigger insertion failed");
}
}
- /**
- * Inserts an EventReport record into the given database.
- */
- private static void insertToDb(EventReport report, SQLiteDatabase db)
- throws SQLiteException {
+ /** Inserts an EventReport record into the given database. */
+ public static void insertToDb(EventReport report, SQLiteDatabase db) throws SQLiteException {
ContentValues values = new ContentValues();
values.put(MeasurementTables.EventReportContract.ID, report.getId());
- values.put(MeasurementTables.EventReportContract.SOURCE_ID, report.getSourceId());
+ values.put(
+ MeasurementTables.EventReportContract.SOURCE_EVENT_ID,
+ report.getSourceEventId().getValue());
values.put(MeasurementTables.EventReportContract.ENROLLMENT_ID, report.getEnrollmentId());
values.put(MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
report.getAttributionDestination().toString());
values.put(MeasurementTables.EventReportContract.REPORT_TIME, report.getReportTime());
- values.put(MeasurementTables.EventReportContract.TRIGGER_DATA, report.getTriggerData());
+ values.put(MeasurementTables.EventReportContract.TRIGGER_DATA,
+ report.getTriggerData().getValue());
values.put(MeasurementTables.EventReportContract.TRIGGER_PRIORITY,
report.getTriggerPriority());
values.put(MeasurementTables.EventReportContract.TRIGGER_DEDUP_KEY,
- report.getTriggerDedupKey());
+ getNullableUnsignedLong(report.getTriggerDedupKey()));
values.put(MeasurementTables.EventReportContract.TRIGGER_TIME, report.getTriggerTime());
values.put(MeasurementTables.EventReportContract.STATUS, report.getStatus());
values.put(MeasurementTables.EventReportContract.SOURCE_TYPE,
report.getSourceType().toString());
values.put(MeasurementTables.EventReportContract.RANDOMIZED_TRIGGER_RATE,
report.getRandomizedTriggerRate());
+ values.put(MeasurementTables.EventReportContract.SOURCE_ID, report.getSourceId());
+ values.put(MeasurementTables.EventReportContract.TRIGGER_ID, report.getTriggerId());
long row = db.insert(MeasurementTables.EventReportContract.TABLE, null, values);
if (row == -1) {
throw new SQLiteException("EventReport insertion failed");
}
}
- /**
- * Inserts an Attribution record into the given database.
- */
- private static void insertToDb(Attribution attribution, SQLiteDatabase db)
+ /** Inserts an Attribution record into the given database. */
+ public static void insertToDb(Attribution attribution, SQLiteDatabase db)
throws SQLiteException {
ContentValues values = new ContentValues();
values.put(MeasurementTables.AttributionContract.ID, attribution.getId());
@@ -388,6 +404,8 @@ public abstract class AbstractDbIntegrationTest {
attribution.getTriggerTime());
values.put(MeasurementTables.AttributionContract.REGISTRANT,
attribution.getRegistrant());
+ values.put(MeasurementTables.AttributionContract.SOURCE_ID, attribution.getSourceId());
+ values.put(MeasurementTables.AttributionContract.TRIGGER_ID, attribution.getTriggerId());
long row = db.insert(MeasurementTables.AttributionContract.TABLE, null, values);
if (row == -1) {
throw new SQLiteException("Attribution insertion failed");
@@ -395,7 +413,7 @@ public abstract class AbstractDbIntegrationTest {
}
/** Inserts an AggregateReport record into the given database. */
- private static void insertToDb(AggregateReport aggregateReport, SQLiteDatabase db)
+ public static void insertToDb(AggregateReport aggregateReport, SQLiteDatabase db)
throws SQLiteException {
ContentValues values = new ContentValues();
values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId());
@@ -419,6 +437,8 @@ public abstract class AbstractDbIntegrationTest {
aggregateReport.getDebugCleartextPayload());
values.put(MeasurementTables.AggregateReport.STATUS, aggregateReport.getStatus());
values.put(MeasurementTables.AggregateReport.API_VERSION, aggregateReport.getApiVersion());
+ values.put(MeasurementTables.AggregateReport.SOURCE_ID, aggregateReport.getSourceId());
+ values.put(MeasurementTables.AggregateReport.TRIGGER_ID, aggregateReport.getTriggerId());
long row = db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
if (row == -1) {
throw new SQLiteException("AggregateReport insertion failed");
@@ -443,4 +463,8 @@ public abstract class AbstractDbIntegrationTest {
throw new SQLiteException("AggregateEncryptionKey insertion failed.");
}
}
+
+ private static Long getNullableUnsignedLong(UnsignedLong ulong) {
+ return Optional.ofNullable(ulong).map(UnsignedLong::getValue).orElse(null);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AppDeletionIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AppDeletionIntegrationTest.java
index 0b2ad8c70..5deda84da 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AppDeletionIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/AppDeletionIntegrationTest.java
@@ -18,6 +18,8 @@ package com.android.adservices.data.measurement;
import android.net.Uri;
+import com.android.adservices.data.DbTestUtil;
+
import org.json.JSONException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -49,9 +51,7 @@ public class AppDeletionIntegrationTest extends AbstractDbIntegrationTest {
}
public void runActionToTest() {
- DatastoreManagerFactory
- .getDatastoreManager(sContext)
- .runInTransaction(
- (dao) -> dao.deleteAppRecords(mUri));
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest())
+ .runInTransaction((dao) -> dao.deleteAppRecords(mUri));
}
}
diff --git a/adservices/service-core/java/com/android/adservices/data/measurement/migration/DbHelperV1.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DbHelperV1.java
index 8fa73742c..81c360650 100644
--- a/adservices/service-core/java/com/android/adservices/data/measurement/migration/DbHelperV1.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DbHelperV1.java
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.adservices.data.measurement.migration;
+package com.android.adservices.data.measurement;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import com.android.adservices.data.DbHelper;
import com.android.adservices.data.enrollment.EnrollmentTables;
-import com.android.adservices.data.measurement.MeasurementTables;
import com.android.adservices.data.topics.TopicsTables;
import java.util.Arrays;
@@ -29,14 +28,14 @@ import java.util.Collections;
import java.util.List;
/** Snapshot of DBHelper at Version 1 */
-class DbHelperV1 extends DbHelper {
+public class DbHelperV1 extends DbHelper {
/**
* @param context the context
* @param dbName Name of database to query
* @param dbVersion db version
*/
- DbHelperV1(Context context, String dbName, int dbVersion) {
+ public DbHelperV1(Context context, String dbName, int dbVersion) {
super(context, dbName, dbVersion);
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DbState.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DbState.java
index e649226bc..512374fb5 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DbState.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DbState.java
@@ -26,6 +26,7 @@ import com.android.adservices.service.measurement.Source;
import com.android.adservices.service.measurement.Trigger;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
@@ -219,13 +220,13 @@ public class DbState {
private Source getSourceFrom(JSONObject sJSON) throws JSONException {
return new Source.Builder()
.setId(sJSON.getString("id"))
- .setEventId(sJSON.getLong("eventId"))
+ .setEventId(new UnsignedLong(sJSON.getString("eventId")))
.setSourceType(
Source.SourceType.valueOf(
sJSON.getString("sourceType").toUpperCase(Locale.ENGLISH)))
.setPublisher(Uri.parse(sJSON.getString("publisher")))
.setPublisherType(sJSON.optInt("publisherType"))
- .setAppDestination(Uri.parse(sJSON.getString("appDestination")))
+ .setAppDestination(parseIfNonNull(sJSON.optString("appDestination", null)))
.setWebDestination(parseIfNonNull(sJSON.optString("webDestination", null)))
.setAggregateSource(sJSON.optString("aggregationKeys", null))
.setAggregateContributions(sJSON.optInt("aggregateContributions"))
@@ -241,7 +242,7 @@ public class DbState {
.setInstallAttributed(sJSON.optBoolean("installAttributed", false))
.setAttributionMode(
sJSON.optInt("attribution_mode", Source.AttributionMode.TRUTHFULLY))
- .setAggregateFilterData(sJSON.optString("aggregateFilterData", null))
+ .setFilterData(sJSON.optString("filterData", null))
.build();
}
@@ -258,16 +259,17 @@ public class DbState {
.setStatus(tJSON.getInt("status"))
.setRegistrant(Uri.parse(tJSON.getString("registrant")))
.setFilters(tJSON.optString("filters", null))
+ .setNotFilters(tJSON.optString("not_filters", null))
.build();
}
private EventReport getEventReportFrom(JSONObject rJSON) throws JSONException {
return new EventReport.Builder()
.setId(rJSON.getString("id"))
- .setSourceId(rJSON.getLong("sourceId"))
+ .setSourceEventId(new UnsignedLong(rJSON.getString("sourceEventId")))
.setAttributionDestination(Uri.parse(rJSON.getString("attributionDestination")))
.setEnrollmentId(rJSON.getString("enrollmentId"))
- .setTriggerData(rJSON.getLong("triggerData"))
+ .setTriggerData(new UnsignedLong(rJSON.getString("triggerData")))
.setTriggerTime(rJSON.getLong("triggerTime"))
.setReportTime(rJSON.getLong("reportTime"))
.setTriggerPriority(rJSON.getLong("triggerPriority"))
@@ -276,6 +278,8 @@ public class DbState {
.setSourceType(
Source.SourceType.valueOf(
rJSON.getString("sourceType").toUpperCase(Locale.ENGLISH)))
+ .setSourceId(rJSON.optString("sourceId", null))
+ .setTriggerId(rJSON.optString("triggerId", null))
.build();
}
@@ -290,6 +294,8 @@ public class DbState {
.setEnrollmentId(attrJSON.getString("enrollmentId"))
.setTriggerTime(attrJSON.getLong("triggerTime"))
.setRegistrant(attrJSON.getString("registrant"))
+ .setSourceId(attrJSON.optString("sourceId", null))
+ .setTriggerId(attrJSON.optString("triggerId", null))
.build();
}
@@ -336,6 +342,14 @@ public class DbState {
cursor.getString(
cursor.getColumnIndex(
MeasurementTables.AttributionContract.REGISTRANT)))
+ .setSourceId(
+ cursor.getString(
+ cursor.getColumnIndex(
+ MeasurementTables.AttributionContract.SOURCE_ID)))
+ .setTriggerId(
+ cursor.getString(
+ cursor.getColumnIndex(
+ MeasurementTables.AttributionContract.TRIGGER_ID)))
.build();
}
@@ -351,6 +365,8 @@ public class DbState {
.setDebugCleartextPayload(rJSON.getString("debugCleartextPayload"))
.setStatus(rJSON.getInt("status"))
.setApiVersion(rJSON.optString("apiVersion", null))
+ .setSourceId(rJSON.optString("sourceId", null))
+ .setTriggerId(rJSON.optString("triggerId", null))
.build();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteApiIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteApiIntegrationTest.java
index 3e24bfcf5..6ffc56d54 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteApiIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteApiIntegrationTest.java
@@ -16,10 +16,14 @@
package com.android.adservices.data.measurement;
+import android.adservices.measurement.DeletionParam;
import android.adservices.measurement.DeletionRequest;
import android.content.res.AssetManager;
import android.net.Uri;
+import com.android.adservices.data.DbTestUtil;
+import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -89,18 +93,20 @@ public class DeleteApiIntegrationTest extends AbstractDbIntegrationTest {
Integer finalMatchBehavior = matchBehavior;
Integer finalDeletionMode = deletionMode;
- DatastoreManagerFactory.getDatastoreManager(sContext)
- .runInTransaction(
- (dao) -> {
- dao.deleteMeasurementData(
- Uri.parse(registrantValue),
- startValueInstant,
- endValueInstant,
- originList,
- domainList,
- finalMatchBehavior,
- finalDeletionMode);
- });
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ MeasurementDataDeleter measurementDataDeleter =
+ new MeasurementDataDeleter(datastoreManager);
+ measurementDataDeleter.delete(
+ new DeletionParam.Builder()
+ .setPackageName(registrantValue)
+ .setMatchBehavior(finalMatchBehavior)
+ .setEnd(endValueInstant)
+ .setStart(startValueInstant)
+ .setDeletionMode(finalDeletionMode)
+ .setDomainUris(domainList)
+ .setOriginUris(originList)
+ .build());
}
private Object get(String name) {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredDynamicIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredDynamicIntegrationTest.java
index aa7fd7ce5..0e91dbf77 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredDynamicIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredDynamicIntegrationTest.java
@@ -19,7 +19,9 @@ import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DELETE
import android.net.Uri;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONException;
import org.junit.runner.RunWith;
@@ -54,7 +56,7 @@ public class DeleteExpiredDynamicIntegrationTest extends AbstractDbIntegrationTe
.setAppDestination(Uri.parse("android-app://com.example.app/aD"))
.setPublisher(Uri.parse("https://example.com/aS"))
.setId("non-expired")
- .setEventId(2L)
+ .setEventId(new UnsignedLong(2L))
.setPriority(3L)
.setEventTime(insideExpiredWindow)
.setExpiryTime(5L)
@@ -76,7 +78,7 @@ public class DeleteExpiredDynamicIntegrationTest extends AbstractDbIntegrationTe
// test, although it's ostensibly unused by this constructor.
public DeleteExpiredDynamicIntegrationTest(DbState input, DbState output, String name) {
super(input, output);
- this.mDatastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ this.mDatastoreManager = new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
}
public void runActionToTest() {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredIntegrationTest.java
index d33f6de91..09dfcc9c1 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/DeleteExpiredIntegrationTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.data.measurement;
+import com.android.adservices.data.DbTestUtil;
+
import org.json.JSONException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -44,7 +46,7 @@ public class DeleteExpiredIntegrationTest extends AbstractDbIntegrationTest {
}
public void runActionToTest() {
- DatastoreManagerFactory.getDatastoreManager(sContext)
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest())
.runInTransaction(IMeasurementDao::deleteExpiredRecords);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java
index ee509a89f..533109e8e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java
@@ -22,15 +22,23 @@ import static com.android.adservices.data.measurement.MeasurementTables.EventRep
import static com.android.adservices.data.measurement.MeasurementTables.MSMT_TABLE_PREFIX;
import static com.android.adservices.data.measurement.MeasurementTables.SourceContract;
import static com.android.adservices.data.measurement.MeasurementTables.TriggerContract;
+import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import android.adservices.measurement.DeletionRequest;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
@@ -39,9 +47,12 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.data.DbHelper;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.AsyncRegistrationFixture;
import com.android.adservices.service.measurement.Attribution;
import com.android.adservices.service.measurement.EventReport;
import com.android.adservices.service.measurement.EventSurfaceType;
+import com.android.adservices.service.measurement.PrivacyParams;
import com.android.adservices.service.measurement.Source;
import com.android.adservices.service.measurement.SourceFixture;
import com.android.adservices.service.measurement.Trigger;
@@ -49,12 +60,15 @@ import com.android.adservices.service.measurement.TriggerFixture;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
import com.android.adservices.service.measurement.aggregation.AggregateReport;
import com.android.adservices.service.measurement.aggregation.AggregateReportFixture;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.google.common.collect.ImmutableList;
+
+import org.json.JSONException;
import org.junit.After;
import org.junit.Assert;
-import org.junit.Rule;
+import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
@@ -74,22 +88,41 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
+import java.util.stream.IntStream;
import java.util.stream.Stream;
public class MeasurementDaoTest {
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
protected static final Context sContext = ApplicationProvider.getApplicationContext();
private static final Uri APP_TWO_SOURCES = Uri.parse("android-app://com.example1.two-sources");
private static final Uri APP_ONE_SOURCE = Uri.parse("android-app://com.example2.one-source");
- private static final Uri APP_NO_SOURCE = Uri.parse("android-app://com.example3.no-sources");
+ private static final String DEFAULT_ENROLLMENT_ID = "enrollment-id";
+ private static final Uri APP_TWO_PUBLISHER =
+ Uri.parse("android-app://com.publisher2.two-sources");
+ private static final Uri APP_ONE_PUBLISHER =
+ Uri.parse("android-app://com.publisher1.one-source");
+ private static final Uri APP_NO_PUBLISHER =
+ Uri.parse("android-app://com.publisher3.no-sources");
private static final Uri APP_TWO_TRIGGERS =
Uri.parse("android-app://com.example1.two-triggers");
private static final Uri APP_ONE_TRIGGER = Uri.parse("android-app://com.example1.one-trigger");
private static final Uri APP_NO_TRIGGERS = Uri.parse("android-app://com.example1.no-triggers");
private static final Uri INSTALLED_PACKAGE = Uri.parse("android-app://com.example.installed");
+ private static final Uri WEB_PUBLISHER_ONE = Uri.parse("https://not.example.com");
+ private static final Uri WEB_PUBLISHER_TWO = Uri.parse("https://notexample.com");
+ private static final Uri WEB_PUBLISHER_THREE = Uri.parse("http://not.example.com");
+ private static final Uri APP_DESTINATION = Uri.parse("android-app://com.destination.example");
+
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void before() {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+ }
@After
public void cleanup() {
@@ -97,6 +130,8 @@ public class MeasurementDaoTest {
for (String table : ALL_MSMT_TABLES) {
db.delete(table, null, null);
}
+
+ mStaticMockSession.finishMocking();
}
@Test
@@ -109,10 +144,10 @@ public class MeasurementDaoTest {
DbHelper.getInstance(sContext)
.getReadableDatabase()
.query(SourceContract.TABLE, null, null, null, null, null, null)) {
- Assert.assertTrue(sourceCursor.moveToNext());
+ assertTrue(sourceCursor.moveToNext());
Source source = SqliteObjectMapper.constructSourceFromCursor(sourceCursor);
- Assert.assertNotNull(source);
- Assert.assertNotNull(source.getId());
+ assertNotNull(source);
+ assertNotNull(source.getId());
assertEquals(validSource.getPublisher(), source.getPublisher());
assertEquals(validSource.getAppDestination(), source.getAppDestination());
assertEquals(validSource.getWebDestination(), source.getWebDestination());
@@ -127,7 +162,7 @@ public class MeasurementDaoTest {
assertEquals(validSource.getInstallCooldownWindow(), source.getInstallCooldownWindow());
assertEquals(validSource.getAttributionMode(), source.getAttributionMode());
assertEquals(validSource.getAggregateSource(), source.getAggregateSource());
- assertEquals(validSource.getAggregateFilterData(), source.getAggregateFilterData());
+ assertEquals(validSource.getFilterData(), source.getFilterData());
assertEquals(validSource.getAggregateContributions(),
source.getAggregateContributions());
}
@@ -149,7 +184,6 @@ public class MeasurementDaoTest {
final MockitoSession session =
ExtendedMockito.mockitoSession()
.spyStatic(DbHelper.class)
- .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
@@ -172,7 +206,7 @@ public class MeasurementDaoTest {
DbHelper.getInstance(sContext)
.getReadableDatabase()
.query(SourceContract.TABLE, null, null, null, null, null, null)) {
- Assert.assertFalse(sourceCursor.moveToNext());
+ assertFalse(sourceCursor.moveToNext());
}
} finally {
session.finishMocking();
@@ -189,10 +223,10 @@ public class MeasurementDaoTest {
DbHelper.getInstance(sContext)
.getReadableDatabase()
.query(TriggerContract.TABLE, null, null, null, null, null, null)) {
- Assert.assertTrue(triggerCursor.moveToNext());
+ assertTrue(triggerCursor.moveToNext());
Trigger trigger = SqliteObjectMapper.constructTriggerFromCursor(triggerCursor);
- Assert.assertNotNull(trigger);
- Assert.assertNotNull(trigger.getId());
+ assertNotNull(trigger);
+ assertNotNull(trigger.getId());
assertEquals(
validTrigger.getAttributionDestination(), trigger.getAttributionDestination());
assertEquals(validTrigger.getDestinationType(), trigger.getDestinationType());
@@ -219,7 +253,6 @@ public class MeasurementDaoTest {
final MockitoSession session =
ExtendedMockito.mockitoSession()
.spyStatic(DbHelper.class)
- .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
@@ -242,7 +275,7 @@ public class MeasurementDaoTest {
DbHelper.getInstance(sContext)
.getReadableDatabase()
.query(TriggerContract.TABLE, null, null, null, null, null, null)) {
- Assert.assertFalse(sourceCursor.moveToNext());
+ assertFalse(sourceCursor.moveToNext());
}
} finally {
session.finishMocking();
@@ -250,20 +283,56 @@ public class MeasurementDaoTest {
}
@Test
- public void testGetNumSourcesPerRegistrant() {
+ public void testGetNumSourcesPerPublisher_publisherTypeApp() {
setupSourceAndTriggerData();
DatastoreManager dm = DatastoreManagerFactory.getDatastoreManager(sContext);
dm.runInTransaction(
measurementDao -> {
- assertEquals(2, measurementDao.getNumSourcesPerRegistrant(APP_TWO_SOURCES));
+ assertEquals(
+ 2,
+ measurementDao.getNumSourcesPerPublisher(
+ APP_TWO_PUBLISHER, EventSurfaceType.APP));
+ });
+ dm.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ 1,
+ measurementDao.getNumSourcesPerPublisher(
+ APP_ONE_PUBLISHER, EventSurfaceType.APP));
+ });
+ dm.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ 0,
+ measurementDao.getNumSourcesPerPublisher(
+ APP_NO_PUBLISHER, EventSurfaceType.APP));
+ });
+ }
+
+ @Test
+ public void testGetNumSourcesPerPublisher_publisherTypeWeb() {
+ setupSourceDataForPublisherTypeWeb();
+ DatastoreManager dm = DatastoreManagerFactory.getDatastoreManager(sContext);
+ dm.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ 1,
+ measurementDao.getNumSourcesPerPublisher(
+ WEB_PUBLISHER_ONE, EventSurfaceType.WEB));
});
dm.runInTransaction(
measurementDao -> {
- assertEquals(1, measurementDao.getNumSourcesPerRegistrant(APP_ONE_SOURCE));
+ assertEquals(
+ 1,
+ measurementDao.getNumSourcesPerPublisher(
+ WEB_PUBLISHER_TWO, EventSurfaceType.WEB));
});
dm.runInTransaction(
measurementDao -> {
- assertEquals(0, measurementDao.getNumSourcesPerRegistrant(APP_NO_SOURCE));
+ assertEquals(
+ 1,
+ measurementDao.getNumSourcesPerPublisher(
+ WEB_PUBLISHER_THREE, EventSurfaceType.WEB));
});
}
@@ -286,6 +355,50 @@ public class MeasurementDaoTest {
}
@Test
+ public void testCountDistinctEnrollmentsPerPublisherXDestinationInAttribution_atWindow() {
+ Uri sourceSite = Uri.parse("android-app://publisher.app");
+ Uri appDestination = Uri.parse("android-app://destination.app");
+ String registrant = "android-app://registrant.app";
+ List<Attribution> attributionsWithAppDestinations =
+ getAttributionsWithDifferentEnrollments(
+ 4, appDestination, 5000000001L, sourceSite, registrant);
+ for (Attribution attribution : attributionsWithAppDestinations) {
+ insertAttribution(attribution);
+ }
+ DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ String excludedEnrollmentId = "enrollment-id-0";
+ datastoreManager.runInTransaction(
+ measurementDao -> {
+ assertEquals(Integer.valueOf(3), measurementDao
+ .countDistinctEnrollmentsPerPublisherXDestinationInAttribution(
+ sourceSite, appDestination, excludedEnrollmentId,
+ 5000000000L, 6000000000L));
+ });
+ }
+
+ @Test
+ public void testCountDistinctEnrollmentsPerPublisherXDestinationInAttribution_beyondWindow() {
+ Uri sourceSite = Uri.parse("android-app://publisher.app");
+ Uri appDestination = Uri.parse("android-app://destination.app");
+ String registrant = "android-app://registrant.app";
+ List<Attribution> attributionsWithAppDestinations =
+ getAttributionsWithDifferentEnrollments(
+ 4, appDestination, 5000000000L, sourceSite, registrant);
+ for (Attribution attribution : attributionsWithAppDestinations) {
+ insertAttribution(attribution);
+ }
+ DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ String excludedEnrollmentId = "enrollment-id-0";
+ datastoreManager.runInTransaction(
+ measurementDao -> {
+ assertEquals(Integer.valueOf(0), measurementDao
+ .countDistinctEnrollmentsPerPublisherXDestinationInAttribution(
+ sourceSite, appDestination, excludedEnrollmentId,
+ 5000000000L, 6000000000L));
+ });
+ }
+
+ @Test
public void testCountDistinctEnrollmentsPerPublisherXDestinationInAttribution_appDestination() {
Uri sourceSite = Uri.parse("android-app://publisher.app");
Uri webDestination = Uri.parse("https://web-destination.com");
@@ -368,6 +481,88 @@ public class MeasurementDaoTest {
}
@Test
+ public void testCountDistinctDestinationsPerPublisherInActiveSource_atWindow() {
+ Uri publisher = Uri.parse("android-app://publisher.app");
+ List<Source> activeSourcesWithAppAndWebDestinations =
+ getSourcesWithDifferentDestinations(
+ 4, true, true, 4500000001L, publisher,
+ SourceFixture.ValidSourceParams.ENROLLMENT_ID, Source.Status.ACTIVE);
+ for (Source source : activeSourcesWithAppAndWebDestinations) {
+ insertSource(source);
+ }
+ DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ Uri excludedDestination = Uri.parse("https://web-destination-2.com");
+ datastoreManager.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ Integer.valueOf(3),
+ measurementDao
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ publisher, EventSurfaceType.APP,
+ SourceFixture.ValidSourceParams.ENROLLMENT_ID,
+ excludedDestination, EventSurfaceType.WEB,
+ 4500000000L, 6000000000L));
+ });
+ }
+
+ @Test
+ public void testCountDistinctDestinationsPerPublisherInActiveSource_expiredSource() {
+ Uri publisher = Uri.parse("android-app://publisher.app");
+ List<Source> activeSourcesWithAppAndWebDestinations =
+ getSourcesWithDifferentDestinations(
+ 4, true, true, 4500000001L, publisher,
+ SourceFixture.ValidSourceParams.ENROLLMENT_ID, Source.Status.ACTIVE);
+ List<Source> expiredSourcesWithAppAndWebDestinations =
+ getSourcesWithDifferentDestinations(
+ 6, true, true, 4500000001L, 6000000000L, publisher,
+ SourceFixture.ValidSourceParams.ENROLLMENT_ID, Source.Status.ACTIVE);
+ for (Source source : activeSourcesWithAppAndWebDestinations) {
+ insertSource(source);
+ }
+ for (Source source : expiredSourcesWithAppAndWebDestinations) {
+ insertSource(source);
+ }
+ DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ Uri excludedDestination = Uri.parse("https://web-destination-2.com");
+ datastoreManager.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ Integer.valueOf(3),
+ measurementDao
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ publisher, EventSurfaceType.APP,
+ SourceFixture.ValidSourceParams.ENROLLMENT_ID,
+ excludedDestination, EventSurfaceType.WEB,
+ 4500000000L, 6000000000L));
+ });
+ }
+
+ @Test
+ public void testCountDistinctDestinationsPerPublisherInActiveSource_beyondWindow() {
+ Uri publisher = Uri.parse("android-app://publisher.app");
+ List<Source> activeSourcesWithAppAndWebDestinations =
+ getSourcesWithDifferentDestinations(
+ 4, true, true, 4500000000L, publisher,
+ SourceFixture.ValidSourceParams.ENROLLMENT_ID, Source.Status.ACTIVE);
+ for (Source source : activeSourcesWithAppAndWebDestinations) {
+ insertSource(source);
+ }
+ DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ Uri excludedDestination = Uri.parse("https://web-destination-2.com");
+ datastoreManager.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ Integer.valueOf(0),
+ measurementDao
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ publisher, EventSurfaceType.APP,
+ SourceFixture.ValidSourceParams.ENROLLMENT_ID,
+ excludedDestination, EventSurfaceType.WEB,
+ 4500000000L, 6000000000L));
+ });
+ }
+
+ @Test
public void testCountDistinctDestinationsPerPublisherInActiveSource_appPublisher() {
Uri publisher = Uri.parse("android-app://publisher.app");
List<Source> activeSourcesWithAppAndWebDestinations =
@@ -637,6 +832,85 @@ public class MeasurementDaoTest {
}
@Test
+ public void testCountDistinctEnrollmentsPerPublisherXDestinationInSource_atWindow() {
+ Uri publisher = Uri.parse("android-app://publisher.app");
+ Uri webDestination = Uri.parse("https://web-destination.com");
+ Uri appDestination = Uri.parse("android-app://destination.app");
+ List<Source> activeSourcesWithAppAndWebDestinations =
+ getSourcesWithDifferentEnrollments(
+ 2, appDestination, webDestination, 4500000001L, publisher,
+ Source.Status.ACTIVE);
+ for (Source source : activeSourcesWithAppAndWebDestinations) {
+ insertSource(source);
+ }
+ DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ String excludedEnrollmentId = "enrollment-id-1";
+ datastoreManager.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ Integer.valueOf(1),
+ measurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ publisher, EventSurfaceType.APP, appDestination,
+ excludedEnrollmentId, 4500000000L, 6000000000L));
+ });
+ }
+
+ @Test
+ public void testCountDistinctEnrollmentsPerPublisherXDestinationInSource_beyondWindow() {
+ Uri publisher = Uri.parse("android-app://publisher.app");
+ Uri webDestination = Uri.parse("https://web-destination.com");
+ Uri appDestination = Uri.parse("android-app://destination.app");
+ List<Source> activeSourcesWithAppAndWebDestinations =
+ getSourcesWithDifferentEnrollments(
+ 2, appDestination, webDestination, 4500000000L, publisher,
+ Source.Status.ACTIVE);
+ for (Source source : activeSourcesWithAppAndWebDestinations) {
+ insertSource(source);
+ }
+ DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ String excludedEnrollmentId = "enrollment-id-1";
+ datastoreManager.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ Integer.valueOf(0),
+ measurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ publisher, EventSurfaceType.APP, appDestination,
+ excludedEnrollmentId, 4500000000L, 6000000000L));
+ });
+ }
+
+ @Test
+ public void testCountDistinctEnrollmentsPerPublisherXDestinationInSource_expiredSource() {
+ Uri publisher = Uri.parse("android-app://publisher.app");
+ Uri webDestination = Uri.parse("https://web-destination.com");
+ Uri appDestination = Uri.parse("android-app://destination.app");
+ List<Source> activeSourcesWithAppAndWebDestinations =
+ getSourcesWithDifferentEnrollments(
+ 2, appDestination, webDestination, 4500000001L, publisher,
+ Source.Status.ACTIVE);
+ List<Source> expiredSourcesWithAppAndWebDestinations =
+ getSourcesWithDifferentEnrollments(
+ 4, appDestination, webDestination, 4500000000L, 6000000000L,
+ publisher, Source.Status.ACTIVE);
+ for (Source source : activeSourcesWithAppAndWebDestinations) {
+ insertSource(source);
+ }
+ for (Source source : expiredSourcesWithAppAndWebDestinations) {
+ insertSource(source);
+ }
+ DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ String excludedEnrollmentId = "enrollment-id-1";
+ datastoreManager.runInTransaction(
+ measurementDao -> {
+ assertEquals(
+ Integer.valueOf(1),
+ measurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ publisher, EventSurfaceType.APP, appDestination,
+ excludedEnrollmentId, 4500000000L, 6000000000L));
+ });
+ }
+
+ @Test
public void testCountDistinctEnrollmentsPerPublisherXDestinationInSource_appDestination() {
Uri publisher = Uri.parse("android-app://publisher.app");
Uri webDestination = Uri.parse("https://web-destination.com");
@@ -736,70 +1010,6 @@ public class MeasurementDaoTest {
});
}
- @Test(expected = NullPointerException.class)
- public void testDeleteMeasurementData_requiredRegistrantAsNull() {
- DatastoreManagerFactory.getDatastoreManager(sContext)
- .runInTransaction(
- (dao) -> {
- dao.deleteMeasurementData(
- null /* registrant */,
- null /* start */,
- null /* end */,
- Collections.emptyList(),
- Collections.emptyList(),
- 0,
- 0);
- });
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testDeleteMeasurementData_invalidRangeStartAfterEndDate() {
- DatastoreManagerFactory.getDatastoreManager(sContext)
- .runInTransaction(
- (dao) -> {
- dao.deleteMeasurementData(
- APP_ONE_SOURCE,
- Instant.now().plusMillis(1),
- Instant.now(),
- Collections.emptyList(),
- Collections.emptyList(),
- 0,
- 0);
- });
- }
-
- @Test(expected = NullPointerException.class)
- public void testDeleteMeasurementData_requiredStartAsNull() {
- DatastoreManagerFactory.getDatastoreManager(sContext)
- .runInTransaction(
- (dao) -> {
- dao.deleteMeasurementData(
- APP_ONE_SOURCE,
- null /* start */,
- Instant.now(),
- Collections.emptyList(),
- Collections.emptyList(),
- 0,
- 0);
- });
- }
-
- @Test(expected = NullPointerException.class)
- public void testDeleteMeasurementData_requiredEndAsNull() {
- DatastoreManagerFactory.getDatastoreManager(sContext)
- .runInTransaction(
- (dao) -> {
- dao.deleteMeasurementData(
- APP_ONE_SOURCE,
- Instant.now(),
- null /* end */,
- Collections.emptyList(),
- Collections.emptyList(),
- 0,
- 0);
- });
- }
-
@Test
public void testInstallAttribution_selectHighestPriority() {
long currentTimestamp = System.currentTimeMillis();
@@ -807,21 +1017,23 @@ public class MeasurementDaoTest {
SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
Objects.requireNonNull(db);
AbstractDbIntegrationTest.insertToDb(
- createSourceForIATest("IA1", currentTimestamp, 100, -1, false),
+ createSourceForIATest(
+ "IA1", currentTimestamp, 100, -1, false, DEFAULT_ENROLLMENT_ID),
db);
AbstractDbIntegrationTest.insertToDb(
- createSourceForIATest("IA2", currentTimestamp, 50, -1, false),
+ createSourceForIATest(
+ "IA2", currentTimestamp, 50, -1, false, DEFAULT_ENROLLMENT_ID),
db);
// Should select id IA1 because it has higher priority
- Assert.assertTrue(
+ assertTrue(
DatastoreManagerFactory.getDatastoreManager(sContext)
.runInTransaction(
measurementDao -> {
measurementDao.doInstallAttribution(
INSTALLED_PACKAGE, currentTimestamp);
}));
- Assert.assertTrue(getInstallAttributionStatus("IA1", db));
- Assert.assertFalse(getInstallAttributionStatus("IA2", db));
+ assertTrue(getInstallAttributionStatus("IA1", db));
+ assertFalse(getInstallAttributionStatus("IA2", db));
removeSources(Arrays.asList("IA1", "IA2"), db);
}
@@ -831,21 +1043,22 @@ public class MeasurementDaoTest {
SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
Objects.requireNonNull(db);
AbstractDbIntegrationTest.insertToDb(
- createSourceForIATest("IA1", currentTimestamp, -1, 10, false),
+ createSourceForIATest(
+ "IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID),
db);
AbstractDbIntegrationTest.insertToDb(
- createSourceForIATest("IA2", currentTimestamp, -1, 5, false),
+ createSourceForIATest("IA2", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID),
db);
// Should select id=IA2 as it is latest
- Assert.assertTrue(
+ assertTrue(
DatastoreManagerFactory.getDatastoreManager(sContext)
.runInTransaction(
measurementDao -> {
measurementDao.doInstallAttribution(
INSTALLED_PACKAGE, currentTimestamp);
}));
- Assert.assertFalse(getInstallAttributionStatus("IA1", db));
- Assert.assertTrue(getInstallAttributionStatus("IA2", db));
+ assertFalse(getInstallAttributionStatus("IA1", db));
+ assertTrue(getInstallAttributionStatus("IA2", db));
removeSources(Arrays.asList("IA1", "IA2"), db);
}
@@ -856,14 +1069,15 @@ public class MeasurementDaoTest {
SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
Objects.requireNonNull(db);
AbstractDbIntegrationTest.insertToDb(
- createSourceForIATest("IA1", currentTimestamp, -1, 10, false),
+ createSourceForIATest(
+ "IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID),
db);
AbstractDbIntegrationTest.insertToDb(
- createSourceForIATest("IA2", currentTimestamp, -1, 5, false),
+ createSourceForIATest("IA2", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID),
db);
// Should select id=IA1 as it is the only valid choice.
// id=IA2 is newer than the evenTimestamp of install event.
- Assert.assertTrue(
+ assertTrue(
DatastoreManagerFactory.getDatastoreManager(sContext)
.runInTransaction(
measurementDao -> {
@@ -871,8 +1085,8 @@ public class MeasurementDaoTest {
INSTALLED_PACKAGE,
currentTimestamp - TimeUnit.DAYS.toMillis(7));
}));
- Assert.assertTrue(getInstallAttributionStatus("IA1", db));
- Assert.assertFalse(getInstallAttributionStatus("IA2", db));
+ assertTrue(getInstallAttributionStatus("IA1", db));
+ assertFalse(getInstallAttributionStatus("IA2", db));
removeSources(Arrays.asList("IA1", "IA2"), db);
}
@@ -883,125 +1097,870 @@ public class MeasurementDaoTest {
SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
Objects.requireNonNull(db);
AbstractDbIntegrationTest.insertToDb(
- createSourceForIATest("IA1", currentTimestamp, 10, 10, true),
+ createSourceForIATest("IA1", currentTimestamp, 10, 10, true, DEFAULT_ENROLLMENT_ID),
db);
AbstractDbIntegrationTest.insertToDb(
- createSourceForIATest("IA2", currentTimestamp, 10, 11, true),
+ createSourceForIATest("IA2", currentTimestamp, 10, 11, true, DEFAULT_ENROLLMENT_ID),
db);
// Should not update any sources.
- Assert.assertTrue(
+ assertTrue(
DatastoreManagerFactory.getDatastoreManager(sContext)
.runInTransaction(
measurementDao ->
measurementDao.doInstallAttribution(
INSTALLED_PACKAGE, currentTimestamp)));
- Assert.assertFalse(getInstallAttributionStatus("IA1", db));
- Assert.assertFalse(getInstallAttributionStatus("IA2", db));
+ assertFalse(getInstallAttributionStatus("IA1", db));
+ assertFalse(getInstallAttributionStatus("IA2", db));
removeSources(Arrays.asList("IA1", "IA2"), db);
}
@Test
+ public void doInstallAttribution_noValidSourceStatus_IgnoresSources() {
+ long currentTimestamp = System.currentTimeMillis();
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ Objects.requireNonNull(db);
+ Source source =
+ createSourceForIATest(
+ "IA1", currentTimestamp, 100, -1, false, DEFAULT_ENROLLMENT_ID);
+
+ // Execution
+ // Active source should get install attributed
+ source.setStatus(Source.Status.ACTIVE);
+ AbstractDbIntegrationTest.insertToDb(source, db);
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.doInstallAttribution(
+ INSTALLED_PACKAGE, currentTimestamp)));
+ assertTrue(getInstallAttributionStatus("IA1", db));
+ removeSources(Collections.singletonList("IA1"), db);
+
+ // Active source should not get install attributed
+ source.setStatus(Source.Status.IGNORED);
+ AbstractDbIntegrationTest.insertToDb(source, db);
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.doInstallAttribution(
+ INSTALLED_PACKAGE, currentTimestamp)));
+ assertFalse(getInstallAttributionStatus("IA1", db));
+ removeSources(Collections.singletonList("IA1"), db);
+
+ // MARKED_TO_DELETE source should not get install attributed
+ source.setStatus(Source.Status.MARKED_TO_DELETE);
+ AbstractDbIntegrationTest.insertToDb(source, db);
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.doInstallAttribution(
+ INSTALLED_PACKAGE, currentTimestamp)));
+ assertFalse(getInstallAttributionStatus("IA1", db));
+ removeSources(Collections.singletonList("IA1"), db);
+ }
+
+ @Test
+ public void doInstallAttribution_withSourcesAcrossEnrollments_marksOneInstallFromEachAdTech() {
+ long currentTimestamp = System.currentTimeMillis();
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ Objects.requireNonNull(db);
+
+ // Enrollment1: Choose IA2 because that's newer and still occurred before install
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA1", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID + "_1"),
+ db);
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA2", currentTimestamp, -1, 9, false, DEFAULT_ENROLLMENT_ID + "_1"),
+ db);
+
+ // Enrollment2: Choose IA4 because IA3's install attribution window has expired
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA3", currentTimestamp, -1, 10, true, DEFAULT_ENROLLMENT_ID + "_2"),
+ db);
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA4", currentTimestamp, -1, 9, false, DEFAULT_ENROLLMENT_ID + "_2"),
+ db);
+
+ // Enrollment3: Choose IA5 because IA6 was registered after install event
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA5", currentTimestamp, -1, 10, false, DEFAULT_ENROLLMENT_ID + "_3"),
+ db);
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA6", currentTimestamp, -1, 5, false, DEFAULT_ENROLLMENT_ID + "_3"),
+ db);
+
+ // Enrollment4: Choose IA8 due to higher priority
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA7", currentTimestamp, 5, 10, false, DEFAULT_ENROLLMENT_ID + "_4"),
+ db);
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA8", currentTimestamp, 10, 10, false, DEFAULT_ENROLLMENT_ID + "_4"),
+ db);
+
+ // Enrollment5: Choose none because both sources are ineligible
+ // Expired install attribution window
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA9", currentTimestamp, 5, 31, true, DEFAULT_ENROLLMENT_ID + "_5"),
+ db);
+ // Registered after install attribution
+ AbstractDbIntegrationTest.insertToDb(
+ createSourceForIATest(
+ "IA10", currentTimestamp, 10, 3, false, DEFAULT_ENROLLMENT_ID + "_5"),
+ db);
+
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao -> {
+ measurementDao.doInstallAttribution(
+ INSTALLED_PACKAGE,
+ currentTimestamp - TimeUnit.DAYS.toMillis(7));
+ }));
+ assertTrue(getInstallAttributionStatus("IA2", db));
+ assertTrue(getInstallAttributionStatus("IA4", db));
+ assertTrue(getInstallAttributionStatus("IA5", db));
+ assertTrue(getInstallAttributionStatus("IA8", db));
+
+ assertFalse(getInstallAttributionStatus("IA1", db));
+ assertFalse(getInstallAttributionStatus("IA3", db));
+ assertFalse(getInstallAttributionStatus("IA6", db));
+ assertFalse(getInstallAttributionStatus("IA7", db));
+ assertFalse(getInstallAttributionStatus("IA9", db));
+ assertFalse(getInstallAttributionStatus("IA10", db));
+
+ removeSources(
+ Arrays.asList(
+ "IA1", "IA2", "IA3", "IA4", "IA5", "IA6", "IA7", "IA8", "IA8", "IA10"),
+ db);
+ }
+
+ @Test
+ public void deleteSources_providedIds_deletesMatchingSourcesAndRelatedData()
+ throws JSONException {
+ // Setup - Creates the following -
+ // source - S1, S2, S3, S4
+ // trigger - T1, T2, T3, T4
+ // event reports - E11, E12, E21, E22, E23, E33, E44
+ // aggregate reports - AR11, AR12, AR21, AR34
+ // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44
+ prepareDataForSourceAndTriggerDeletion();
+
+ // Execution
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao -> {
+ measurementDao.deleteSources(List.of("S1", "S2"));
+
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getSource("S1");
+ });
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getSource("S2");
+ });
+
+ assertNotNull(measurementDao.getSource("S3"));
+ assertNotNull(measurementDao.getSource("S4"));
+ assertNotNull(measurementDao.getTrigger("T1"));
+ assertNotNull(measurementDao.getTrigger("T2"));
+ assertNotNull(measurementDao.getTrigger("T3"));
+ assertNotNull(measurementDao.getTrigger("T4"));
+
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getEventReport("E11");
+ });
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getEventReport("E12");
+ });
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getEventReport("E21");
+ });
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getEventReport("E22");
+ });
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getEventReport("E23");
+ });
+ assertNotNull(measurementDao.getEventReport("E33"));
+ assertNotNull(measurementDao.getEventReport("E44"));
+
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getAggregateReport("AR11");
+ });
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getAggregateReport("AR12");
+ });
+ assertThrows(
+ DatastoreException.class,
+ () -> {
+ measurementDao.getAggregateReport("AR21");
+ });
+ assertNotNull(measurementDao.getAggregateReport("AR34"));
+ });
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
+ }
+
+ @Test
+ public void deleteTriggers_providedIds_deletesMatchingTriggersAndRelatedData()
+ throws JSONException {
+ // Setup - Creates the following -
+ // source - S1, S2, S3, S4
+ // trigger - T1, T2, T3, T4
+ // event reports - E11, E12, E21, E22, E23, E33, E44
+ // aggregate reports - AR11, AR12, AR21, AR34
+ // attributions - ATT11, ATT12, ATT21, ATT22, ATT33, ATT44
+ prepareDataForSourceAndTriggerDeletion();
+
+ // Execution
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao -> {
+ measurementDao.deleteTriggers(List.of("T1", "T2"));
+
+ assertNotNull(measurementDao.getSource("S1"));
+ assertNotNull(measurementDao.getSource("S2"));
+ assertNotNull(measurementDao.getSource("S3"));
+ assertNotNull(measurementDao.getSource("S4"));
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getTrigger("T1"));
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getTrigger("T2"));
+ assertNotNull(measurementDao.getTrigger("T3"));
+ assertNotNull(measurementDao.getTrigger("T4"));
+
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getEventReport("E11"));
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getEventReport("E12"));
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getEventReport("E21"));
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getEventReport("E22"));
+ assertNotNull(measurementDao.getEventReport("E23"));
+ assertNotNull(measurementDao.getEventReport("E33"));
+ assertNotNull(measurementDao.getEventReport("E44"));
+
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getAggregateReport("AR11"));
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getAggregateReport("AR12"));
+ assertThrows(
+ DatastoreException.class,
+ () -> measurementDao.getAggregateReport("AR21"));
+
+ assertNotNull(measurementDao.getAggregateReport("AR34"));
+ });
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ assertEquals(2, DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE));
+ }
+
+ private void prepareDataForSourceAndTriggerDeletion() throws JSONException {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ Source s1 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(1L))
+ .setId("S1")
+ .build(); // deleted
+ Source s2 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(2L))
+ .setId("S2")
+ .build(); // deleted
+ Source s3 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(3L))
+ .setId("S3")
+ .build();
+ Source s4 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(4L))
+ .setId("S4")
+ .build();
+ Trigger t1 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
+ .setId("T1")
+ .build();
+ Trigger t2 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
+ .setId("T2")
+ .build();
+ Trigger t3 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
+ .setId("T3")
+ .build();
+ Trigger t4 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
+ .setId("T4")
+ .build();
+ EventReport e11 = createEventReportForSourceAndTrigger("E11", s1, t1);
+ EventReport e12 = createEventReportForSourceAndTrigger("E12", s1, t2);
+ EventReport e21 = createEventReportForSourceAndTrigger("E21", s2, t1);
+ EventReport e22 = createEventReportForSourceAndTrigger("E22", s2, t2);
+ EventReport e23 = createEventReportForSourceAndTrigger("E23", s2, t3);
+ EventReport e33 = createEventReportForSourceAndTrigger("E33", s3, t3);
+ EventReport e44 = createEventReportForSourceAndTrigger("E44", s4, t4);
+ AggregateReport ar11 = createAggregateReportForSourceAndTrigger("AR11", s1, t1);
+ AggregateReport ar12 = createAggregateReportForSourceAndTrigger("AR12", s1, t2);
+ AggregateReport ar21 = createAggregateReportForSourceAndTrigger("AR21", s2, t1);
+ AggregateReport ar34 = createAggregateReportForSourceAndTrigger("AR34", s3, t4);
+ Attribution att11 =
+ createAttributionWithSourceAndTriggerIds(
+ "ATT11", s1.getId(), t1.getId()); // deleted
+ Attribution att12 =
+ createAttributionWithSourceAndTriggerIds(
+ "ATT12", s1.getId(), t2.getId()); // deleted
+ Attribution att21 =
+ createAttributionWithSourceAndTriggerIds(
+ "ATT21", s2.getId(), t1.getId()); // deleted
+ Attribution att22 =
+ createAttributionWithSourceAndTriggerIds(
+ "ATT22", s2.getId(), t2.getId()); // deleted
+ Attribution att33 =
+ createAttributionWithSourceAndTriggerIds("ATT33", s3.getId(), t3.getId());
+ Attribution att44 =
+ createAttributionWithSourceAndTriggerIds("ATT44", s4.getId(), t4.getId());
+
+ AbstractDbIntegrationTest.insertToDb(s1, db);
+ AbstractDbIntegrationTest.insertToDb(s2, db);
+ AbstractDbIntegrationTest.insertToDb(s3, db);
+ AbstractDbIntegrationTest.insertToDb(s4, db);
+
+ AbstractDbIntegrationTest.insertToDb(t1, db);
+ AbstractDbIntegrationTest.insertToDb(t2, db);
+ AbstractDbIntegrationTest.insertToDb(t3, db);
+ AbstractDbIntegrationTest.insertToDb(t4, db);
+
+ AbstractDbIntegrationTest.insertToDb(e11, db);
+ AbstractDbIntegrationTest.insertToDb(e12, db);
+ AbstractDbIntegrationTest.insertToDb(e21, db);
+ AbstractDbIntegrationTest.insertToDb(e22, db);
+ AbstractDbIntegrationTest.insertToDb(e23, db);
+ AbstractDbIntegrationTest.insertToDb(e33, db);
+ AbstractDbIntegrationTest.insertToDb(e44, db);
+
+ AbstractDbIntegrationTest.insertToDb(ar11, db);
+ AbstractDbIntegrationTest.insertToDb(ar12, db);
+ AbstractDbIntegrationTest.insertToDb(ar21, db);
+ AbstractDbIntegrationTest.insertToDb(ar34, db);
+
+ AbstractDbIntegrationTest.insertToDb(att11, db);
+ AbstractDbIntegrationTest.insertToDb(att12, db);
+ AbstractDbIntegrationTest.insertToDb(att21, db);
+ AbstractDbIntegrationTest.insertToDb(att22, db);
+ AbstractDbIntegrationTest.insertToDb(att33, db);
+ AbstractDbIntegrationTest.insertToDb(att44, db);
+ }
+
+ @Test
public void testUndoInstallAttribution_noMarkedSource() {
long currentTimestamp = System.currentTimeMillis();
SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
Objects.requireNonNull(db);
- Source source = createSourceForIATest("IA1", currentTimestamp, 10, 10, false);
+ Source source =
+ createSourceForIATest(
+ "IA1", currentTimestamp, 10, 10, false, DEFAULT_ENROLLMENT_ID);
source.setInstallAttributed(true);
AbstractDbIntegrationTest.insertToDb(source, db);
- Assert.assertTrue(
+ assertTrue(
DatastoreManagerFactory.getDatastoreManager(sContext)
.runInTransaction(
measurementDao ->
measurementDao.undoInstallAttribution(INSTALLED_PACKAGE)));
// Should set installAttributed = false for id=IA1
- Assert.assertFalse(getInstallAttributionStatus("IA1", db));
+ assertFalse(getInstallAttributionStatus("IA1", db));
+ }
+
+ @Test
+ public void getNumAggregateReportsPerDestination_returnsExpected() {
+ List<AggregateReport> reportsWithPlainDestination =
+ Arrays.asList(generateMockAggregateReport("https://destination-1.com", 1));
+ List<AggregateReport> reportsWithPlainAndSubDomainDestination =
+ Arrays.asList(
+ generateMockAggregateReport("https://destination-2.com", 2),
+ generateMockAggregateReport("https://subdomain.destination-2.com", 3));
+ List<AggregateReport> reportsWithPlainAndPathDestination =
+ Arrays.asList(
+ generateMockAggregateReport("https://subdomain.destination-3.com", 4),
+ generateMockAggregateReport("https://subdomain.destination-3.com/abcd", 5));
+ List<AggregateReport> reportsWithAll3Types =
+ Arrays.asList(
+ generateMockAggregateReport("https://destination-4.com", 6),
+ generateMockAggregateReport("https://subdomain.destination-4.com", 7),
+ generateMockAggregateReport("https://subdomain.destination-4.com/abcd", 8));
+ List<AggregateReport> reportsWithAndroidAppDestination =
+ Arrays.asList(generateMockAggregateReport("android-app://destination-5.app", 9));
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ Objects.requireNonNull(db);
+ Stream.of(
+ reportsWithPlainDestination,
+ reportsWithPlainAndSubDomainDestination,
+ reportsWithPlainAndPathDestination,
+ reportsWithAll3Types,
+ reportsWithAndroidAppDestination)
+ .flatMap(Collection::stream)
+ .forEach(
+ aggregateReport -> {
+ ContentValues values = new ContentValues();
+ values.put(
+ MeasurementTables.AggregateReport.ID, aggregateReport.getId());
+ values.put(
+ MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
+ aggregateReport.getAttributionDestination().toString());
+ db.insert(MeasurementTables.AggregateReport.TABLE, null, values);
+ });
+
+ List<String> attributionDestinations1 = createWebDestinationVariants(1);
+ List<String> attributionDestinations2 = createWebDestinationVariants(2);
+ List<String> attributionDestinations3 = createWebDestinationVariants(3);
+ List<String> attributionDestinations4 = createWebDestinationVariants(4);
+ List<String> attributionDestinations5 = createAppDestinationVariants(5);
+
+ // expected query return values for attribution destination variants
+ List<Integer> destination1ExpectedCounts = Arrays.asList(1, 1, 1, 1, 0);
+ List<Integer> destination2ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
+ List<Integer> destination3ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
+ List<Integer> destination4ExpectedCounts = Arrays.asList(3, 3, 3, 3, 0);
+ List<Integer> destination5ExpectedCounts = Arrays.asList(0, 0, 1, 1, 0);
+ assertAggregateReportCount(
+ attributionDestinations1, EventSurfaceType.WEB, destination1ExpectedCounts);
+ assertAggregateReportCount(
+ attributionDestinations2, EventSurfaceType.WEB, destination2ExpectedCounts);
+ assertAggregateReportCount(
+ attributionDestinations3, EventSurfaceType.WEB, destination3ExpectedCounts);
+ assertAggregateReportCount(
+ attributionDestinations4, EventSurfaceType.WEB, destination4ExpectedCounts);
+ assertAggregateReportCount(
+ attributionDestinations5, EventSurfaceType.APP, destination5ExpectedCounts);
+ }
+
+ @Test
+ public void getNumEventReportsPerDestination_returnsExpected() {
+ List<EventReport> reportsWithPlainDestination =
+ Arrays.asList(generateMockEventReport("https://destination-1.com", 1));
+ List<EventReport> reportsWithPlainAndSubDomainDestination =
+ Arrays.asList(
+ generateMockEventReport("https://destination-2.com", 2),
+ generateMockEventReport("https://subdomain.destination-2.com", 3));
+ List<EventReport> reportsWithPlainAndPathDestination =
+ Arrays.asList(
+ generateMockEventReport("https://subdomain.destination-3.com", 4),
+ generateMockEventReport("https://subdomain.destination-3.com/abcd", 5));
+ List<EventReport> reportsWithAll3Types =
+ Arrays.asList(
+ generateMockEventReport("https://destination-4.com", 6),
+ generateMockEventReport("https://subdomain.destination-4.com", 7),
+ generateMockEventReport("https://subdomain.destination-4.com/abcd", 8));
+ List<EventReport> reportsWithAndroidAppDestination =
+ Arrays.asList(generateMockEventReport("android-app://destination-5.app", 9));
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ Objects.requireNonNull(db);
+ Stream.of(
+ reportsWithPlainDestination,
+ reportsWithPlainAndSubDomainDestination,
+ reportsWithPlainAndPathDestination,
+ reportsWithAll3Types,
+ reportsWithAndroidAppDestination)
+ .flatMap(Collection::stream)
+ .forEach(
+ eventReport -> {
+ ContentValues values = new ContentValues();
+ values.put(
+ MeasurementTables.EventReportContract.ID, eventReport.getId());
+ values.put(
+ MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
+ eventReport.getAttributionDestination().toString());
+ db.insert(MeasurementTables.EventReportContract.TABLE, null, values);
+ });
+
+ List<String> attributionDestinations1 = createWebDestinationVariants(1);
+ List<String> attributionDestinations2 = createWebDestinationVariants(2);
+ List<String> attributionDestinations3 = createWebDestinationVariants(3);
+ List<String> attributionDestinations4 = createWebDestinationVariants(4);
+ List<String> attributionDestinations5 = createAppDestinationVariants(5);
+
+ // expected query return values for attribution destination variants
+ List<Integer> destination1ExpectedCounts = Arrays.asList(1, 1, 1, 1, 0);
+ List<Integer> destination2ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
+ List<Integer> destination3ExpectedCounts = Arrays.asList(2, 2, 2, 2, 0);
+ List<Integer> destination4ExpectedCounts = Arrays.asList(3, 3, 3, 3, 0);
+ List<Integer> destination5ExpectedCounts = Arrays.asList(0, 0, 1, 1, 0);
+ assertEventReportCount(
+ attributionDestinations1, EventSurfaceType.WEB, destination1ExpectedCounts);
+ assertEventReportCount(
+ attributionDestinations2, EventSurfaceType.WEB, destination2ExpectedCounts);
+ assertEventReportCount(
+ attributionDestinations3, EventSurfaceType.WEB, destination3ExpectedCounts);
+ assertEventReportCount(
+ attributionDestinations4, EventSurfaceType.WEB, destination4ExpectedCounts);
+ assertEventReportCount(
+ attributionDestinations5, EventSurfaceType.APP, destination5ExpectedCounts);
}
@Test
public void testGetSourceEventReports() {
- List<Source> sourceList = new ArrayList<>();
- sourceList.add(
- SourceFixture.getValidSourceBuilder()
+ List<Source> sourceList =
+ Arrays.asList(
+ SourceFixture.getValidSourceBuilder()
+ .setId("1")
+ .setEventId(new UnsignedLong(3L))
+ .setEnrollmentId("1")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setId("2")
+ .setEventId(new UnsignedLong(4L))
+ .setEnrollmentId("1")
+ .build(),
+ // Should always be ignored
+ SourceFixture.getValidSourceBuilder()
+ .setId("3")
+ .setEventId(new UnsignedLong(4L))
+ .setEnrollmentId("2")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setId("15")
+ .setEventId(new UnsignedLong(15L))
+ .setEnrollmentId("2")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setId("16")
+ .setEventId(new UnsignedLong(16L))
+ .setEnrollmentId("2")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setId("20")
+ .setEventId(new UnsignedLong(20L))
+ .setEnrollmentId("2")
+ .build());
+
+ List<Trigger> triggers =
+ Arrays.asList(
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("101")
+ .setEnrollmentId("2")
+ .build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("102")
+ .setEnrollmentId("2")
+ .build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("201")
+ .setEnrollmentId("2")
+ .build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("202")
+ .setEnrollmentId("2")
+ .build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("1001")
+ .setEnrollmentId("2")
+ .build());
+
+ // Should match with source 1
+ List<EventReport> reportList1 = new ArrayList<>();
+ reportList1.add(
+ new EventReport.Builder()
.setId("1")
- .setEventId(3)
+ .setSourceEventId(new UnsignedLong(3L))
.setEnrollmentId("1")
+ .setAttributionDestination(sourceList.get(0).getAppDestination())
+ .setSourceType(sourceList.get(0).getSourceType())
+ .setSourceId("1")
+ .setTriggerId("101")
.build());
- sourceList.add(
- SourceFixture.getValidSourceBuilder()
- .setId("2")
- .setEventId(4)
+ reportList1.add(
+ new EventReport.Builder()
+ .setId("7")
+ .setSourceEventId(new UnsignedLong(3L))
.setEnrollmentId("1")
+ .setAttributionDestination(sourceList.get(0).getAppDestination())
+ .setAttributionDestination(APP_DESTINATION)
+ .setSourceType(sourceList.get(0).getSourceType())
+ .setSourceId("1")
+ .setTriggerId("102")
.build());
- // Should always be ignored
- sourceList.add(
- SourceFixture.getValidSourceBuilder()
+
+ // Should match with source 2
+ List<EventReport> reportList2 = new ArrayList<>();
+ reportList2.add(
+ new EventReport.Builder()
.setId("3")
- .setEventId(4)
- .setEnrollmentId("2")
+ .setSourceEventId(new UnsignedLong(4L))
+ .setEnrollmentId("1")
+ .setAttributionDestination(sourceList.get(1).getAppDestination())
+ .setSourceType(sourceList.get(1).getSourceType())
+ .setSourceId("2")
+ .setTriggerId("201")
.build());
+ reportList2.add(
+ new EventReport.Builder()
+ .setId("8")
+ .setSourceEventId(new UnsignedLong(4L))
+ .setEnrollmentId("1")
+ .setAttributionDestination(sourceList.get(1).getAppDestination())
+ .setSourceType(sourceList.get(1).getSourceType())
+ .setSourceId("2")
+ .setTriggerId("202")
+ .build());
+
+ List<EventReport> reportList3 = new ArrayList<>();
+ // Should not match with any source
+ reportList3.add(
+ new EventReport.Builder()
+ .setId("2")
+ .setSourceEventId(new UnsignedLong(5L))
+ .setEnrollmentId("1")
+ .setSourceType(Source.SourceType.EVENT)
+ .setAttributionDestination(APP_DESTINATION)
+ .setSourceId("15")
+ .setTriggerId("1001")
+ .build());
+ reportList3.add(
+ new EventReport.Builder()
+ .setId("4")
+ .setSourceEventId(new UnsignedLong(6L))
+ .setEnrollmentId("1")
+ .setSourceType(Source.SourceType.EVENT)
+ .setAttributionDestination(APP_DESTINATION)
+ .setSourceId("16")
+ .setTriggerId("1001")
+ .build());
+ reportList3.add(
+ new EventReport.Builder()
+ .setId("5")
+ .setSourceEventId(new UnsignedLong(1L))
+ .setEnrollmentId("1")
+ .setSourceType(Source.SourceType.EVENT)
+ .setAttributionDestination(APP_DESTINATION)
+ .setSourceId("15")
+ .setTriggerId("1001")
+ .build());
+ reportList3.add(
+ new EventReport.Builder()
+ .setId("6")
+ .setSourceEventId(new UnsignedLong(2L))
+ .setEnrollmentId("1")
+ .setSourceType(Source.SourceType.EVENT)
+ .setAttributionDestination(APP_DESTINATION)
+ .setSourceId("20")
+ .setTriggerId("1001")
+ .build());
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ Objects.requireNonNull(db);
+ sourceList.forEach(source -> AbstractDbIntegrationTest.insertToDb(source, db));
+ triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
+
+ Stream.of(reportList1, reportList2, reportList3)
+ .flatMap(Collection::stream)
+ .forEach(
+ (eventReport -> {
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertEventReport(eventReport));
+ }));
+
+ assertEquals(
+ reportList1,
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransactionWithResult(
+ measurementDao ->
+ measurementDao.getSourceEventReports(sourceList.get(0)))
+ .orElseThrow());
+
+ assertEquals(
+ reportList2,
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransactionWithResult(
+ measurementDao ->
+ measurementDao.getSourceEventReports(sourceList.get(1)))
+ .orElseThrow());
+ }
+
+ @Test
+ public void getSourceEventReports_sourcesWithSameEventId_haveSeparateEventReportsMatch() {
+ List<Source> sourceList =
+ Arrays.asList(
+ SourceFixture.getValidSourceBuilder()
+ .setId("1")
+ .setEventId(new UnsignedLong(1L))
+ .setEnrollmentId("1")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setId("2")
+ .setEventId(new UnsignedLong(1L))
+ .setEnrollmentId("1")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setId("3")
+ .setEventId(new UnsignedLong(2L))
+ .setEnrollmentId("2")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setId("4")
+ .setEventId(new UnsignedLong(2L))
+ .setEnrollmentId("2")
+ .build());
+
+ List<Trigger> triggers =
+ Arrays.asList(
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("101")
+ .setEnrollmentId("2")
+ .build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("102")
+ .setEnrollmentId("2")
+ .build());
// Should match with source 1
List<EventReport> reportList1 = new ArrayList<>();
reportList1.add(
- new EventReport.Builder().setId("1").setSourceId(3).setEnrollmentId("1").build());
+ new EventReport.Builder()
+ .setId("1")
+ .setSourceEventId(new UnsignedLong(1L))
+ .setEnrollmentId("1")
+ .setAttributionDestination(sourceList.get(0).getAppDestination())
+ .setSourceType(sourceList.get(0).getSourceType())
+ .setSourceId("1")
+ .setTriggerId("101")
+ .build());
reportList1.add(
- new EventReport.Builder().setId("7").setSourceId(3).setEnrollmentId("1").build());
+ new EventReport.Builder()
+ .setId("2")
+ .setSourceEventId(new UnsignedLong(1L))
+ .setEnrollmentId("1")
+ .setAttributionDestination(sourceList.get(0).getAppDestination())
+ .setAttributionDestination(APP_DESTINATION)
+ .setSourceType(sourceList.get(0).getSourceType())
+ .setSourceId("1")
+ .setTriggerId("102")
+ .build());
// Should match with source 2
List<EventReport> reportList2 = new ArrayList<>();
reportList2.add(
- new EventReport.Builder().setId("3").setSourceId(4).setEnrollmentId("1").build());
+ new EventReport.Builder()
+ .setId("3")
+ .setSourceEventId(new UnsignedLong(2L))
+ .setEnrollmentId("1")
+ .setAttributionDestination(sourceList.get(1).getAppDestination())
+ .setSourceType(sourceList.get(1).getSourceType())
+ .setSourceId("2")
+ .setTriggerId("101")
+ .build());
reportList2.add(
- new EventReport.Builder().setId("8").setSourceId(4).setEnrollmentId("1").build());
+ new EventReport.Builder()
+ .setId("4")
+ .setSourceEventId(new UnsignedLong(2L))
+ .setEnrollmentId("1")
+ .setAttributionDestination(sourceList.get(1).getAppDestination())
+ .setSourceType(sourceList.get(1).getSourceType())
+ .setSourceId("2")
+ .setTriggerId("102")
+ .build());
+ // Match with source3
List<EventReport> reportList3 = new ArrayList<>();
- // Should not match with any source
- reportList3.add(
- new EventReport.Builder().setId("2").setSourceId(5).setEnrollmentId("1").build());
- reportList3.add(
- new EventReport.Builder().setId("4").setSourceId(6).setEnrollmentId("1").build());
reportList3.add(
- new EventReport.Builder().setId("5").setSourceId(1).setEnrollmentId("1").build());
+ new EventReport.Builder()
+ .setId("5")
+ .setSourceEventId(new UnsignedLong(2L))
+ .setEnrollmentId("2")
+ .setSourceType(Source.SourceType.EVENT)
+ .setAttributionDestination(APP_DESTINATION)
+ .setSourceId("3")
+ .setTriggerId("101")
+ .build());
reportList3.add(
- new EventReport.Builder().setId("6").setSourceId(2).setEnrollmentId("1").build());
+ new EventReport.Builder()
+ .setId("6")
+ .setSourceEventId(new UnsignedLong(2L))
+ .setEnrollmentId("2")
+ .setSourceType(Source.SourceType.EVENT)
+ .setAttributionDestination(APP_DESTINATION)
+ .setSourceId("3")
+ .setTriggerId("102")
+ .build());
SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
Objects.requireNonNull(db);
- sourceList.forEach(
- source -> {
- ContentValues values = new ContentValues();
- values.put(SourceContract.ID, source.getId());
- values.put(SourceContract.EVENT_ID, source.getEventId());
- values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId());
- db.insert(SourceContract.TABLE, null, values);
- });
+ sourceList.forEach(source -> AbstractDbIntegrationTest.insertToDb(source, db));
+ triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
+
Stream.of(reportList1, reportList2, reportList3)
.flatMap(Collection::stream)
.forEach(
- eventReport -> {
- ContentValues values = new ContentValues();
- values.put(EventReportContract.ID, eventReport.getId());
- values.put(EventReportContract.SOURCE_ID, eventReport.getSourceId());
- values.put(
- EventReportContract.ENROLLMENT_ID,
- eventReport.getEnrollmentId());
- db.insert(EventReportContract.TABLE, null, values);
- });
+ (eventReport -> {
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertEventReport(eventReport));
+ }));
- Assert.assertEquals(
+ assertEquals(
reportList1,
DatastoreManagerFactory.getDatastoreManager(sContext)
.runInTransactionWithResult(
- measurementDao -> measurementDao.getSourceEventReports(
- sourceList.get(0)))
+ measurementDao ->
+ measurementDao.getSourceEventReports(sourceList.get(0)))
.orElseThrow());
- Assert.assertEquals(
+ assertEquals(
reportList2,
DatastoreManagerFactory.getDatastoreManager(sContext)
.runInTransactionWithResult(
- measurementDao -> measurementDao.getSourceEventReports(
- sourceList.get(1)))
+ measurementDao ->
+ measurementDao.getSourceEventReports(sourceList.get(1)))
+ .orElseThrow());
+
+ assertEquals(
+ reportList3,
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransactionWithResult(
+ measurementDao ->
+ measurementDao.getSourceEventReports(sourceList.get(2)))
.orElseThrow());
}
@@ -1023,18 +1982,20 @@ public class MeasurementDaoTest {
});
// Multiple Elements
- Assert.assertTrue(DatastoreManagerFactory.getDatastoreManager(sContext)
- .runInTransaction(
- measurementDao -> measurementDao.updateSourceStatus(
- sourceList, Source.Status.IGNORED)
- ));
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.updateSourceStatus(
+ List.of("1", "2", "3"), Source.Status.IGNORED)));
// Single Element
- Assert.assertTrue(DatastoreManagerFactory.getDatastoreManager(sContext)
- .runInTransaction(
- measurementDao -> measurementDao.updateSourceStatus(
- sourceList.subList(0, 1), Source.Status.IGNORED)
- ));
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.updateSourceStatus(
+ List.of("1", "2"), Source.Status.IGNORED)));
}
@Test
@@ -1130,18 +2091,18 @@ public class MeasurementDaoTest {
.setDestinationType(EventSurfaceType.APP)
.build();
List<Source> result1 = runFunc.apply(trigger1MatchSource1And2);
- Assert.assertEquals(3, result1.size());
- Assert.assertEquals(sApp1.getId(), result1.get(0).getId());
- Assert.assertEquals(sApp2.getId(), result1.get(1).getId());
- Assert.assertEquals(sAppWeb7.getId(), result1.get(2).getId());
+ assertEquals(3, result1.size());
+ assertEquals(sApp1.getId(), result1.get(0).getId());
+ assertEquals(sApp2.getId(), result1.get(1).getId());
+ assertEquals(sAppWeb7.getId(), result1.get(2).getId());
// Trigger Time > sApp1's eventTime and = sApp1's expiryTime
// Trigger Time > sApp2's eventTime and < sApp2's expiryTime
// Trigger Time = sApp3's eventTime
// Trigger Time < sApp4's eventTime
// sApp5 and sApp6 don't have app destination
- // Trigger Time > sAppWeb7's eventTime and < sAppWeb7's expiryTime
- // Expected: Match with sApp1, sApp2, sAppWeb7
+ // Trigger Time > sAppWeb7's eventTime and = sAppWeb7's expiryTime
+ // Expected: Match with sApp2, sApp3
Trigger trigger2MatchSource127 =
TriggerFixture.getValidTriggerBuilder()
.setTriggerTime(20)
@@ -1151,10 +2112,9 @@ public class MeasurementDaoTest {
.build();
List<Source> result2 = runFunc.apply(trigger2MatchSource127);
- Assert.assertEquals(3, result2.size());
- Assert.assertEquals(sApp1.getId(), result2.get(0).getId());
- Assert.assertEquals(sApp2.getId(), result2.get(1).getId());
- Assert.assertEquals(sAppWeb7.getId(), result2.get(2).getId());
+ assertEquals(2, result2.size());
+ assertEquals(sApp2.getId(), result2.get(0).getId());
+ assertEquals(sApp3.getId(), result2.get(1).getId());
// Trigger Time > sApp1's expiryTime
// Trigger Time > sApp2's eventTime and < sApp2's expiryTime
@@ -1172,9 +2132,9 @@ public class MeasurementDaoTest {
.build();
List<Source> result3 = runFunc.apply(trigger3MatchSource237);
- Assert.assertEquals(2, result3.size());
- Assert.assertEquals(sApp2.getId(), result3.get(0).getId());
- Assert.assertEquals(sApp3.getId(), result3.get(1).getId());
+ assertEquals(2, result3.size());
+ assertEquals(sApp2.getId(), result3.get(0).getId());
+ assertEquals(sApp3.getId(), result3.get(1).getId());
// Trigger Time > sApp1's expiryTime
// Trigger Time > sApp2's eventTime and < sApp2's expiryTime
@@ -1192,10 +2152,10 @@ public class MeasurementDaoTest {
.build();
List<Source> result4 = runFunc.apply(trigger4MatchSource1And2And3);
- Assert.assertEquals(3, result4.size());
- Assert.assertEquals(sApp2.getId(), result4.get(0).getId());
- Assert.assertEquals(sApp3.getId(), result4.get(1).getId());
- Assert.assertEquals(sApp4.getId(), result4.get(2).getId());
+ assertEquals(3, result4.size());
+ assertEquals(sApp2.getId(), result4.get(0).getId());
+ assertEquals(sApp3.getId(), result4.get(1).getId());
+ assertEquals(sApp4.getId(), result4.get(2).getId());
// sApp1, sApp2, sApp3, sApp4 don't have web destination
// Trigger Time > sWeb5's eventTime and < sApp5's expiryTime
@@ -1210,10 +2170,10 @@ public class MeasurementDaoTest {
.setDestinationType(EventSurfaceType.WEB)
.build();
List<Source> result5 = runFunc.apply(trigger5MatchSource567);
- Assert.assertEquals(3, result1.size());
- Assert.assertEquals(sWeb5.getId(), result5.get(0).getId());
- Assert.assertEquals(sWeb6.getId(), result5.get(1).getId());
- Assert.assertEquals(sAppWeb7.getId(), result5.get(2).getId());
+ assertEquals(3, result1.size());
+ assertEquals(sWeb5.getId(), result5.get(0).getId());
+ assertEquals(sWeb6.getId(), result5.get(1).getId());
+ assertEquals(sAppWeb7.getId(), result5.get(2).getId());
// sApp1, sApp2, sApp3, sApp4 don't have web destination
// Trigger Time > sWeb5's expiryTime
@@ -1229,8 +2189,8 @@ public class MeasurementDaoTest {
.build();
List<Source> result6 = runFunc.apply(trigger6MatchSource67);
- Assert.assertEquals(1, result6.size());
- Assert.assertEquals(sWeb6.getId(), result6.get(0).getId());
+ assertEquals(1, result6.size());
+ assertEquals(sWeb6.getId(), result6.get(0).getId());
}
private void insertInDb(SQLiteDatabase db, Source source) {
@@ -1277,11 +2237,11 @@ public class MeasurementDaoTest {
null,
null,
null)) {
- Assert.assertTrue(cursor.moveToNext());
+ assertTrue(cursor.moveToNext());
AggregateEncryptionKey aggregateEncryptionKey =
SqliteObjectMapper.constructAggregateEncryptionKeyFromCursor(cursor);
- Assert.assertNotNull(aggregateEncryptionKey);
- Assert.assertNotNull(aggregateEncryptionKey.getId());
+ assertNotNull(aggregateEncryptionKey);
+ assertNotNull(aggregateEncryptionKey.getId());
assertEquals(keyId, aggregateEncryptionKey.getKeyId());
assertEquals(publicKey, aggregateEncryptionKey.getPublicKey());
assertEquals(expiry, aggregateEncryptionKey.getExpiry());
@@ -1298,12 +2258,12 @@ public class MeasurementDaoTest {
DbHelper.getInstance(sContext).getReadableDatabase()
.query(MeasurementTables.AggregateReport.TABLE,
null, null, null, null, null, null)) {
- Assert.assertTrue(cursor.moveToNext());
+ assertTrue(cursor.moveToNext());
AggregateReport aggregateReport =
SqliteObjectMapper.constructAggregateReport(cursor);
- Assert.assertNotNull(aggregateReport);
- Assert.assertNotNull(aggregateReport.getId());
- Assert.assertTrue(Objects.equals(validAggregateReport, aggregateReport));
+ assertNotNull(aggregateReport);
+ assertNotNull(aggregateReport.getId());
+ assertTrue(Objects.equals(validAggregateReport, aggregateReport));
}
}
@@ -1489,7 +2449,44 @@ public class MeasurementDaoTest {
.setSourceOrigin(source.getPublisher().toString())
.setSourceSite(source.getPublisher().toString())
.setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
+ .setTriggerTime(trigger.getTriggerTime())
+ .build();
+ DatastoreManager dm = DatastoreManagerFactory.getDatastoreManager(sContext);
+
+ // Execution
+ dm.runInTransaction(
+ (dao) -> {
+ dao.insertAttribution(attribution);
+ });
+
+ // Assertion
+ AtomicLong attributionsCount = new AtomicLong();
+ dm.runInTransaction(
+ (dao) -> {
+ attributionsCount.set(dao.getAttributionsPerRateLimitWindow(source, trigger));
+ });
+
+ assertEquals(1L, attributionsCount.get());
+ }
+
+ @Test
+ public void testGetAttributionsPerRateLimitWindow_atTimeWindow() {
+ // Setup
+ Source source = SourceFixture.getValidSource();
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1))
+ .build();
+ Attribution attribution =
+ new Attribution.Builder()
+ .setEnrollmentId(source.getEnrollmentId())
+ .setDestinationOrigin(source.getWebDestination().toString())
+ .setDestinationSite(source.getAppDestination().toString())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(trigger.getTriggerTime()
+ - PrivacyParams.RATE_LIMIT_WINDOW_MILLISECONDS + 1)
.build();
DatastoreManager dm = DatastoreManagerFactory.getDatastoreManager(sContext);
@@ -1510,6 +2507,43 @@ public class MeasurementDaoTest {
}
@Test
+ public void testGetAttributionsPerRateLimitWindow_beyondTimeWindow() {
+ // Setup
+ Source source = SourceFixture.getValidSource();
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setTriggerTime(source.getEventTime() + TimeUnit.HOURS.toMillis(1))
+ .build();
+ Attribution attribution =
+ new Attribution.Builder()
+ .setEnrollmentId(source.getEnrollmentId())
+ .setDestinationOrigin(source.getWebDestination().toString())
+ .setDestinationSite(source.getAppDestination().toString())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(trigger.getTriggerTime()
+ - PrivacyParams.RATE_LIMIT_WINDOW_MILLISECONDS)
+ .build();
+ DatastoreManager dm = DatastoreManagerFactory.getDatastoreManager(sContext);
+
+ // Execution
+ dm.runInTransaction(
+ (dao) -> {
+ dao.insertAttribution(attribution);
+ });
+
+ // Assertion
+ AtomicLong attributionsCount = new AtomicLong();
+ dm.runInTransaction(
+ (dao) -> {
+ attributionsCount.set(dao.getAttributionsPerRateLimitWindow(source, trigger));
+ });
+
+ assertEquals(0L, attributionsCount.get());
+ }
+
+ @Test
public void testTransactionRollbackForRuntimeException() {
assertThrows(
IllegalArgumentException.class,
@@ -1542,29 +2576,609 @@ public class MeasurementDaoTest {
.getCount());
}
+ @Test
+ public void testDeleteAppRecordsNotPresentForSources() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+
+ List<Source> sourceList = new ArrayList<>();
+ // Source registrant is still installed, record is not deleted.
+ sourceList.add(
+ new Source.Builder()
+ .setId("1")
+ .setEventId(new UnsignedLong(1L))
+ .setAppDestination(Uri.parse("android-app://installed-app-destination"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://installed-registrant"))
+ .setPublisher(Uri.parse("android-app://installed-registrant"))
+ .setStatus(Source.Status.ACTIVE)
+ .build());
+ // Source registrant is not installed, record is deleted.
+ sourceList.add(
+ new Source.Builder()
+ .setId("2")
+ .setEventId(new UnsignedLong(2L))
+ .setAppDestination(Uri.parse("android-app://installed-app-destination"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://not-installed-registrant"))
+ .setPublisher(Uri.parse("android-app://not-installed-registrant"))
+ .setStatus(Source.Status.ACTIVE)
+ .build());
+ // Source registrant is installed and status is active on not installed destination, record
+ // is not deleted.
+ sourceList.add(
+ new Source.Builder()
+ .setId("3")
+ .setEventId(new UnsignedLong(3L))
+ .setAppDestination(Uri.parse("android-app://not-installed-app-destination"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://installed-registrant"))
+ .setPublisher(Uri.parse("android-app://installed-registrant"))
+ .setStatus(Source.Status.ACTIVE)
+ .build());
+
+ // Source registrant is installed and status is ignored on not installed destination, record
+ // is deleted.
+ sourceList.add(
+ new Source.Builder()
+ .setId("4")
+ .setEventId(new UnsignedLong(4L))
+ .setAppDestination(Uri.parse("android-app://not-installed-app-destination"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://installed-registrant"))
+ .setPublisher(Uri.parse("android-app://installed-registrant"))
+ .setStatus(Source.Status.IGNORED)
+ .build());
+
+ // Source registrant is installed and status is ignored on installed destination, record is
+ // not deleted.
+ sourceList.add(
+ new Source.Builder()
+ .setId("5")
+ .setEventId(new UnsignedLong(5L))
+ .setAppDestination(Uri.parse("android-app://installed-app-destination"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://installed-registrant"))
+ .setPublisher(Uri.parse("android-app://installed-registrant"))
+ .setStatus(Source.Status.IGNORED)
+ .build());
+
+ sourceList.forEach(
+ source -> {
+ ContentValues values = new ContentValues();
+ values.put(SourceContract.ID, source.getId());
+ values.put(SourceContract.EVENT_ID, source.getEventId().toString());
+ values.put(
+ SourceContract.APP_DESTINATION, source.getAppDestination().toString());
+ values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId());
+ values.put(SourceContract.REGISTRANT, source.getRegistrant().toString());
+ values.put(SourceContract.PUBLISHER, source.getPublisher().toString());
+ values.put(SourceContract.STATUS, source.getStatus());
+ db.insert(SourceContract.TABLE, /* nullColumnHack */ null, values);
+ });
+
+ long count = DatabaseUtils.queryNumEntries(db, SourceContract.TABLE, /* selection */ null);
+ assertEquals(5, count);
+
+ List<Uri> installedUriList = new ArrayList<>();
+ installedUriList.add(Uri.parse("android-app://installed-registrant"));
+ installedUriList.add(Uri.parse("android-app://installed-app-destination"));
+
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.deleteAppRecordsNotPresent(
+ installedUriList)));
+
+ count = DatabaseUtils.queryNumEntries(db, SourceContract.TABLE, /* selection */ null);
+ assertEquals(3, count);
+
+ Cursor cursor =
+ db.query(
+ SourceContract.TABLE,
+ /* columns */ null,
+ /* selection */ null,
+ /* selectionArgs */ null,
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderBy */ null);
+ while (cursor.moveToNext()) {
+ Source source = SqliteObjectMapper.constructSourceFromCursor(cursor);
+ assertThat(Arrays.asList("1", "3", "5")).contains(source.getId());
+ }
+ }
+
+ @Test
+ public void testDeleteAppRecordsNotPresentForTriggers() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ List<Trigger> triggerList = new ArrayList<>();
+ // Trigger registrant is still installed, record will not be deleted.
+ triggerList.add(
+ new Trigger.Builder()
+ .setId("1")
+ .setAttributionDestination(
+ Uri.parse("android-app://attribution-destination"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://installed-registrant"))
+ .build());
+
+ // Trigger registrant is not installed, record will be deleted.
+ triggerList.add(
+ new Trigger.Builder()
+ .setId("2")
+ .setAttributionDestination(
+ Uri.parse("android-app://attribution-destination"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://not-installed-registrant"))
+ .build());
+
+ triggerList.forEach(
+ trigger -> {
+ ContentValues values = new ContentValues();
+ values.put(TriggerContract.ID, trigger.getId());
+ values.put(
+ TriggerContract.ATTRIBUTION_DESTINATION,
+ trigger.getAttributionDestination().toString());
+ values.put(TriggerContract.ENROLLMENT_ID, trigger.getEnrollmentId());
+ values.put(TriggerContract.REGISTRANT, trigger.getRegistrant().toString());
+ db.insert(TriggerContract.TABLE, /* nullColumnHack */ null, values);
+ });
+
+ long count = DatabaseUtils.queryNumEntries(db, TriggerContract.TABLE, /* selection */ null);
+ assertEquals(2, count);
+
+ List<Uri> installedUriList = new ArrayList<>();
+ installedUriList.add(Uri.parse("android-app://installed-registrant"));
+
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.deleteAppRecordsNotPresent(
+ installedUriList)));
+
+ count = DatabaseUtils.queryNumEntries(db, TriggerContract.TABLE, /* selection */ null);
+ assertEquals(1, count);
+
+ Cursor cursor =
+ db.query(
+ TriggerContract.TABLE,
+ /* columns */ null,
+ /* selection */ null,
+ /* selectionArgs */ null,
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderBy */ null);
+ while (cursor.moveToNext()) {
+ Trigger trigger = SqliteObjectMapper.constructTriggerFromCursor(cursor);
+ assertEquals("1", trigger.getId());
+ }
+ }
+
+ @Test
+ public void testDeleteAppRecordsNotPresentForEventReports() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ List<EventReport> eventReportList = new ArrayList<>();
+ // Event report attribution destination is still installed, record will not be deleted.
+ eventReportList.add(
+ new EventReport.Builder()
+ .setId("1")
+ .setAttributionDestination(
+ Uri.parse("android-app://installed-attribution-destination"))
+ .build());
+ // Event report attribution destination is not installed, record will be deleted.
+ eventReportList.add(
+ new EventReport.Builder()
+ .setId("2")
+ .setAttributionDestination(
+ Uri.parse("android-app://not-installed-attribution-destination"))
+ .build());
+ eventReportList.forEach(
+ eventReport -> {
+ ContentValues values = new ContentValues();
+ values.put(EventReportContract.ID, eventReport.getId());
+ values.put(
+ EventReportContract.ATTRIBUTION_DESTINATION,
+ eventReport.getAttributionDestination().toString());
+ db.insert(EventReportContract.TABLE, /* nullColumnHack */ null, values);
+ });
+
+ long count =
+ DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null);
+ assertEquals(2, count);
+
+ List<Uri> installedUriList = new ArrayList<>();
+ installedUriList.add(Uri.parse("android-app://installed-attribution-destination"));
+
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.deleteAppRecordsNotPresent(
+ installedUriList)));
+
+ count = DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null);
+ assertEquals(1, count);
+
+ Cursor cursor =
+ db.query(
+ EventReportContract.TABLE,
+ /* columns */ null,
+ /* selection */ null,
+ /* selectionArgs */ null,
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderBy */ null);
+ while (cursor.moveToNext()) {
+ EventReport eventReport = SqliteObjectMapper.constructEventReportFromCursor(cursor);
+ assertEquals("1", eventReport.getId());
+ }
+ }
+
+ @Test
+ public void testDeleteAppRecordsNotPresentForAggregateReports() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ List<AggregateReport> aggregateReportList = new ArrayList<>();
+ // Aggregate report attribution destination and publisher is still installed, record will
+ // not be deleted.
+ aggregateReportList.add(
+ new AggregateReport.Builder()
+ .setId("1")
+ .setAttributionDestination(
+ Uri.parse("android-app://installed-attribution-destination"))
+ .setPublisher(Uri.parse("android-app://installed-publisher"))
+ .build());
+ // Aggregate report attribution destination is not installed, record will be deleted.
+ aggregateReportList.add(
+ new AggregateReport.Builder()
+ .setId("2")
+ .setAttributionDestination(
+ Uri.parse("android-app://not-installed-attribution-destination"))
+ .setPublisher(Uri.parse("android-app://installed-publisher"))
+ .build());
+ // Aggregate report publisher is not installed, record will be deleted.
+ aggregateReportList.add(
+ new AggregateReport.Builder()
+ .setId("3")
+ .setAttributionDestination(
+ Uri.parse("android-app://installed-attribution-destination"))
+ .setPublisher(Uri.parse("android-app://not-installed-publisher"))
+ .build());
+ aggregateReportList.forEach(
+ aggregateReport -> {
+ ContentValues values = new ContentValues();
+ values.put(MeasurementTables.AggregateReport.ID, aggregateReport.getId());
+ values.put(
+ MeasurementTables.AggregateReport.ATTRIBUTION_DESTINATION,
+ aggregateReport.getAttributionDestination().toString());
+ values.put(
+ MeasurementTables.AggregateReport.PUBLISHER,
+ aggregateReport.getPublisher().toString());
+ db.insert(
+ MeasurementTables.AggregateReport.TABLE, /* nullColumnHack */
+ null,
+ values);
+ });
+
+ long count =
+ DatabaseUtils.queryNumEntries(
+ db, MeasurementTables.AggregateReport.TABLE, /* selection */ null);
+ assertEquals(3, count);
+
+ List<Uri> installedUriList = new ArrayList<>();
+ installedUriList.add(Uri.parse("android-app://installed-attribution-destination"));
+ installedUriList.add(Uri.parse("android-app://installed-publisher"));
+
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.deleteAppRecordsNotPresent(
+ installedUriList)));
+
+ count =
+ DatabaseUtils.queryNumEntries(
+ db, MeasurementTables.AggregateReport.TABLE, /* selection */ null);
+ assertEquals(1, count);
+
+ Cursor cursor =
+ db.query(
+ MeasurementTables.AggregateReport.TABLE,
+ /* columns */ null,
+ /* selection */ null,
+ /* selectionArgs */ null,
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderBy */ null);
+ while (cursor.moveToNext()) {
+ AggregateReport aggregateReport = SqliteObjectMapper.constructAggregateReport(cursor);
+ assertEquals("1", aggregateReport.getId());
+ }
+ }
+
+ @Test
+ public void testDeleteAppRecordsNotPresentForAttributions() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ List<Attribution> attributionList = new ArrayList<>();
+ // Attribution has source site and destination site still installed, record will not be
+ // deleted.
+ attributionList.add(
+ new Attribution.Builder()
+ .setId("1")
+ .setSourceSite("android-app://installed-source-site")
+ .setSourceOrigin("android-app://installed-source-site")
+ .setDestinationSite("android-app://installed-destination-site")
+ .setDestinationOrigin("android-app://installed-destination-site")
+ .setRegistrant("android-app://installed-source-site")
+ .setEnrollmentId("enrollment-id")
+ .build());
+ // Attribution has source site not installed, record will be deleted.
+ attributionList.add(
+ new Attribution.Builder()
+ .setId("2")
+ .setSourceSite("android-app://not-installed-source-site")
+ .setSourceOrigin("android-app://not-installed-source-site")
+ .setDestinationSite("android-app://installed-destination-site")
+ .setDestinationOrigin("android-app://installed-destination-site")
+ .setRegistrant("android-app://installed-source-site")
+ .setEnrollmentId("enrollment-id")
+ .build());
+ // Attribution has destination site not installed, record will be deleted.
+ attributionList.add(
+ new Attribution.Builder()
+ .setId("3")
+ .setSourceSite("android-app://installed-source-site")
+ .setSourceOrigin("android-app://installed-source-site")
+ .setDestinationSite("android-app://not-installed-destination-site")
+ .setDestinationOrigin("android-app://not-installed-destination-site")
+ .setRegistrant("android-app://installed-source-site")
+ .setEnrollmentId("enrollment-id")
+ .build());
+ attributionList.forEach(
+ attribution -> {
+ ContentValues values = new ContentValues();
+ values.put(AttributionContract.ID, attribution.getId());
+ values.put(AttributionContract.SOURCE_SITE, attribution.getSourceSite());
+ values.put(AttributionContract.SOURCE_ORIGIN, attribution.getSourceOrigin());
+ values.put(
+ AttributionContract.DESTINATION_SITE, attribution.getDestinationSite());
+ values.put(
+ AttributionContract.DESTINATION_ORIGIN,
+ attribution.getDestinationOrigin());
+ values.put(AttributionContract.REGISTRANT, attribution.getRegistrant());
+ values.put(AttributionContract.ENROLLMENT_ID, attribution.getEnrollmentId());
+ db.insert(AttributionContract.TABLE, /* nullColumnHack */ null, values);
+ });
+
+ long count =
+ DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE, /* selection */ null);
+ assertEquals(3, count);
+
+ List<Uri> installedUriList = new ArrayList<>();
+ installedUriList.add(Uri.parse("android-app://installed-source-site"));
+ installedUriList.add(Uri.parse("android-app://installed-destination-site"));
+
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.deleteAppRecordsNotPresent(
+ installedUriList)));
+
+ count = DatabaseUtils.queryNumEntries(db, AttributionContract.TABLE, /* selection */ null);
+ assertEquals(1, count);
+
+ Cursor cursor =
+ db.query(
+ AttributionContract.TABLE,
+ /* columns */ null,
+ /* selection */ null,
+ /* selectionArgs */ null,
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderBy */ null);
+ while (cursor.moveToNext()) {
+ Attribution attribution = constructAttributionFromCursor(cursor);
+ assertEquals("1", attribution.getId());
+ }
+ }
+
+ @Test
+ public void testDeleteAppRecordsNotPresentForEventReportsFromSources() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+
+ List<Source> sourceList = new ArrayList<>();
+ sourceList.add(
+ new Source.Builder() // deleted
+ .setId("1")
+ .setEventId(new UnsignedLong(1L))
+ .setAppDestination(Uri.parse("android-app://app-destination-1"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://uninstalled-app"))
+ .setPublisher(Uri.parse("android-app://uninstalled-app"))
+ .build());
+ sourceList.add(
+ new Source.Builder()
+ .setId("2")
+ .setEventId(new UnsignedLong(2L))
+ .setAppDestination(Uri.parse("android-app://app-destination-2"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://installed-app"))
+ .setPublisher(Uri.parse("android-app://installed-app"))
+ .build());
+ sourceList.forEach(source -> AbstractDbIntegrationTest.insertToDb(source, db));
+
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("1")
+ .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
+ .setRegistrant(Uri.parse("android-app://installed-app"))
+ .build();
+ AbstractDbIntegrationTest.insertToDb(trigger, db);
+
+ List<EventReport> reportList = new ArrayList<>();
+ reportList.add(
+ new EventReport.Builder()
+ .setId("1") // deleted
+ .setSourceEventId(new UnsignedLong(1L))
+ .setAttributionDestination(Uri.parse("android-app://app-destination-1"))
+ .setEnrollmentId("enrollment-id")
+ .setTriggerData(new UnsignedLong(5L))
+ .setSourceId(sourceList.get(0).getId())
+ .setTriggerId(trigger.getId())
+ .setSourceType(sourceList.get(0).getSourceType())
+ .build());
+ reportList.add(
+ new EventReport.Builder()
+ .setId("2")
+ .setSourceEventId(new UnsignedLong(2L))
+ .setAttributionDestination(Uri.parse("android-app://app-destination-2"))
+ .setEnrollmentId("enrollment-id")
+ .setTriggerData(new UnsignedLong(5L))
+ .setSourceId(sourceList.get(1).getId())
+ .setTriggerId(trigger.getId())
+ .setSourceType(sourceList.get(1).getSourceType())
+ .build());
+ reportList.forEach(report -> AbstractDbIntegrationTest.insertToDb(report, db));
+
+ long count =
+ DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null);
+ assertEquals(2, count);
+
+ List<Uri> installedUriList = new ArrayList<>();
+ installedUriList.add(Uri.parse("android-app://installed-app"));
+ installedUriList.add(Uri.parse("android-app://app-destination-1"));
+ installedUriList.add(Uri.parse("android-app://app-destination-2"));
+
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.deleteAppRecordsNotPresent(
+ installedUriList)));
+
+ count = DatabaseUtils.queryNumEntries(db, EventReportContract.TABLE, /* selection */ null);
+ assertEquals(1, count);
+
+ Cursor cursor =
+ db.query(
+ EventReportContract.TABLE,
+ /* columns */ null,
+ /* selection */ null,
+ /* selectionArgs */ null,
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderBy */ null);
+ while (cursor.moveToNext()) {
+ EventReport eventReport = SqliteObjectMapper.constructEventReportFromCursor(cursor);
+ assertEquals("2", eventReport.getId());
+ }
+ }
+
+ @Test
+ public void testDeleteAppRecordsNotPresentForLargeAppList() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ List<Source> sourceList = new ArrayList<>();
+
+ int limit = 5000;
+ sourceList.add(
+ new Source.Builder()
+ .setId("1")
+ .setEventId(new UnsignedLong(1L))
+ .setAppDestination(Uri.parse("android-app://app-destination-1"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://installed-app" + limit))
+ .setPublisher(Uri.parse("android-app://installed-app" + limit))
+ .build());
+ sourceList.add(
+ new Source.Builder()
+ .setId("2")
+ .setEventId(new UnsignedLong(1L))
+ .setAppDestination(Uri.parse("android-app://app-destination-1"))
+ .setEnrollmentId("enrollment-id")
+ .setRegistrant(Uri.parse("android-app://installed-app" + (limit + 1)))
+ .setPublisher(Uri.parse("android-app://installed-app" + (limit + 1)))
+ .build());
+ sourceList.forEach(
+ source -> {
+ ContentValues values = new ContentValues();
+ values.put(SourceContract.ID, source.getId());
+ values.put(SourceContract.EVENT_ID, source.getEventId().toString());
+ values.put(
+ SourceContract.APP_DESTINATION, source.getAppDestination().toString());
+ values.put(SourceContract.ENROLLMENT_ID, source.getEnrollmentId());
+ values.put(SourceContract.REGISTRANT, source.getRegistrant().toString());
+ values.put(SourceContract.PUBLISHER, source.getPublisher().toString());
+ db.insert(SourceContract.TABLE, /* nullColumnHack */ null, values);
+ });
+
+ long count = DatabaseUtils.queryNumEntries(db, SourceContract.TABLE, /* selection */ null);
+ assertEquals(2, count);
+
+ List<Uri> installedUriList = new ArrayList<>();
+ for (int i = 0; i <= limit; i++) {
+ installedUriList.add(Uri.parse("android-app://installed-app" + i));
+ }
+
+ assertTrue(
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ measurementDao ->
+ measurementDao.deleteAppRecordsNotPresent(
+ installedUriList)));
+
+ count = DatabaseUtils.queryNumEntries(db, SourceContract.TABLE, /* selection */ null);
+ assertEquals(1, count);
+ }
+
+ private static List<Source> getSourcesWithDifferentDestinations(
+ int numSources,
+ boolean hasAppDestination,
+ boolean hasWebDestination,
+ long eventTime,
+ Uri publisher,
+ String enrollmentId,
+ @Source.Status int sourceStatus) {
+ long expiryTime = eventTime + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
+ return getSourcesWithDifferentDestinations(
+ numSources,
+ hasAppDestination,
+ hasWebDestination,
+ eventTime,
+ expiryTime,
+ publisher,
+ enrollmentId,
+ sourceStatus);
+ }
+
private static List<Source> getSourcesWithDifferentDestinations(
int numSources,
boolean hasAppDestination,
boolean hasWebDestination,
long eventTime,
+ long expiryTime,
Uri publisher,
String enrollmentId,
@Source.Status int sourceStatus) {
List<Source> sources = new ArrayList<>();
for (int i = 0; i < numSources; i++) {
- Source.Builder sourceBuilder = new Source.Builder()
- .setEventTime(eventTime)
- .setPublisher(publisher)
- .setEnrollmentId(enrollmentId)
- .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
- .setStatus(sourceStatus);
+ Source.Builder sourceBuilder =
+ new Source.Builder()
+ .setEventId(new UnsignedLong(0L))
+ .setEventTime(eventTime)
+ .setExpiryTime(expiryTime)
+ .setPublisher(publisher)
+ .setEnrollmentId(enrollmentId)
+ .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
+ .setStatus(sourceStatus);
if (hasAppDestination) {
- sourceBuilder.setAppDestination(Uri.parse(
- "android-app://app-destination-" + String.valueOf(i)));
+ sourceBuilder.setAppDestination(
+ Uri.parse("android-app://app-destination-" + String.valueOf(i)));
}
if (hasWebDestination) {
- sourceBuilder.setWebDestination(Uri.parse(
- "https://web-destination-" + String.valueOf(i) + ".com"));
+ sourceBuilder.setWebDestination(
+ Uri.parse("https://web-destination-" + String.valueOf(i) + ".com"));
}
sources.add(sourceBuilder.build());
}
@@ -1578,16 +3192,39 @@ public class MeasurementDaoTest {
long eventTime,
Uri publisher,
@Source.Status int sourceStatus) {
+ long expiryTime = eventTime + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
+ return getSourcesWithDifferentEnrollments(
+ numSources,
+ appDestination,
+ webDestination,
+ eventTime,
+ expiryTime,
+ publisher,
+ sourceStatus);
+ }
+
+ private static List<Source> getSourcesWithDifferentEnrollments(
+ int numSources,
+ Uri appDestination,
+ Uri webDestination,
+ long eventTime,
+ long expiryTime,
+ Uri publisher,
+ @Source.Status int sourceStatus) {
List<Source> sources = new ArrayList<>();
for (int i = 0; i < numSources; i++) {
- Source.Builder sourceBuilder = new Source.Builder()
- .setEventTime(eventTime)
- .setPublisher(publisher)
- .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
- .setStatus(sourceStatus)
- .setAppDestination(appDestination)
- .setWebDestination(webDestination)
- .setEnrollmentId("enrollment-id-" + i);
+ Source.Builder sourceBuilder =
+ new Source.Builder()
+ .setEventId(new UnsignedLong(0L))
+ .setEventTime(eventTime)
+ .setExpiryTime(expiryTime)
+ .setPublisher(publisher)
+ .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
+ .setStatus(sourceStatus)
+ .setAppDestination(appDestination)
+ .setWebDestination(webDestination)
+ .setEnrollmentId("enrollment-id-" + i);
sources.add(sourceBuilder.build());
}
return sources;
@@ -1623,8 +3260,41 @@ public class MeasurementDaoTest {
values.put(AttributionContract.DESTINATION_SITE, attribution.getDestinationSite());
values.put(AttributionContract.ENROLLMENT_ID, attribution.getEnrollmentId());
values.put(AttributionContract.TRIGGER_TIME, attribution.getTriggerTime());
+ values.put(AttributionContract.SOURCE_ID, attribution.getSourceId());
+ values.put(AttributionContract.TRIGGER_ID, attribution.getTriggerId());
long row = db.insert("msmt_attribution", null, values);
- Assert.assertNotEquals("Attribution insertion failed", -1, row);
+ assertNotEquals("Attribution insertion failed", -1, row);
+ }
+
+ private static Attribution createAttributionWithSourceAndTriggerIds(
+ String attributionId, String sourceId, String triggerId) {
+ return new Attribution.Builder()
+ .setId(attributionId)
+ .setTriggerTime(0L)
+ .setSourceSite("android-app://source.app")
+ .setSourceOrigin("android-app://source.app")
+ .setDestinationSite("android-app://destination.app")
+ .setDestinationOrigin("android-app://destination.app")
+ .setEnrollmentId("enrollment-id-")
+ .setRegistrant("android-app://registrant.app")
+ .setSourceId(sourceId)
+ .setTriggerId(triggerId)
+ .build();
+ }
+
+ private static Attribution createAttributionWithSourceAndTriggerIds(
+ String sourceId, String triggerId) {
+ return new Attribution.Builder()
+ .setTriggerTime(0L)
+ .setSourceSite("android-app://source.app")
+ .setSourceOrigin("android-app://source.app")
+ .setDestinationSite("android-app://destination.app")
+ .setDestinationOrigin("android-app://destination.app")
+ .setEnrollmentId("enrollment-id-")
+ .setRegistrant("android-app://registrant.app")
+ .setSourceId(sourceId)
+ .setTriggerId(triggerId)
+ .build();
}
// This is needed because MeasurementDao::insertSource inserts a default value for status.
@@ -1632,7 +3302,7 @@ public class MeasurementDaoTest {
SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
ContentValues values = new ContentValues();
values.put(SourceContract.ID, UUID.randomUUID().toString());
- values.put(SourceContract.EVENT_ID, source.getEventId());
+ values.put(SourceContract.EVENT_ID, source.getEventId().getValue());
values.put(SourceContract.PUBLISHER, source.getPublisher().toString());
values.put(SourceContract.PUBLISHER_TYPE, source.getPublisherType());
values.put(
@@ -1650,32 +3320,869 @@ public class MeasurementDaoTest {
values.put(SourceContract.INSTALL_COOLDOWN_WINDOW, source.getInstallCooldownWindow());
values.put(SourceContract.ATTRIBUTION_MODE, source.getAttributionMode());
values.put(SourceContract.AGGREGATE_SOURCE, source.getAggregateSource());
- values.put(SourceContract.FILTER_DATA, source.getAggregateFilterData());
+ values.put(SourceContract.FILTER_DATA, source.getFilterData());
values.put(SourceContract.AGGREGATE_CONTRIBUTIONS, 0);
- values.put(SourceContract.DEBUG_KEY, source.getDebugKey());
long row = db.insert("msmt_source", null, values);
- Assert.assertNotEquals("Source insertion failed", -1, row);
+ assertNotEquals("Source insertion failed", -1, row);
}
private static String getNullableUriString(Uri uri) {
return Optional.ofNullable(uri).map(Uri::toString).orElse(null);
}
+ /** Test that the AsyncRegistration is inserted correctly. */
+ @Test
+ public void testInsertAsyncRegistration() {
+ AsyncRegistration validAsyncRegistration =
+ AsyncRegistrationFixture.getValidAsyncRegistration();
+ String validAsyncRegistrationId = validAsyncRegistration.getId();
+
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertAsyncRegistration(validAsyncRegistration));
+
+ try (Cursor cursor =
+ DbHelper.getInstance(sContext)
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ MeasurementTables.AsyncRegistrationContract.ID + " = ? ",
+ new String[] {validAsyncRegistrationId},
+ null,
+ null,
+ null)) {
+
+ assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ assertNotNull(asyncRegistration);
+ assertNotNull(asyncRegistration.getId());
+ assertEquals(asyncRegistration.getId(), validAsyncRegistration.getId());
+ assertNotNull(asyncRegistration.getEnrollmentId());
+ assertEquals(
+ asyncRegistration.getEnrollmentId(), validAsyncRegistration.getEnrollmentId());
+ assertNotNull(asyncRegistration.getRegistrationUri());
+ assertNotNull(asyncRegistration.getTopOrigin());
+ assertEquals(asyncRegistration.getTopOrigin(), validAsyncRegistration.getTopOrigin());
+ assertNotNull(asyncRegistration.getRegistrant());
+ assertEquals(asyncRegistration.getRegistrant(), validAsyncRegistration.getRegistrant());
+ assertNotNull(asyncRegistration.getSourceType());
+ assertEquals(asyncRegistration.getSourceType(), validAsyncRegistration.getSourceType());
+ assertNotNull(asyncRegistration.getDebugKeyAllowed());
+ assertEquals(
+ asyncRegistration.getDebugKeyAllowed(),
+ validAsyncRegistration.getDebugKeyAllowed());
+ assertNotNull(asyncRegistration.getRetryCount());
+ assertEquals(asyncRegistration.getRetryCount(), validAsyncRegistration.getRetryCount());
+ assertNotNull(asyncRegistration.getRequestTime());
+ assertEquals(
+ asyncRegistration.getRequestTime(), validAsyncRegistration.getRequestTime());
+ assertNotNull(asyncRegistration.getOsDestination());
+ assertEquals(
+ asyncRegistration.getOsDestination(),
+ validAsyncRegistration.getOsDestination());
+ assertNotNull(asyncRegistration.getLastProcessingTime());
+ assertEquals(
+ asyncRegistration.getLastProcessingTime(),
+ validAsyncRegistration.getLastProcessingTime());
+ assertEquals(
+ asyncRegistration.getRedirectType(), validAsyncRegistration.getRedirectType());
+ assertEquals(
+ asyncRegistration.getRedirectCount(),
+ validAsyncRegistration.getRedirectCount());
+ assertNotNull(asyncRegistration.getRegistrationUri());
+ assertEquals(
+ asyncRegistration.getRegistrationUri(),
+ validAsyncRegistration.getRegistrationUri());
+ assertNotNull(asyncRegistration.getDebugKeyAllowed());
+ assertEquals(
+ asyncRegistration.getDebugKeyAllowed(),
+ validAsyncRegistration.getDebugKeyAllowed());
+ }
+ }
+
+ /** Test that records in AsyncRegistration queue are fetched properly. */
+ @Test
+ public void testFetchNextQueuedAsyncRegistration_validRetryLimit() {
+ AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
+ String asyncRegistrationId = asyncRegistration.getId();
+
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ (dao) -> {
+ AsyncRegistration fetchedAsyncRegistration =
+ dao.fetchNextQueuedAsyncRegistration(
+ (short) 1, new ArrayList<>());
+ assertNotNull(fetchedAsyncRegistration);
+ assertEquals(fetchedAsyncRegistration.getId(), asyncRegistrationId);
+ fetchedAsyncRegistration.incrementRetryCount();
+ dao.updateRetryCount(fetchedAsyncRegistration);
+ });
+
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ (dao) -> {
+ AsyncRegistration fetchedAsyncRegistration =
+ dao.fetchNextQueuedAsyncRegistration(
+ (short) 1, new ArrayList<>());
+ assertNull(fetchedAsyncRegistration);
+ });
+ }
+
+ /** Test that records in AsyncRegistration queue are fetched properly. */
+ @Test
+ public void testFetchNextQueuedAsyncRegistration_excludeByEnrollmentId() {
+ AsyncRegistration firstAsyncRegistration =
+ AsyncRegistrationFixture.getValidAsyncRegistration();
+ AsyncRegistration secondAsyncRegistration =
+ AsyncRegistrationFixture.getValidAsyncRegistration();
+ String firstAsyncRegistrationEnrollmentId = firstAsyncRegistration.getEnrollmentId();
+ String secondAsyncRegistrationEnrollmentId = secondAsyncRegistration.getEnrollmentId();
+
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertAsyncRegistration(firstAsyncRegistration));
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertAsyncRegistration(secondAsyncRegistration));
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ (dao) -> {
+ ArrayList<String> excludedEnrollmentIds = new ArrayList<>();
+ excludedEnrollmentIds.add(firstAsyncRegistrationEnrollmentId);
+ AsyncRegistration fetchedAsyncRegistration =
+ dao.fetchNextQueuedAsyncRegistration(
+ (short) 1, excludedEnrollmentIds);
+ assertNotNull(fetchedAsyncRegistration);
+ assertEquals(
+ fetchedAsyncRegistration.getEnrollmentId(),
+ secondAsyncRegistrationEnrollmentId);
+ });
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ (dao) -> {
+ ArrayList<String> excludedEnrollmentIds = new ArrayList<>();
+ excludedEnrollmentIds.add(firstAsyncRegistrationEnrollmentId);
+ excludedEnrollmentIds.add(secondAsyncRegistrationEnrollmentId);
+ AsyncRegistration fetchedAsyncRegistration =
+ dao.fetchNextQueuedAsyncRegistration(
+ (short) 1, excludedEnrollmentIds);
+ assertNull(fetchedAsyncRegistration);
+ });
+ }
+
+ /** Test that AsyncRegistration is deleted correctly. */
+ @Test
+ public void testDeleteAsyncRegistration() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
+ String asyncRegistrationID = asyncRegistration.getId();
+
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
+ try (Cursor cursor =
+ DbHelper.getInstance(sContext)
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ MeasurementTables.AsyncRegistrationContract.ID + " = ? ",
+ new String[] {asyncRegistration.getId().toString()},
+ null,
+ null,
+ null)) {
+ assertTrue(cursor.moveToNext());
+ AsyncRegistration updateAsyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ assertNotNull(updateAsyncRegistration);
+ }
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.deleteAsyncRegistration(asyncRegistration.getId()));
+
+ db.query(
+ /* table */ MeasurementTables.AsyncRegistrationContract.TABLE,
+ /* columns */ null,
+ /* selection */ MeasurementTables.AsyncRegistrationContract.ID + " = ? ",
+ /* selectionArgs */ new String[] {asyncRegistrationID.toString()},
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderedBy */ null);
+
+ assertThat(
+ db.query(
+ /* table */ MeasurementTables.AsyncRegistrationContract
+ .TABLE,
+ /* columns */ null,
+ /* selection */ MeasurementTables.AsyncRegistrationContract
+ .ID
+ + " = ? ",
+ /* selectionArgs */ new String[] {
+ asyncRegistrationID.toString()
+ },
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderedBy */ null)
+ .getCount())
+ .isEqualTo(0);
+ }
+
+ /** Test that retry count in AsyncRegistration is updated correctly. */
+ @Test
+ public void testUpdateAsyncRegistrationRetryCount() {
+ AsyncRegistration asyncRegistration = AsyncRegistrationFixture.getValidAsyncRegistration();
+ String asyncRegistrationId = asyncRegistration.getId();
+ long originalRetryCount = asyncRegistration.getRetryCount();
+
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertAsyncRegistration(asyncRegistration));
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ (dao) -> {
+ asyncRegistration.incrementRetryCount();
+ dao.updateRetryCount(asyncRegistration);
+ });
+
+ try (Cursor cursor =
+ DbHelper.getInstance(sContext)
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ MeasurementTables.AsyncRegistrationContract.ID + " = ? ",
+ new String[] {asyncRegistrationId},
+ null,
+ null,
+ null)) {
+ assertTrue(cursor.moveToNext());
+ AsyncRegistration updateAsyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ assertNotNull(updateAsyncRegistration);
+ assertTrue(updateAsyncRegistration.getRetryCount() == originalRetryCount + 1);
+ }
+ }
+
+ @Test
+ public void getSource_fetchesMatchingSourceFromDb() {
+ // Setup - insert 2 sources with different IDs
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ String sourceId1 = "source1";
+ Source source1 = SourceFixture.getValidSourceBuilder().setId(sourceId1).build();
+ insertInDb(db, source1);
+ String sourceId2 = "source2";
+ Source source2 = SourceFixture.getValidSourceBuilder().setId(sourceId2).build();
+ insertInDb(db, source2);
+
+ // Execution
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ (dao) -> {
+ assertEquals(source1, dao.getSource(sourceId1));
+ assertEquals(source2, dao.getSource(sourceId2));
+ });
+ }
+
+ @Test
+ public void fetchMatchingAggregateReports_returnsMatchingReports() {
+ // setup - create reports for 3*3 combinations of source and trigger
+ List<Source> sources =
+ Arrays.asList(
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(1L))
+ .setId("source1")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(2L))
+ .setId("source2")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(3L))
+ .setId("source3")
+ .build());
+ List<Trigger> triggers =
+ Arrays.asList(
+ TriggerFixture.getValidTriggerBuilder().setId("trigger1").build(),
+ TriggerFixture.getValidTriggerBuilder().setId("trigger2").build(),
+ TriggerFixture.getValidTriggerBuilder().setId("trigger3").build());
+ List<AggregateReport> reports =
+ ImmutableList.of(
+ createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(0)),
+ createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(1)),
+ createAggregateReportForSourceAndTrigger(sources.get(0), triggers.get(2)),
+ createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(0)),
+ createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(1)),
+ createAggregateReportForSourceAndTrigger(sources.get(1), triggers.get(2)),
+ createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(0)),
+ createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(1)),
+ createAggregateReportForSourceAndTrigger(sources.get(2), triggers.get(2)));
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).getWritableDatabase();
+ sources.forEach(source -> AbstractDbIntegrationTest.insertToDb(source, db));
+ triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
+ reports.forEach(
+ report ->
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertAggregateReport(report)));
+
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ (dao) -> {
+ // Execution
+ List<AggregateReport> aggregateReports =
+ dao.fetchMatchingAggregateReports(
+ Arrays.asList(
+ sources.get(1).getId(), "nonMatchingSource"),
+ Arrays.asList(
+ triggers.get(2).getId(), "nonMatchingTrigger"));
+ assertEquals(5, aggregateReports.size());
+
+ aggregateReports =
+ dao.fetchMatchingAggregateReports(
+ Arrays.asList(
+ sources.get(0).getId(), sources.get(1).getId()),
+ Collections.emptyList());
+ assertEquals(6, aggregateReports.size());
+
+ aggregateReports =
+ dao.fetchMatchingAggregateReports(
+ Collections.emptyList(),
+ Arrays.asList(
+ triggers.get(0).getId(),
+ triggers.get(2).getId()));
+ assertEquals(6, aggregateReports.size());
+
+ aggregateReports =
+ dao.fetchMatchingAggregateReports(
+ Arrays.asList(
+ sources.get(0).getId(),
+ sources.get(1).getId(),
+ sources.get(2).getId()),
+ Arrays.asList(
+ triggers.get(0).getId(),
+ triggers.get(1).getId(),
+ triggers.get(2).getId()));
+ assertEquals(9, aggregateReports.size());
+ });
+ }
+
+ @Test
+ public void fetchMatchingEventReports_returnsMatchingReports() throws JSONException {
+ // setup - create reports for 3*3 combinations of source and trigger
+ List<Source> sources =
+ Arrays.asList(
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(1L))
+ .setId("source1")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(2L))
+ .setId("source2")
+ .build(),
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(3L))
+ .setId("source3")
+ .build());
+ List<Trigger> triggers =
+ Arrays.asList(
+ TriggerFixture.getValidTriggerBuilder()
+ .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
+ .setId("trigger1")
+ .build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
+ .setId("trigger2")
+ .build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setEventTriggers(TriggerFixture.ValidTriggerParams.EVENT_TRIGGERS)
+ .setId("trigger3")
+ .build());
+ List<EventReport> reports =
+ ImmutableList.of(
+ createEventReportForSourceAndTrigger(sources.get(0), triggers.get(0)),
+ createEventReportForSourceAndTrigger(sources.get(0), triggers.get(1)),
+ createEventReportForSourceAndTrigger(sources.get(0), triggers.get(2)),
+ createEventReportForSourceAndTrigger(sources.get(1), triggers.get(0)),
+ createEventReportForSourceAndTrigger(sources.get(1), triggers.get(1)),
+ createEventReportForSourceAndTrigger(sources.get(1), triggers.get(2)),
+ createEventReportForSourceAndTrigger(sources.get(2), triggers.get(0)),
+ createEventReportForSourceAndTrigger(sources.get(2), triggers.get(1)),
+ createEventReportForSourceAndTrigger(sources.get(2), triggers.get(2)));
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).getWritableDatabase();
+ sources.forEach(source -> AbstractDbIntegrationTest.insertToDb(source, db));
+ triggers.forEach(trigger -> AbstractDbIntegrationTest.insertToDb(trigger, db));
+ reports.forEach(
+ report ->
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction((dao) -> dao.insertEventReport(report)));
+
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ (dao) -> {
+ // Execution
+ List<EventReport> eventReports =
+ dao.fetchMatchingEventReports(
+ Arrays.asList(
+ sources.get(1).getId(), "nonMatchingSource"),
+ Arrays.asList(
+ triggers.get(2).getId(), "nonMatchingTrigger"));
+ assertEquals(5, eventReports.size());
+
+ eventReports =
+ dao.fetchMatchingEventReports(
+ Arrays.asList(
+ sources.get(0).getId(), sources.get(1).getId()),
+ Collections.emptyList());
+ assertEquals(6, eventReports.size());
+
+ eventReports =
+ dao.fetchMatchingEventReports(
+ Collections.emptyList(),
+ Arrays.asList(
+ triggers.get(0).getId(),
+ triggers.get(2).getId()));
+ assertEquals(6, eventReports.size());
+
+ eventReports =
+ dao.fetchMatchingEventReports(
+ Arrays.asList(
+ sources.get(0).getId(),
+ sources.get(1).getId(),
+ sources.get(2).getId()),
+ Arrays.asList(
+ triggers.get(0).getId(),
+ triggers.get(1).getId(),
+ triggers.get(2).getId()));
+ assertEquals(9, eventReports.size());
+ });
+ }
+
+ @Test
+ public void fetchMatchingSources_bringsMatchingSources() {
+ // Setup
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(1L))
+ .setPublisher(Uri.parse("https://subdomain1.site1.com"))
+ .setEventTime(5000)
+ .setRegistrant(Uri.parse("android-app://com.registrant1"))
+ .setId("source1")
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(2L))
+ .setPublisher(Uri.parse("https://subdomain1.site1.com"))
+ .setEventTime(10000)
+ .setRegistrant(Uri.parse("android-app://com.registrant1"))
+ .setId("source2")
+ .build();
+ Source source3 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(3L))
+ .setPublisher(Uri.parse("https://subdomain2.site1.com"))
+ .setEventTime(15000)
+ .setRegistrant(Uri.parse("android-app://com.registrant1"))
+ .setId("source3")
+ .build();
+ Source source4 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(4L))
+ .setPublisher(Uri.parse("https://subdomain2.site2.com"))
+ .setEventTime(15000)
+ .setRegistrant(Uri.parse("android-app://com.registrant1"))
+ .setId("source4")
+ .build();
+ Source source5 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(5L))
+ .setPublisher(Uri.parse("https://subdomain2.site1.com"))
+ .setEventTime(20000)
+ .setRegistrant(Uri.parse("android-app://com.registrant2"))
+ .setId("source5")
+ .build();
+ List<Source> sources = List.of(source1, source2, source3, source4, source5);
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).getWritableDatabase();
+ sources.forEach(source -> insertInDb(db, source));
+
+ // Execution
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ dao -> {
+ // --- DELETE behaviour ---
+ // 1,2,3 & 4 are match registrant1
+ List<String> actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(),
+ List.of(),
+ DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(4, actualSources.size());
+
+ // 1 & 2 match registrant1 and "https://subdomain1.site1.com" publisher
+ // origin
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(
+ Uri.parse(
+ "https://subdomain1.site1.com")),
+ List.of(), DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(2, actualSources.size());
+
+ // Only 2 matches registrant1 and "https://subdomain1.site1.com"
+ // publisher origin within
+ // the range
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(8000),
+ Instant.ofEpochMilli(50000),
+ List.of(
+ Uri.parse(
+ "https://subdomain1.site1.com")),
+ List.of(), DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(1, actualSources.size());
+
+ // 1,2 & 3 matches registrant1 and "https://site1.com" publisher origin
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(),
+ List.of(Uri.parse("https://site1.com")),
+ DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(3, actualSources.size());
+
+ // 3 matches origin and 4 matches domain URI
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(10000),
+ Instant.ofEpochMilli(20000),
+ List.of(Uri.parse("https://subdomain2.site1.com")),
+ List.of(Uri.parse("https://site2.com")),
+ DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(2, actualSources.size());
+
+ // --- PRESERVE (anti-match exception registrant) behaviour ---
+ // all registrant1 registrant based sources are matched to returns 0 as
+ // anti-match
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(),
+ List.of(),
+ DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(0, actualSources.size());
+
+ // 3 & 4 match registrant1 and don't match
+ // "https://subdomain1.site1.com" publisher origin
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(
+ Uri.parse(
+ "https://subdomain1.site1.com")),
+ List.of(), DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(2, actualSources.size());
+
+ // 3 & 4 match registrant1, in range and don't match
+ // "https://subdomain1.site1.com"
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(8000),
+ Instant.ofEpochMilli(50000),
+ List.of(
+ Uri.parse(
+ "https://subdomain1.site1.com")),
+ List.of(), DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(2, actualSources.size());
+
+ // Only 4 matches registrant1, in range and don't match
+ // "https://site1.com"
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(),
+ List.of(Uri.parse("https://site1.com")),
+ DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(1, actualSources.size());
+
+ // only 2 is registrant1 based, in range and does not match either
+ // site2.com or subdomain2.site1.com
+ actualSources =
+ dao.fetchMatchingSources(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(10000),
+ Instant.ofEpochMilli(20000),
+ List.of(Uri.parse("https://subdomain2.site1.com")),
+ List.of(Uri.parse("https://site2.com")),
+ DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(1, actualSources.size());
+ });
+ }
+
+ @Test
+ public void fetchMatchingTriggers_bringsMatchingTriggers() {
+ // Setup
+ Trigger trigger1 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(Uri.parse("https://subdomain1.site1.com"))
+ .setTriggerTime(5000)
+ .setRegistrant(Uri.parse("android-app://com.registrant1"))
+ .setId("trigger1")
+ .build();
+ Trigger trigger2 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(Uri.parse("https://subdomain1.site1.com"))
+ .setTriggerTime(10000)
+ .setRegistrant(Uri.parse("android-app://com.registrant1"))
+ .setId("trigger2")
+ .build();
+ Trigger trigger3 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(Uri.parse("https://subdomain2.site1.com"))
+ .setTriggerTime(15000)
+ .setRegistrant(Uri.parse("android-app://com.registrant1"))
+ .setId("trigger3")
+ .build();
+ Trigger trigger4 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(Uri.parse("https://subdomain2.site2.com"))
+ .setTriggerTime(15000)
+ .setRegistrant(Uri.parse("android-app://com.registrant1"))
+ .setId("trigger4")
+ .build();
+ Trigger trigger5 =
+ TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(Uri.parse("https://subdomain2.site1.com"))
+ .setTriggerTime(20000)
+ .setRegistrant(Uri.parse("android-app://com.registrant2"))
+ .setId("trigger5")
+ .build();
+ List<Trigger> triggers = List.of(trigger1, trigger2, trigger3, trigger4, trigger5);
+
+ SQLiteDatabase db = DbHelper.getInstance(sContext).getWritableDatabase();
+ triggers.forEach(
+ trigger -> {
+ ContentValues values = new ContentValues();
+ values.put(TriggerContract.ID, trigger.getId());
+ values.put(
+ TriggerContract.ATTRIBUTION_DESTINATION,
+ trigger.getAttributionDestination().toString());
+ values.put(TriggerContract.TRIGGER_TIME, trigger.getTriggerTime());
+ values.put(TriggerContract.ENROLLMENT_ID, trigger.getEnrollmentId());
+ values.put(TriggerContract.REGISTRANT, trigger.getRegistrant().toString());
+ values.put(TriggerContract.STATUS, trigger.getStatus());
+ db.insert(TriggerContract.TABLE, /* nullColumnHack */ null, values);
+ });
+
+ // Execution
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransaction(
+ dao -> {
+ // --- DELETE behaviour ---
+ // 1,2,3 & 4 are match registrant1
+ List<String> actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(),
+ List.of(),
+ DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(4, actualSources.size());
+
+ // 1 & 2 match registrant1 and "https://subdomain1.site1.com" publisher
+ // origin
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(
+ Uri.parse(
+ "https://subdomain1.site1.com")),
+ List.of(), DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(2, actualSources.size());
+
+ // Only 2 matches registrant1 and "https://subdomain1.site1.com"
+ // publisher origin within
+ // the range
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(8000),
+ Instant.ofEpochMilli(50000),
+ List.of(
+ Uri.parse(
+ "https://subdomain1.site1.com")),
+ List.of(), DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(1, actualSources.size());
+
+ // 1,2 & 3 matches registrant1 and "https://site1.com" publisher origin
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(),
+ List.of(Uri.parse("https://site1.com")),
+ DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(3, actualSources.size());
+
+ // 3 matches origin and 4 matches domain URI
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(10000),
+ Instant.ofEpochMilli(20000),
+ List.of(Uri.parse("https://subdomain2.site1.com")),
+ List.of(Uri.parse("https://site2.com")),
+ DeletionRequest.MATCH_BEHAVIOR_DELETE);
+ assertEquals(2, actualSources.size());
+
+ // --- PRESERVE (anti-match exception registrant) behaviour ---
+ // all registrant1 registrant based sources are matched to returns 0 as
+ // anti-match
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(),
+ List.of(),
+ DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(0, actualSources.size());
+
+ // 3 & 4 match registrant1 and don't match
+ // "https://subdomain1.site1.com" publisher origin
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(
+ Uri.parse(
+ "https://subdomain1.site1.com")),
+ List.of(), DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(2, actualSources.size());
+
+ // 3 & 4 match registrant1, in range and don't match
+ // "https://subdomain1.site1.com"
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(8000),
+ Instant.ofEpochMilli(50000),
+ List.of(
+ Uri.parse(
+ "https://subdomain1.site1.com")),
+ List.of(), DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(2, actualSources.size());
+
+ // Only 4 matches registrant1, in range and don't match
+ // "https://site1.com"
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(0),
+ Instant.ofEpochMilli(50000),
+ List.of(),
+ List.of(Uri.parse("https://site1.com")),
+ DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(1, actualSources.size());
+
+ // only 2 is registrant1 based, in range and does not match either
+ // site2.com or subdomain2.site1.com
+ actualSources =
+ dao.fetchMatchingTriggers(
+ Uri.parse("android-app://com.registrant1"),
+ Instant.ofEpochMilli(10000),
+ Instant.ofEpochMilli(20000),
+ List.of(Uri.parse("https://subdomain2.site1.com")),
+ List.of(Uri.parse("https://site2.com")),
+ DeletionRequest.MATCH_BEHAVIOR_PRESERVE);
+ assertEquals(1, actualSources.size());
+ });
+ }
+
+ private AggregateReport createAggregateReportForSourceAndTrigger(
+ Source source, Trigger trigger) {
+ return createAggregateReportForSourceAndTrigger(
+ UUID.randomUUID().toString(), source, trigger);
+ }
+
+ private EventReport createEventReportForSourceAndTrigger(Source source, Trigger trigger)
+ throws JSONException {
+ return createEventReportForSourceAndTrigger(UUID.randomUUID().toString(), source, trigger);
+ }
+
+ private AggregateReport createAggregateReportForSourceAndTrigger(
+ String reportId, Source source, Trigger trigger) {
+ return AggregateReportFixture.getValidAggregateReportBuilder()
+ .setId(reportId)
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
+ .build();
+ }
+
+ private EventReport createEventReportForSourceAndTrigger(
+ String reportId, Source source, Trigger trigger) throws JSONException {
+ return new EventReport.Builder()
+ .setId(reportId)
+ .populateFromSourceAndTrigger(source, trigger, trigger.parseEventTriggers().get(0))
+ .setSourceEventId(source.getEventId())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
+ .build();
+ }
+
private void setupSourceAndTriggerData() {
SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
List<Source> sourcesList = new ArrayList<>();
- sourcesList.add(SourceFixture.getValidSourceBuilder()
- .setId("S1").setRegistrant(APP_TWO_SOURCES).build());
- sourcesList.add(SourceFixture.getValidSourceBuilder()
- .setId("S2").setRegistrant(APP_TWO_SOURCES).build());
- sourcesList.add(SourceFixture.getValidSourceBuilder()
- .setId("S3").setRegistrant(APP_ONE_SOURCE).build());
+ sourcesList.add(
+ SourceFixture.getValidSourceBuilder()
+ .setId("S1")
+ .setRegistrant(APP_TWO_SOURCES)
+ .setPublisher(APP_TWO_PUBLISHER)
+ .setPublisherType(EventSurfaceType.APP)
+ .build());
+ sourcesList.add(
+ SourceFixture.getValidSourceBuilder()
+ .setId("S2")
+ .setRegistrant(APP_TWO_SOURCES)
+ .setPublisher(APP_TWO_PUBLISHER)
+ .setPublisherType(EventSurfaceType.APP)
+ .build());
+ sourcesList.add(
+ SourceFixture.getValidSourceBuilder()
+ .setId("S3")
+ .setRegistrant(APP_ONE_SOURCE)
+ .setPublisher(APP_ONE_PUBLISHER)
+ .setPublisherType(EventSurfaceType.APP)
+ .build());
for (Source source : sourcesList) {
ContentValues values = new ContentValues();
values.put("_id", source.getId());
values.put("registrant", source.getRegistrant().toString());
+ values.put("publisher", source.getPublisher().toString());
+ values.put("publisher_type", source.getPublisherType());
+
long row = db.insert("msmt_source", null, values);
- Assert.assertNotEquals("Source insertion failed", -1, row);
+ assertNotEquals("Source insertion failed", -1, row);
}
List<Trigger> triggersList = new ArrayList<>();
triggersList.add(TriggerFixture.getValidTriggerBuilder()
@@ -1689,17 +4196,54 @@ public class MeasurementDaoTest {
values.put("_id", trigger.getId());
values.put("registrant", trigger.getRegistrant().toString());
long row = db.insert("msmt_trigger", null, values);
- Assert.assertNotEquals("Trigger insertion failed", -1, row);
+ assertNotEquals("Trigger insertion failed", -1, row);
+ }
+ }
+
+ private void setupSourceDataForPublisherTypeWeb() {
+ SQLiteDatabase db = DbHelper.getInstance(sContext).safeGetWritableDatabase();
+ List<Source> sourcesList = new ArrayList<>();
+ sourcesList.add(
+ SourceFixture.getValidSourceBuilder()
+ .setId("W1")
+ .setPublisher(WEB_PUBLISHER_ONE)
+ .setPublisherType(EventSurfaceType.WEB)
+ .build());
+ sourcesList.add(
+ SourceFixture.getValidSourceBuilder()
+ .setId("W2")
+ .setPublisher(WEB_PUBLISHER_TWO)
+ .setPublisherType(EventSurfaceType.WEB)
+ .build());
+ sourcesList.add(
+ SourceFixture.getValidSourceBuilder()
+ .setId("S3")
+ .setPublisher(WEB_PUBLISHER_THREE)
+ .setPublisherType(EventSurfaceType.WEB)
+ .build());
+ for (Source source : sourcesList) {
+ ContentValues values = new ContentValues();
+ values.put("_id", source.getId());
+ values.put("publisher", source.getPublisher().toString());
+ values.put("publisher_type", source.getPublisherType());
+
+ long row = db.insert("msmt_source", null, values);
+ assertNotEquals("Source insertion failed", -1, row);
}
}
- private Source createSourceForIATest(String id, long currentTime, long priority,
- int eventTimePastDays, boolean expiredIAWindow) {
+ private Source createSourceForIATest(
+ String id,
+ long currentTime,
+ long priority,
+ int eventTimePastDays,
+ boolean expiredIAWindow,
+ String enrollmentId) {
return new Source.Builder()
.setId(id)
.setPublisher(Uri.parse("android-app://com.example.sample"))
.setRegistrant(Uri.parse("android-app://com.example.sample"))
- .setEnrollmentId("enrollment-id")
+ .setEnrollmentId(enrollmentId)
.setExpiryTime(currentTime + TimeUnit.DAYS.toMillis(30))
.setInstallAttributionWindow(TimeUnit.DAYS.toMillis(expiredIAWindow ? 0 : 30))
.setAppDestination(INSTALLED_PACKAGE)
@@ -1711,6 +4255,66 @@ public class MeasurementDaoTest {
.build();
}
+ private AggregateReport generateMockAggregateReport(String attributionDestination, int id) {
+ return new AggregateReport.Builder()
+ .setId(String.valueOf(id))
+ .setAttributionDestination(Uri.parse(attributionDestination))
+ .build();
+ }
+
+ private EventReport generateMockEventReport(String attributionDestination, int id) {
+ return new EventReport.Builder()
+ .setId(String.valueOf(id))
+ .setAttributionDestination(Uri.parse(attributionDestination))
+ .build();
+ }
+
+ private void assertAggregateReportCount(
+ List<String> attributionDestinations,
+ int destinationType,
+ List<Integer> expectedCounts) {
+ IntStream.range(0, attributionDestinations.size())
+ .forEach(i -> Assert.assertEquals(expectedCounts.get(i),
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransactionWithResult(measurementDao ->
+ measurementDao.getNumAggregateReportsPerDestination(
+ Uri.parse(attributionDestinations.get(i)),
+ destinationType))
+ .orElseThrow()));
+ }
+
+ private void assertEventReportCount(
+ List<String> attributionDestinations,
+ int destinationType,
+ List<Integer> expectedCounts) {
+ IntStream.range(0, attributionDestinations.size())
+ .forEach(i -> Assert.assertEquals(expectedCounts.get(i),
+ DatastoreManagerFactory.getDatastoreManager(sContext)
+ .runInTransactionWithResult(measurementDao ->
+ measurementDao.getNumEventReportsPerDestination(
+ Uri.parse(attributionDestinations.get(i)),
+ destinationType))
+ .orElseThrow()));
+ }
+
+ private List<String> createAppDestinationVariants(int destinationNum) {
+ return Arrays.asList(
+ "android-app://subdomain.destination-" + destinationNum + ".app/abcd",
+ "android-app://subdomain.destination-" + destinationNum + ".app",
+ "android-app://destination-" + destinationNum + ".app/abcd",
+ "android-app://destination-" + destinationNum + ".app",
+ "android-app://destination-" + destinationNum + ".ap");
+ }
+
+ private List<String> createWebDestinationVariants(int destinationNum) {
+ return Arrays.asList(
+ "https://subdomain.destination-" + destinationNum + ".com/abcd",
+ "https://subdomain.destination-" + destinationNum + ".com",
+ "https://destination-" + destinationNum + ".com/abcd",
+ "https://destination-" + destinationNum + ".com",
+ "https://destination-" + destinationNum + ".co");
+ }
+
private boolean getInstallAttributionStatus(String sourceDbId, SQLiteDatabase db) {
Cursor cursor =
db.query(
@@ -1722,7 +4326,7 @@ public class MeasurementDaoTest {
null,
null,
null);
- Assert.assertTrue(cursor.moveToFirst());
+ assertTrue(cursor.moveToFirst());
return cursor.getInt(0) == 1;
}
@@ -1732,4 +4336,42 @@ public class MeasurementDaoTest {
SourceContract.ID + " IN ( ? )",
new String[] {String.join(",", dbIds)});
}
+
+ /** Create {@link Attribution} object from SQLite datastore. */
+ private static Attribution constructAttributionFromCursor(Cursor cursor) {
+ Attribution.Builder builder = new Attribution.Builder();
+ int index = cursor.getColumnIndex(MeasurementTables.AttributionContract.ID);
+ if (index > -1 && !cursor.isNull(index)) {
+ builder.setId(cursor.getString(index));
+ }
+ index = cursor.getColumnIndex(MeasurementTables.AttributionContract.SOURCE_SITE);
+ if (index > -1 && !cursor.isNull(index)) {
+ builder.setSourceSite(cursor.getString(index));
+ }
+ index = cursor.getColumnIndex(MeasurementTables.AttributionContract.SOURCE_ORIGIN);
+ if (index > -1 && !cursor.isNull(index)) {
+ builder.setSourceOrigin(cursor.getString(index));
+ }
+ index = cursor.getColumnIndex(MeasurementTables.AttributionContract.DESTINATION_SITE);
+ if (index > -1 && !cursor.isNull(index)) {
+ builder.setDestinationSite(cursor.getString(index));
+ }
+ index = cursor.getColumnIndex(MeasurementTables.AttributionContract.DESTINATION_ORIGIN);
+ if (index > -1 && !cursor.isNull(index)) {
+ builder.setDestinationOrigin(cursor.getString(index));
+ }
+ index = cursor.getColumnIndex(MeasurementTables.AttributionContract.ENROLLMENT_ID);
+ if (index > -1 && !cursor.isNull(index)) {
+ builder.setEnrollmentId(cursor.getString(index));
+ }
+ index = cursor.getColumnIndex(MeasurementTables.AttributionContract.TRIGGER_TIME);
+ if (index > -1 && !cursor.isNull(index)) {
+ builder.setTriggerTime(cursor.getLong(index));
+ }
+ index = cursor.getColumnIndex(MeasurementTables.AttributionContract.REGISTRANT);
+ if (index > -1 && !cursor.isNull(index)) {
+ builder.setRegistrant(cursor.getString(index));
+ }
+ return builder.build();
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/deletion/MeasurementDataDeleterTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/deletion/MeasurementDataDeleterTest.java
new file mode 100644
index 000000000..c6971b908
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/deletion/MeasurementDataDeleterTest.java
@@ -0,0 +1,421 @@
+/*
+ * 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.data.measurement.deletion;
+
+import static com.android.adservices.data.measurement.deletion.MeasurementDataDeleter.ANDROID_APP_SCHEME;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.adservices.measurement.DeletionParam;
+import android.adservices.measurement.DeletionRequest;
+import android.net.Uri;
+
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.IMeasurementDao;
+import com.android.adservices.data.measurement.ITransaction;
+import com.android.adservices.service.measurement.EventReport;
+import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.SourceFixture;
+import com.android.adservices.service.measurement.Trigger;
+import com.android.adservices.service.measurement.TriggerFixture;
+import com.android.adservices.service.measurement.aggregation.AggregatableAttributionSource;
+import com.android.adservices.service.measurement.aggregation.AggregateAttributionData;
+import com.android.adservices.service.measurement.aggregation.AggregateHistogramContribution;
+import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.aggregation.AggregateReportFixture;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.math.BigInteger;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MeasurementDataDeleterTest {
+ private static final List<AggregateHistogramContribution> CONTRIBUTIONS_1 =
+ Arrays.asList(
+ new AggregateHistogramContribution.Builder()
+ .setKey(new BigInteger("10"))
+ .setValue(45)
+ .build(),
+ new AggregateHistogramContribution.Builder()
+ .setKey(new BigInteger("100"))
+ .setValue(87)
+ .build());
+
+ private static final AggregateReport AGGREGATE_REPORT_1 =
+ AggregateReportFixture.getValidAggregateReportBuilder()
+ .setId("reportId1")
+ .setAggregateAttributionData(
+ new AggregateAttributionData.Builder()
+ .setId(1L)
+ .setContributions(CONTRIBUTIONS_1)
+ .build())
+ .setSourceId("source1")
+ .setTriggerId("trigger1")
+ .build();
+
+ private static final List<AggregateHistogramContribution> CONTRIBUTIONS_2 =
+ Arrays.asList(
+ new AggregateHistogramContribution.Builder()
+ .setKey(new BigInteger("500"))
+ .setValue(2000)
+ .build(),
+ new AggregateHistogramContribution.Builder()
+ .setKey(new BigInteger("10000"))
+ .setValue(3454)
+ .build());
+
+ private static final AggregateReport AGGREGATE_REPORT_2 =
+ AggregateReportFixture.getValidAggregateReportBuilder()
+ .setId("reportId2")
+ .setAggregateAttributionData(
+ new AggregateAttributionData.Builder()
+ .setId(2L)
+ .setContributions(CONTRIBUTIONS_2)
+ .build())
+ .setSourceId("source2")
+ .setTriggerId("trigger2")
+ .build();
+
+ private static final Instant START = Instant.ofEpochMilli(5000);
+ private static final Instant END = Instant.ofEpochMilli(10000);
+ private static final String PACKAGE_NAME = "com.package.name";
+
+ @Mock private IMeasurementDao mMeasurementDao;
+ @Mock private ITransaction mTransaction;
+ @Mock private AggregatableAttributionSource mAggregatableAttributionSource1;
+ @Mock private AggregatableAttributionSource mAggregatableAttributionSource2;
+ @Mock private EventReport mEventReport1;
+ @Mock private EventReport mEventReport2;
+ @Mock private EventReport mEventReport3;
+ @Mock private AggregateReport mAggregateReport1;
+ @Mock private AggregateReport mAggregateReport2;
+ @Mock private List<Uri> mOriginUris;
+ @Mock private List<Uri> mDomainUris;
+
+ private MeasurementDataDeleter mMeasurementDataDeleter;
+
+ private class FakeDatastoreManager extends DatastoreManager {
+ @Override
+ public ITransaction createNewTransaction() {
+ return mTransaction;
+ }
+
+ @Override
+ public IMeasurementDao getMeasurementDao() {
+ return mMeasurementDao;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mMeasurementDataDeleter = new MeasurementDataDeleter(new FakeDatastoreManager());
+ }
+
+ @Test
+ public void resetAggregateContributions_hasMatchingReports_resetsContributions()
+ throws DatastoreException {
+ // Setup
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source1")
+ .setAggregatableAttributionSource(mAggregatableAttributionSource1)
+ .setAggregateContributions(32666)
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source2")
+ .setAggregatableAttributionSource(mAggregatableAttributionSource2)
+ .setAggregateContributions(6235)
+ .build();
+
+ when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
+ when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
+
+ // Execute
+ mMeasurementDataDeleter.resetAggregateContributions(
+ mMeasurementDao, Arrays.asList(AGGREGATE_REPORT_1, AGGREGATE_REPORT_2));
+
+ // Verify
+ ArgumentCaptor<Source> sourceCaptor = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao, times(2))
+ .updateSourceAggregateContributions(sourceCaptor.capture());
+ assertEquals(2, sourceCaptor.getAllValues().size());
+ assertEquals(
+ 32534,
+ sourceCaptor.getAllValues().get(0).getAggregateContributions()); // 32666-87-45
+ assertEquals(
+ 781,
+ sourceCaptor.getAllValues().get(1).getAggregateContributions()); // 6235-3454-2000
+ }
+
+ @Test
+ public void resetAggregateContributions_withSourceContributionsGoingBelowZero_resetsToZero()
+ throws DatastoreException {
+ // Setup
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source1")
+ .setAggregatableAttributionSource(mAggregatableAttributionSource1)
+ .setAggregateContributions(10)
+ .build();
+
+ when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
+
+ // Execute
+ mMeasurementDataDeleter.resetAggregateContributions(
+ mMeasurementDao, Collections.singletonList(AGGREGATE_REPORT_1));
+
+ // Verify
+ ArgumentCaptor<Source> sourceCaptor = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao, times(1))
+ .updateSourceAggregateContributions(sourceCaptor.capture());
+ assertEquals(1, sourceCaptor.getAllValues().size());
+ assertEquals(0, sourceCaptor.getValue().getAggregateContributions());
+ }
+
+ @Test
+ public void resetDedupKeys_hasMatchingEventReports_removesTriggerDedupKeysFromSource()
+ throws DatastoreException {
+ // Setup
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("sourceId1")
+ .setDedupKeys(
+ new ArrayList<>(
+ Arrays.asList(
+ new UnsignedLong("1"),
+ new UnsignedLong("2"),
+ new UnsignedLong("3"))))
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("sourceId2")
+ .setDedupKeys(
+ new ArrayList<>(
+ Arrays.asList(
+ new UnsignedLong("11"),
+ new UnsignedLong("22"),
+ new UnsignedLong("33"))))
+ .build();
+
+ when(mEventReport1.getTriggerDedupKey()).thenReturn(new UnsignedLong("1")); // S1 - T1
+ when(mEventReport2.getTriggerDedupKey()).thenReturn(new UnsignedLong("22")); // S2 - T2
+ when(mEventReport3.getTriggerDedupKey()).thenReturn(new UnsignedLong("3")); // S1 - T3
+ when(mEventReport1.getSourceId()).thenReturn(source1.getId());
+ when(mEventReport2.getSourceId()).thenReturn(source2.getId());
+ when(mEventReport3.getSourceId()).thenReturn(source1.getId());
+
+ when(mMeasurementDao.getSource(source1.getId())).thenReturn(source1);
+ when(mMeasurementDao.getSource(source2.getId())).thenReturn(source2);
+
+ // Execution
+ mMeasurementDataDeleter.resetDedupKeys(
+ mMeasurementDao, List.of(mEventReport1, mEventReport2, mEventReport3));
+
+ // Verification
+ verify(mMeasurementDao, times(2)).updateSourceDedupKeys(source1);
+ verify(mMeasurementDao).updateSourceDedupKeys(source2);
+ assertEquals(Collections.singletonList(new UnsignedLong("2")), source1.getDedupKeys());
+ assertEquals(
+ Arrays.asList(new UnsignedLong("11"), new UnsignedLong("33")),
+ source2.getDedupKeys());
+ }
+
+ @Test
+ public void resetDedupKeys_eventReportHasNullSourceId_ignoresRemoval()
+ throws DatastoreException {
+ // Setup
+ when(mEventReport1.getSourceId()).thenReturn(null);
+ when(mEventReport1.getTriggerDedupKey()).thenReturn(new UnsignedLong("1")); // S1 - T1
+
+ // Execution
+ mMeasurementDataDeleter.resetDedupKeys(mMeasurementDao, List.of(mEventReport1));
+
+ // Verification
+ verify(mMeasurementDao, never()).getSource(anyString());
+ verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
+ }
+
+ @Test
+ public void delete_deletionModeAll_success() throws DatastoreException {
+ // Setup
+ MeasurementDataDeleter subjectUnderTest = spy(mMeasurementDataDeleter);
+ List<String> triggerIds = List.of("triggerId1", "triggerId2");
+ List<String> sourceIds = List.of("sourceId1", "sourceId2");
+ Source source1 = SourceFixture.getValidSourceBuilder().setId("sourceId1").build();
+ Source source2 = SourceFixture.getValidSourceBuilder().setId("sourceId2").build();
+ Trigger trigger1 = TriggerFixture.getValidTriggerBuilder().setId("triggerId1").build();
+ Trigger trigger2 = TriggerFixture.getValidTriggerBuilder().setId("triggerId2").build();
+ when(mEventReport1.getId()).thenReturn("eventReportId1");
+ when(mEventReport2.getId()).thenReturn("eventReportId2");
+ when(mAggregateReport1.getId()).thenReturn("aggregateReportId1");
+ when(mAggregateReport2.getId()).thenReturn("aggregateReportId2");
+ DeletionParam deletionParam =
+ new DeletionParam.Builder()
+ .setOriginUris(mOriginUris)
+ .setDomainUris(mDomainUris)
+ .setDeletionMode(DeletionRequest.DELETION_MODE_ALL)
+ .setStart(START)
+ .setEnd(END)
+ .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
+ .setPackageName(PACKAGE_NAME)
+ .build();
+
+ doNothing()
+ .when(subjectUnderTest)
+ .resetAggregateContributions(
+ mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
+ doNothing()
+ .when(subjectUnderTest)
+ .resetDedupKeys(mMeasurementDao, List.of(mEventReport1, mEventReport2));
+ when(mMeasurementDao.fetchMatchingEventReports(sourceIds, triggerIds))
+ .thenReturn(List.of(mEventReport1, mEventReport2));
+ when(mMeasurementDao.fetchMatchingAggregateReports(sourceIds, triggerIds))
+ .thenReturn(List.of(mAggregateReport1, mAggregateReport2));
+ when(mMeasurementDao.fetchMatchingSources(
+ Uri.parse(ANDROID_APP_SCHEME + "://" + PACKAGE_NAME),
+ START,
+ END,
+ mOriginUris,
+ mDomainUris,
+ DeletionRequest.MATCH_BEHAVIOR_DELETE))
+ .thenReturn(Arrays.asList(source1.getId(), source2.getId()));
+ when(mMeasurementDao.fetchMatchingTriggers(
+ Uri.parse(ANDROID_APP_SCHEME + "://" + PACKAGE_NAME),
+ START,
+ END,
+ mOriginUris,
+ mDomainUris,
+ DeletionRequest.MATCH_BEHAVIOR_DELETE))
+ .thenReturn(Arrays.asList(trigger1.getId(), trigger2.getId()));
+
+ // Execution
+ boolean result = subjectUnderTest.delete(deletionParam);
+
+ // Assertions
+ assertTrue(result);
+ verify(subjectUnderTest)
+ .resetAggregateContributions(
+ mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
+ verify(subjectUnderTest)
+ .resetDedupKeys(mMeasurementDao, List.of(mEventReport1, mEventReport2));
+ verify(mMeasurementDao).deleteSources(sourceIds);
+ verify(mMeasurementDao).deleteTriggers(triggerIds);
+ }
+
+ @Test
+ public void delete_deletionModeExcludeInternalData_success() throws DatastoreException {
+ // Setup
+ MeasurementDataDeleter subjectUnderTest = spy(mMeasurementDataDeleter);
+ List<String> triggerIds = List.of("triggerId1", "triggerId2");
+ List<String> sourceIds = List.of("sourceId1", "sourceId2");
+ Source source1 = SourceFixture.getValidSourceBuilder().setId("sourceId1").build();
+ Source source2 = SourceFixture.getValidSourceBuilder().setId("sourceId2").build();
+ Trigger trigger1 = TriggerFixture.getValidTriggerBuilder().setId("triggerId1").build();
+ Trigger trigger2 = TriggerFixture.getValidTriggerBuilder().setId("triggerId2").build();
+ when(mEventReport1.getId()).thenReturn("eventReportId1");
+ when(mEventReport2.getId()).thenReturn("eventReportId2");
+ when(mAggregateReport1.getId()).thenReturn("aggregateReportId1");
+ when(mAggregateReport2.getId()).thenReturn("aggregateReportId2");
+ DeletionParam deletionParam =
+ new DeletionParam.Builder()
+ .setOriginUris(mOriginUris)
+ .setDomainUris(mDomainUris)
+ .setDeletionMode(DeletionRequest.DELETION_MODE_EXCLUDE_INTERNAL_DATA)
+ .setStart(START)
+ .setEnd(END)
+ .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
+ .setPackageName(PACKAGE_NAME)
+ .build();
+
+ doNothing()
+ .when(subjectUnderTest)
+ .resetAggregateContributions(
+ mMeasurementDao, List.of(mAggregateReport1, mAggregateReport2));
+ doNothing()
+ .when(subjectUnderTest)
+ .resetDedupKeys(mMeasurementDao, List.of(mEventReport1, mEventReport2));
+ when(mMeasurementDao.fetchMatchingEventReports(sourceIds, triggerIds))
+ .thenReturn(List.of(mEventReport1, mEventReport2));
+ when(mMeasurementDao.fetchMatchingAggregateReports(sourceIds, triggerIds))
+ .thenReturn(List.of(mAggregateReport1, mAggregateReport2));
+ when(mMeasurementDao.fetchMatchingSources(
+ Uri.parse(ANDROID_APP_SCHEME + "://" + PACKAGE_NAME),
+ START,
+ END,
+ mOriginUris,
+ mDomainUris,
+ DeletionRequest.MATCH_BEHAVIOR_DELETE))
+ .thenReturn(Arrays.asList(source1.getId(), source2.getId()));
+ when(mMeasurementDao.fetchMatchingTriggers(
+ Uri.parse(ANDROID_APP_SCHEME + "://" + PACKAGE_NAME),
+ START,
+ END,
+ mOriginUris,
+ mDomainUris,
+ DeletionRequest.MATCH_BEHAVIOR_DELETE))
+ .thenReturn(Arrays.asList(trigger1.getId(), trigger2.getId()));
+
+ // Execution
+ boolean result = subjectUnderTest.delete(deletionParam);
+
+ // Assertions
+ assertTrue(result);
+ verify(mMeasurementDao)
+ .markEventReportStatus(
+ eq("eventReportId1"), eq(EventReport.Status.MARKED_TO_DELETE));
+ verify(mMeasurementDao)
+ .markEventReportStatus(
+ eq("eventReportId2"), eq(EventReport.Status.MARKED_TO_DELETE));
+ verify(mMeasurementDao)
+ .markAggregateReportStatus(
+ eq("aggregateReportId2"), eq(AggregateReport.Status.MARKED_TO_DELETE));
+ verify(mMeasurementDao)
+ .markAggregateReportStatus(
+ eq("aggregateReportId2"), eq(AggregateReport.Status.MARKED_TO_DELETE));
+ verify(mMeasurementDao)
+ .updateSourceStatus(
+ eq(List.of(source1.getId(), source2.getId())),
+ eq(Source.Status.MARKED_TO_DELETE));
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(List.of(trigger1.getId(), trigger2.getId())),
+ eq(Trigger.Status.MARKED_TO_DELETE));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/AbstractMeasurementDbMigratorTestBase.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/AbstractMeasurementDbMigratorTestBase.java
index ec920b6b3..731010a11 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/AbstractMeasurementDbMigratorTestBase.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/AbstractMeasurementDbMigratorTestBase.java
@@ -26,6 +26,7 @@ import android.database.sqlite.SQLiteDatabase;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.data.DbHelper;
+import com.android.adservices.data.measurement.DbHelperV1;
import org.junit.Before;
import org.junit.Test;
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/MeasurementDbMigratorV3Test.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/MeasurementDbMigratorV3Test.java
new file mode 100644
index 000000000..21deb8747
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/migration/MeasurementDbMigratorV3Test.java
@@ -0,0 +1,143 @@
+/*
+ * 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.data.measurement.migration;
+
+import static com.android.adservices.data.DbTestUtil.doesIndexExist;
+import static com.android.adservices.data.DbTestUtil.doesTableExistAndColumnCountMatch;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.adservices.data.DbHelper;
+import com.android.adservices.data.measurement.MeasurementTables;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MeasurementDbMigratorV3Test extends AbstractMeasurementDbMigratorTestBase {
+
+ private static final String[][] INSERTED_EVENT_REPORT_DATA = {
+ // id, destination, sourceId
+ {"1", "https://example.com", "random one"},
+ {"2", "http://will-be-trimmed.example.com", "random two"},
+ {"3", "http://example.com/will-be-trimmed", "random three"},
+ {"4", "android-app://com.android.app/will-be-trimmed", "random four"},
+ {"5", "android-app://com.another.android.app", "random five"},
+ };
+
+ private static final String[][] MIGRATED_EVENT_REPORT_DATA = {
+ // id, destination, sourceEventId
+ {"1", "https://example.com", "random one"},
+ {"2", "http://example.com", "random two"},
+ {"3", "http://example.com", "random three"},
+ {"4", "android-app://com.android.app", "random four"},
+ {"5", "android-app://com.another.android.app", "random five"},
+ };
+
+ @Test
+ public void performMigration_success_v2ToV3() {
+ // Setup
+ DbHelper dbHelper = getDbHelper(1);
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+
+ // Execution
+ new MeasurementDbMigratorV2().performMigration(db, 1, 2);
+
+ insertEventReports(db);
+
+ new MeasurementDbMigratorV3().performMigration(db, 2, 3);
+ // To mimic real onUpgrade behaviour. Without closing the db, changes don't reflect.
+ db.close();
+
+ // Verify
+ db = dbHelper.getReadableDatabase();
+ assertTrue(
+ doesTableExistAndColumnCountMatch(
+ db, MeasurementTables.AsyncRegistrationContract.TABLE, 16));
+ assertTrue(
+ doesTableExistAndColumnCountMatch(
+ db, MeasurementTables.EventReportContract.TABLE, 17));
+ assertTrue(
+ doesTableExistAndColumnCountMatch(db, MeasurementTables.AggregateReport.TABLE, 14));
+ assertTrue(
+ doesTableExistAndColumnCountMatch(
+ db, MeasurementTables.AttributionContract.TABLE, 10));
+ assertTrue(doesIndexExist(db, "idx_msmt_attribution_ss_so_ds_do_ei_tt"));
+ assertEventReportMigration(db);
+ }
+
+ @Override
+ int getTargetVersion() {
+ return 3;
+ }
+
+ @Override
+ AbstractMeasurementDbMigrator getTestSubject() {
+ return new MeasurementDbMigratorV3();
+ }
+
+ private static void insertEventReports(SQLiteDatabase db) {
+ for (int i = 0; i < INSERTED_EVENT_REPORT_DATA.length; i++) {
+ insertEventReport(
+ db,
+ INSERTED_EVENT_REPORT_DATA[i][0],
+ INSERTED_EVENT_REPORT_DATA[i][1],
+ INSERTED_EVENT_REPORT_DATA[i][2]);
+ }
+ }
+
+ private static void assertEventReportMigration(SQLiteDatabase db) {
+ Cursor cursor = db.query(
+ MeasurementTables.EventReportContract.TABLE,
+ new String[] {
+ MeasurementTables.EventReportContract.ID,
+ MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION,
+ MeasurementTables.EventReportContract.SOURCE_EVENT_ID
+ },
+ null, null, null, null,
+ /* orderBy */ MeasurementTables.EventReportContract.ID,
+ null);
+ while (cursor.moveToNext()) {
+ assertEventReportMigrated(cursor);
+ }
+ }
+
+ private static void insertEventReport(SQLiteDatabase db, String id, String destination,
+ String sourceId) {
+ ContentValues values = new ContentValues();
+ values.put(MeasurementTables.EventReportContract.ID, id);
+ values.put(MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION, destination);
+ values.put(MeasurementTables.EventReportContract.SOURCE_ID, sourceId);
+ db.insert(MeasurementTables.EventReportContract.TABLE, null, values);
+ }
+
+ private static void assertEventReportMigrated(Cursor cursor) {
+ int i = cursor.getPosition();
+ assertEquals(MIGRATED_EVENT_REPORT_DATA[i][0], cursor.getString(cursor.getColumnIndex(
+ MeasurementTables.EventReportContract.ID)));
+ assertEquals(MIGRATED_EVENT_REPORT_DATA[i][1], cursor.getString(cursor.getColumnIndex(
+ MeasurementTables.EventReportContract.ATTRIBUTION_DESTINATION)));
+ assertEquals(MIGRATED_EVENT_REPORT_DATA[i][2], cursor.getString(cursor.getColumnIndex(
+ MeasurementTables.EventReportContract.SOURCE_EVENT_ID)));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/TopicsDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/TopicsDaoTest.java
index 21870da41..1f9233921 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/TopicsDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/TopicsDaoTest.java
@@ -31,6 +31,7 @@ import com.android.adservices.data.DbTestUtil;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
@@ -72,6 +73,7 @@ public final class TopicsDaoTest {
DbTestUtil.deleteTable(TopicsTables.AppUsageHistoryContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.BlockedTopicsContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.EpochOriginContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.TopicContributorsContract.TABLE);
}
@Test
@@ -712,12 +714,16 @@ public final class TopicsDaoTest {
/* current Epoch ID */ 3, /* look back Epochs */ 3))
.isEmpty();
- mTopicsDao.deleteAllTopicsTables(Collections.emptyList());
+ mTopicsDao.deleteAllTopicsTables(/* tablesToExclude */ Collections.emptyList());
assertThat(mTopicsDao.retrieveAllBlockedTopics()).isEmpty();
+
+ mTopicsDao.persistTopicContributors(epochId1, Map.of(topic1.getTopic(), Set.of(app1)));
+ mTopicsDao.deleteAllTopicsTables(/* tablesToExclude */ Collections.emptyList());
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId1)).isEmpty();
}
@Test
- public void testDeleteAppFromTable() {
+ public void testDeleteFromTableByColumn() {
// Test with AppClassificationTopics Contract
final long epochId = 1L;
@@ -747,9 +753,11 @@ public final class TopicsDaoTest {
.isEqualTo(expectedTopicsMap);
// Erase Data for app1, app2
- mTopicsDao.deleteAppFromTable(
- TopicsTables.AppClassificationTopicsContract.TABLE,
- TopicsTables.AppClassificationTopicsContract.APP,
+ mTopicsDao.deleteFromTableByColumn(
+ List.of(
+ Pair.create(
+ TopicsTables.AppClassificationTopicsContract.TABLE,
+ TopicsTables.AppClassificationTopicsContract.APP)),
List.of(app1, app2));
expectedTopicsMap.remove(app1);
@@ -764,42 +772,216 @@ public final class TopicsDaoTest {
returnedAppSdkTopics.put(Pair.create(app2, sdk), topic1);
mTopicsDao.persistReturnedAppTopicsMap(epochId, returnedAppSdkTopics);
- mTopicsDao.deleteAppFromTable(
- TopicsTables.ReturnedTopicContract.TABLE,
- TopicsTables.ReturnedTopicContract.APP,
+ mTopicsDao.deleteFromTableByColumn(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.APP)),
List.of(app1, app2));
assertThat(mTopicsDao.retrieveReturnedTopics(epochId, /* numberOfLookBackEpochs */ 1))
.isEmpty();
+
+ // Verify TopicContributorsContract. This is also able to test a non-String value
+ long epochId2 = epochId + 1;
+ mTopicsDao.persistTopicContributors(epochId, Map.of(topic1.getTopic(), Set.of(app1)));
+ mTopicsDao.persistTopicContributors(
+ epochId2, Map.of(topic1.getTopic(), Set.of(app1, app2)));
+
+ mTopicsDao.deleteFromTableByColumn(
+ List.of(
+ Pair.create(
+ TopicsTables.TopicContributorsContract.TABLE,
+ TopicsTables.TopicContributorsContract.EPOCH_ID)),
+ List.of(String.valueOf(epochId), String.valueOf(epochId2)));
+
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId)).isEmpty();
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId2)).isEmpty();
}
@Test
- public void testDeleteAppFromTable_nullArguments() {
+ public void testDeleteFromTableByColumn_nullArguments() {
assertThrows(
NullPointerException.class,
() ->
- mTopicsDao.deleteAppFromTable(
- /* tableName */ null,
- TopicsTables.AppClassificationTopicsContract.APP,
- List.of("app ")));
+ mTopicsDao.deleteFromTableByColumn(
+ /* tableNamesAndColumnNamePairs */ null, List.of("app ")));
assertThrows(
NullPointerException.class,
() ->
- mTopicsDao.deleteAppFromTable(
- TopicsTables.AppClassificationTopicsContract.TABLE,
- /* appColumnName */ null,
- List.of("app ")));
+ mTopicsDao.deleteFromTableByColumn(
+ List.of(
+ Pair.create(
+ TopicsTables.AppClassificationTopicsContract.TABLE,
+ TopicsTables.AppClassificationTopicsContract.APP)),
+ /* app */ null));
+ }
+
+ @Test
+ public void testDeleteEntriesFromTableByColumnWithEqualCondition() {
+ final long epochId1 = 1L;
+ final long epochId2 = 2L;
+ final String app = "app";
+ final String sdk = "sdk";
+
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+
+ // Verify ReturnedTopicContract
+ mTopicsDao.persistReturnedAppTopicsMap(epochId1, Map.of(Pair.create(app, sdk), topic1));
+ mTopicsDao.persistReturnedAppTopicsMap(epochId2, Map.of(Pair.create(app, sdk), topic2));
+
+ // Verify equalConditionValue is double by deleting with epochID = epoch2
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.APP)),
+ List.of(app),
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ String.valueOf(epochId2),
+ /* isStringEqualConditionColumnValue */ false);
+ assertThat(mTopicsDao.retrieveReturnedTopics(epochId1, /* numberOfLookBackEpochs */ 1))
+ .isEqualTo(Map.of(epochId1, Map.of(Pair.create(app, sdk), topic1)));
+ assertThat(mTopicsDao.retrieveReturnedTopics(epochId2, /* numberOfLookBackEpochs */ 1))
+ .isEmpty();
+
+ // Verify equalConditionValue is String by deleting sdk
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.APP)),
+ List.of(app),
+ TopicsTables.ReturnedTopicContract.SDK,
+ sdk,
+ /* isStringEqualConditionColumnValue */ true);
+ assertThat(mTopicsDao.retrieveReturnedTopics(epochId1, /* numberOfLookBackEpochs */ 1))
+ .isEmpty();
+ }
+
+ @Test
+ public void testDeleteEntriesFromTableByColumnWithEqualCondition_nullArguments() {
assertThrows(
NullPointerException.class,
() ->
- mTopicsDao.deleteAppFromTable(
- TopicsTables.AppClassificationTopicsContract.TABLE,
- TopicsTables.AppClassificationTopicsContract.APP,
- /* app */ null));
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ /* tableNamesAndColumnNamePairs */ null,
+ /* Values to Delete */ List.of("app"),
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ /* epoch Id */ String.valueOf(1L),
+ /* isStringEqualConditionColumnValue */ false));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.APP)),
+ /* Values to Delete */ null,
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ /* epoch Id */ String.valueOf(1L),
+ /* isStringEqualConditionColumnValue */ false));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.APP)),
+ /* Values to Delete */ List.of("app"),
+ /* equalConditionColumnName */ null,
+ /* epoch Id */ String.valueOf(1L),
+ /* isStringEqualConditionColumnValue */ false));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.APP)),
+ /* Values to Delete */ List.of("app"),
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ /* equalConditionColumnValue */ null,
+ /* isStringEqualConditionColumnValue */ false));
+ }
+
+ @Test
+ public void testDeleteEntriesFromTableByColumnWithEqualCondition_nonExistingArguments() {
+ // Persist an entry to Returned Topics Table
+ final long epochId1 = 1L;
+ final int numberOfLookBackEpochs = 1;
+ final String app = "app";
+ final String sdk = "sdk";
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ mTopicsDao.persistReturnedAppTopicsMap(epochId1, Map.of(Pair.create(app, sdk), topic1));
+ Map<Long, Map<Pair<String, String>, Topic>> expectedReturnedTopicsMap =
+ Map.of(epochId1, Map.of(Pair.create(app, sdk), topic1));
+
+ // To test passing in a non-existing table name
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(Pair.create("Some Table", TopicsTables.ReturnedTopicContract.APP)),
+ /* Values to Delete */ List.of(app),
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ /* epoch Id */ String.valueOf(epochId1),
+ /* isStringEqualConditionColumnValue */ false);
+ assertThat(mTopicsDao.retrieveReturnedTopics(epochId1, numberOfLookBackEpochs))
+ .isEqualTo(expectedReturnedTopicsMap);
+
+ // To test passing in a non-existing Column name
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(Pair.create(TopicsTables.ReturnedTopicContract.TABLE, "Some Column")),
+ /* Values to Delete */ List.of(app),
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ /* epoch Id */ String.valueOf(epochId1),
+ /* isStringEqualConditionColumnValue */ false);
+ assertThat(mTopicsDao.retrieveReturnedTopics(epochId1, numberOfLookBackEpochs))
+ .isEqualTo(expectedReturnedTopicsMap);
+
+ // To tests passing in a non-existing Column name for Equal Condition
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.APP)),
+ /* Values to Delete */ List.of(app),
+ "Some Column Name",
+ /* epoch Id */ String.valueOf(epochId1),
+ /* isStringEqualConditionColumnValue */ false);
+ assertThat(mTopicsDao.retrieveReturnedTopics(epochId1, numberOfLookBackEpochs))
+ .isEqualTo(expectedReturnedTopicsMap);
+ }
+
+ @Test
+ public void testDeleteEntriesFromTableByColumnWithEqualCondition_emptyValuesToDelete() {
+ // Persist an entry to Returned Topics Table
+ final long epochId1 = 1L;
+ final int numberOfLookBackEpochs = 1;
+ final String app = "app";
+ final String sdk = "sdk";
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ mTopicsDao.persistReturnedAppTopicsMap(epochId1, Map.of(Pair.create(app, sdk), topic1));
+ Map<Long, Map<Pair<String, String>, Topic>> expectedReturnedTopicsMap =
+ Map.of(epochId1, Map.of(Pair.create(app, sdk), topic1));
+
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.APP)),
+ /* Values to Delete */ List.of(),
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ /* epoch Id */ String.valueOf(epochId1),
+ /* isStringEqualConditionColumnValue */ false);
+ assertThat(mTopicsDao.retrieveReturnedTopics(epochId1, numberOfLookBackEpochs))
+ .isEqualTo(expectedReturnedTopicsMap);
}
@Test
- public void testDeleteAppFromTable_mismatchedTableAndColumnName() {
+ public void testDeleteFromTableByColumn_mismatchedTableAndColumnName() {
// Test with AppClassificationTopics Contract
final long taxonomyVersion = 1L;
final long modelVersion = 1L;
@@ -826,23 +1008,56 @@ public final class TopicsDaoTest {
assertThat(topicsMapFromDb1).isEqualTo(expectedTopicsMap1);
// To Test a table that doesn't have "app" column
- mTopicsDao.deleteAppFromTable(
- TopicsTables.TaxonomyContract.TABLE,
- TopicsTables.AppClassificationTopicsContract.APP,
+ mTopicsDao.deleteFromTableByColumn(
+ List.of(
+ Pair.create(
+ TopicsTables.TaxonomyContract.TABLE,
+ TopicsTables.AppClassificationTopicsContract.APP)),
List.of(app1));
// Nothing will happen as no satisfied entry to delete
assertThat(topicsMapFromDb1).isEqualTo(expectedTopicsMap1);
// To Test table with wrong app column name
- mTopicsDao.deleteAppFromTable(
- TopicsTables.AppClassificationTopicsContract.TABLE,
- "wrong app column name",
+ mTopicsDao.deleteFromTableByColumn(
+ List.of(
+ Pair.create(
+ TopicsTables.AppClassificationTopicsContract.TABLE,
+ "wrong app column name")),
List.of(app1));
// Nothing will happen as no satisfied entry to delete
assertThat(topicsMapFromDb1).isEqualTo(expectedTopicsMap1);
}
@Test
+ public void testDeleteFromTableByColumn_emptyListToDelete() {
+ // Test with AppClassificationTopics Contract
+ final long epochId1 = 1L;
+ final String app = "app";
+
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+
+ mTopicsDao.persistAppClassificationTopics(epochId1, Map.of(app, List.of(topic1)));
+
+ // Test passing in an empty list
+ mTopicsDao.deleteFromTableByColumn(
+ List.of(
+ Pair.create(
+ TopicsTables.AppClassificationTopicsContract.TABLE,
+ TopicsTables.AppClassificationTopicsContract.APP)),
+ List.of());
+
+ assertThat(mTopicsDao.retrieveAppClassificationTopics(epochId1))
+ .isEqualTo(Map.of(app, List.of(topic1)));
+ }
+
+ @Test
+ public void testDeleteFromTableByColumn_nonExistingTable() {
+ // Test the process doesn't throw with non-existing table
+ mTopicsDao.deleteFromTableByColumn(
+ List.of(Pair.create("Some Table", "Some Column")), List.of());
+ }
+
+ @Test
public void testPersistAndRetrieveEpochOrigin() {
final long epochOrigin = 1234567890L;
@@ -874,4 +1089,56 @@ public final class TopicsDaoTest {
// Should return -1 if no origin is persisted
assertThat(mTopicsDao.retrieveEpochOrigin()).isEqualTo(-1);
}
+
+ @Test
+ public void testPersistAndRetrieveTopicContributors() {
+ final long epochId = 1L;
+ final int topicId1 = 1;
+ final int topicId2 = 2;
+ final int topicId3 = 3;
+ final String app1 = "app1";
+ final String app2 = "app2";
+
+ Map<Integer, Set<String>> topicToContributorsMap =
+ Map.of(
+ topicId1, Set.of(app1),
+ topicId2, Set.of(app1, app2));
+
+ mTopicsDao.persistTopicContributors(epochId, topicToContributorsMap);
+ // Trying to persist a topic without contributors, which will be ignored.
+ mTopicsDao.persistTopicContributors(epochId, Map.of(topicId3, Set.of()));
+
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId))
+ .isEqualTo(topicToContributorsMap);
+ }
+
+ @Test
+ public void testPersistAndRetrieveTopicContributors_nullMap() {
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mTopicsDao.persistTopicContributors(
+ /* epochId */ 1L, /* topicToContributorsMap */ null));
+ }
+
+ @Test
+ public void testPersistAndRetrieveTopicContributors_emptyMap() {
+ final long epochId = 1L;
+
+ mTopicsDao.persistTopicContributors(epochId, Map.of());
+
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId)).isEmpty();
+ }
+
+ @Test
+ public void testSupportsTopicContributorsTable() {
+ DbHelper dbHelper = Mockito.mock(DbHelper.class);
+ TopicsDao topicsDao = new TopicsDao(dbHelper);
+
+ Mockito.when(dbHelper.supportsTopicContributorsTable()).thenReturn(false);
+ assertThat(topicsDao.supportsTopicContributorsTable()).isFalse();
+
+ Mockito.when(dbHelper.supportsTopicContributorsTable()).thenReturn(true);
+ assertThat(topicsDao.supportsTopicContributorsTable()).isTrue();
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/AbstractTopicsDbMigratorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/AbstractTopicsDbMigratorTest.java
new file mode 100644
index 000000000..0f4317024
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/AbstractTopicsDbMigratorTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.data.topics.migration;
+
+import static org.junit.Assert.assertThrows;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link com.android.adservices.data.topics.migration.AbstractTopicsDbMigrator} */
+public class AbstractTopicsDbMigratorTest {
+ @Mock private SQLiteDatabase mDb;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testPerformMigration_onUpgrade() {
+ // Test targetVersion is newer than newVersion on upgrading
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ initMigrator(/* targetVersion */ 3)
+ .performMigration(mDb, /* oldVersion */ 1, /* newVersion */ 2));
+
+ // Test targetVersion is not newer than oldVersion on upgrading
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ initMigrator(/* targetVersion */ 1)
+ .performMigration(mDb, /* oldVersion */ 1, /* newVersion */ 2));
+
+ // Test to perform on Upgrading
+ initMigrator(/* targetVersion */ 2)
+ .performMigration(mDb, /* oldVersion */ 1, /* newVersion */ 2);
+ }
+
+ // Initialize the abstractTopicsDbMigrator with a target version. Use isPerformed to indicate
+ // when performMigration is invoked.
+ private AbstractTopicsDbMigrator initMigrator(int targetVersion) {
+ return new AbstractTopicsDbMigrator(targetVersion) {
+ @Override
+ public void performMigration(SQLiteDatabase db) {}
+ };
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/TopicDbMigratorV3Test.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/TopicDbMigratorV3Test.java
new file mode 100644
index 000000000..a6e535631
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/TopicDbMigratorV3Test.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.data.topics.migration;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.data.DbTestUtil;
+import com.android.adservices.data.topics.TopicsTables;
+
+import org.junit.Test;
+
+/** Unit tests for {@link com.android.adservices.data.topics.migration.TopicDbMigratorV3} */
+public class TopicDbMigratorV3Test {
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+ // The database is created with V2 and will migrate to V3.
+ private final TopicsDbHelperV2 mTopicsDbHelper = TopicsDbHelperV2.getInstance(sContext);
+
+ @Test
+ public void testDbMigrationFromV2ToV3() {
+ SQLiteDatabase db = mTopicsDbHelper.getWritableDatabase();
+
+ // TopicContributors table doesn't exist in V2
+ assertThat(
+ DbTestUtil.doesTableExistAndColumnCountMatch(
+ db,
+ TopicsTables.TopicContributorsContract.TABLE,
+ /* number Of Columns */ 4))
+ .isFalse();
+
+ // Use transaction here so that the changes in the performMigration is committed.
+ // In normal DB upgrade, the DB will commit the change automatically.
+ db.beginTransaction();
+ // Upgrade the db V3 by using TopicDbMigratorV3
+ new TopicDbMigratorV3().performMigration(db);
+ // Commit the schema change
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ // TopicContributors table exists in V3
+ db = mTopicsDbHelper.getReadableDatabase();
+ assertThat(
+ DbTestUtil.doesTableExistAndColumnCountMatch(
+ db,
+ TopicsTables.TopicContributorsContract.TABLE,
+ /* number Of Columns */ 4))
+ .isTrue();
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/TopicsDbHelperV2.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/TopicsDbHelperV2.java
new file mode 100644
index 000000000..58fcabb44
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/topics/migration/TopicsDbHelperV2.java
@@ -0,0 +1,98 @@
+/*
+ * 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.data.topics.migration;
+
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_APP_CLASSIFICATION_TOPICS;
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_APP_USAGE_HISTORY;
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_BLOCKED_TOPICS;
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_CALLER_CAN_LEARN_TOPICS;
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_EPOCH_ORIGIN;
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_RETURNED_TOPIC;
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_TOPICS_TAXONOMY;
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_TOP_TOPICS;
+import static com.android.adservices.data.topics.TopicsTables.CREATE_TABLE_USAGE_HISTORY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.adservices.data.DbHelper;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class to get the state of Topics' database on version 2 for test purpose. V2 doesn't include
+ * TopicContributors Table.
+ */
+public class TopicsDbHelperV2 extends DbHelper {
+ private static final int CURRENT_DATABASE_VERSION = 2;
+ // TODO(b/255964885): Consolidate DB Migrator Class across Rubidium
+ private static final String DATABASE_NAME_TOPICS_MIGRATION = "adservices_topics_migration.db";
+ private static TopicsDbHelperV2 sSingleton = null;
+
+ TopicsDbHelperV2(Context context, String dbName, int dbVersion) {
+ super(context, dbName, dbVersion);
+ }
+
+ /** Returns an instance of the DbHelper given a context. */
+ @NonNull
+ public static TopicsDbHelperV2 getInstance(@NonNull Context context) {
+ synchronized (TopicsDbHelperV2.class) {
+ if (sSingleton == null) {
+ clearDatabase(context);
+ sSingleton =
+ new TopicsDbHelperV2(
+ context, DATABASE_NAME_TOPICS_MIGRATION, CURRENT_DATABASE_VERSION);
+ }
+ return sSingleton;
+ }
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ for (String sql : TOPICS_CREATE_STATEMENT_V2) {
+ db.execSQL(sql);
+ }
+ }
+
+ // Clear the database. Ensure there is no stale database with a different version existed.
+ private static void clearDatabase(@NonNull Context context) {
+ File databaseFile = context.getDatabasePath(DATABASE_NAME_TOPICS_MIGRATION);
+ if (databaseFile.exists()) {
+ assertThat(databaseFile.delete()).isTrue();
+ }
+ }
+
+ // This will create tables for DB V2.
+ private static final List<String> TOPICS_CREATE_STATEMENT_V2 =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ CREATE_TABLE_TOPICS_TAXONOMY,
+ CREATE_TABLE_APP_CLASSIFICATION_TOPICS,
+ CREATE_TABLE_TOP_TOPICS,
+ CREATE_TABLE_RETURNED_TOPIC,
+ CREATE_TABLE_USAGE_HISTORY,
+ CREATE_TABLE_APP_USAGE_HISTORY,
+ CREATE_TABLE_CALLER_CAN_LEARN_TOPICS,
+ CREATE_TABLE_BLOCKED_TOPICS,
+ CREATE_TABLE_EPOCH_ORIGIN));
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/download/MobileDataDownloadTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/download/MobileDataDownloadTest.java
index 084e873bd..ed448a897 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/download/MobileDataDownloadTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/download/MobileDataDownloadTest.java
@@ -34,6 +34,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.google.android.libraries.mobiledatadownload.AddFileGroupRequest;
import com.google.android.libraries.mobiledatadownload.DownloadFileGroupRequest;
+import com.google.android.libraries.mobiledatadownload.GetFileGroupRequest;
import com.google.android.libraries.mobiledatadownload.Logger;
import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
import com.google.android.libraries.mobiledatadownload.TaskScheduler;
@@ -46,7 +47,6 @@ import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.Devi
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -73,6 +73,13 @@ public class MobileDataDownloadTest {
private static final String FILE_URL_2 = "https://www.gstatic.com/suggest-dev/odws1_empty.jar";
private static final int FILE_SIZE_2 = 554;
+ private static final String TEST_MDD_TOPICS_CLASSIFIER_MANIFEST_FILE_URL =
+ "https://www.gstatic.com/mdi-serving/rubidium-adservices-topics-classifier/922/217081737fd739c74dd3ca5c407813d818526577";
+ private static final String TEST_MDD_ENROLLMENT_MANIFEST_FILE_URL =
+ "https://dl.google.com/mdi-serving/adservices/adtech_enrollment/manifest_configs/1/manifest_config_1658790241927.binaryproto";
+ public static final String TEST_TOPIC_FILE_GROUP_NAME = "topics-classifier-model";
+ public static final String TEST_ENROLLMENT_FILE_GROUP_NAME = "adtech_enrollment_data";
+
private StaticMockitoSession mStaticMockSession = null;
@Mock Flags mMockFlags;
@@ -136,16 +143,63 @@ public class MobileDataDownloadTest {
assertThat(clientFileGroup.hasAccount()).isFalse();
}
- @Ignore
+ /**
+ * This method tests topics and measurement manifest files. It downloads test classifier model
+ * and adtech enrollment data and verifies files downloaded successfully.
+ */
@Test
- public void testTopicsManifestFileGroupPopulator()
+ public void testManifestFileGroupPopulator()
throws ExecutionException, InterruptedException, TimeoutException {
+ ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlagsForTest);
+ // Return a manifest URL for test only. This will download smaller size files only for
+ // testing MDD download feature.
+ doReturn(TEST_MDD_TOPICS_CLASSIFIER_MANIFEST_FILE_URL)
+ .when(mMockFlags)
+ .getMddTopicsClassifierManifestFileUrl();
+ doReturn(/*Download max download threads */ 2)
+ .when(mMockFlags)
+ .getDownloaderMaxDownloadThreads();
+ // Return production enrollment manifest file. This is a small file that will not affect
+ // running this test.
+ doReturn(TEST_MDD_ENROLLMENT_MANIFEST_FILE_URL)
+ .when(mMockFlags)
+ .getMeasurementManifestFileUrl();
+
MobileDataDownload mdd =
MobileDataDownloadFactory.getMddForTesting(
mContext, FlagsFactory.getFlagsForTest());
mdd.handleTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
+
+ ClientFileGroup clientFileGroup =
+ mdd.getFileGroup(
+ GetFileGroupRequest.newBuilder()
+ .setGroupName(TEST_TOPIC_FILE_GROUP_NAME)
+ .build())
+ .get();
+
+ // Verify topics file group.
+ assertThat(clientFileGroup.getGroupName()).isEqualTo(TEST_TOPIC_FILE_GROUP_NAME);
+ assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(mContext.getPackageName());
+ assertThat(clientFileGroup.getVersionNumber())
+ .isEqualTo(/* Test filegroup version number */ 0);
+ assertThat(clientFileGroup.getFileCount()).isEqualTo(6);
+ assertThat(clientFileGroup.getBuildId()).isEqualTo(/* BuildID generated by Ingress */ 922);
+
+ clientFileGroup =
+ mdd.getFileGroup(
+ GetFileGroupRequest.newBuilder()
+ .setGroupName(TEST_ENROLLMENT_FILE_GROUP_NAME)
+ .build())
+ .get();
+
+ // Verify measurement file group
+ assertThat(clientFileGroup.getGroupName()).isEqualTo(TEST_ENROLLMENT_FILE_GROUP_NAME);
+ assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(mContext.getPackageName());
+ assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
+ assertThat(clientFileGroup.getVersionNumber())
+ .isEqualTo(/* Measurement filegroup version number */ 1);
}
// A helper function to create a DataFilegroup.
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java
index d625836a6..4513beb23 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java
@@ -17,19 +17,22 @@
package com.android.adservices.service;
import static com.android.adservices.service.AdServicesConfig.MAINTENANCE_JOB_ID;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -37,8 +40,12 @@ import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
+import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
+import com.android.adservices.data.adselection.AdSelectionDatabase;
+import com.android.adservices.data.adselection.AdSelectionEntryDao;
+import com.android.adservices.service.common.FledgeMaintenanceTasksWorker;
import com.android.adservices.service.topics.AppUpdateManager;
import com.android.adservices.service.topics.BlockedTopicsManager;
import com.android.adservices.service.topics.CacheManager;
@@ -50,15 +57,19 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.Spy;
+import org.mockito.quality.Strictness;
/** Unit tests for {@link com.android.adservices.service.MaintenanceJobService} */
@SuppressWarnings("ConstantConditions")
public class MaintenanceJobServiceTest {
private static final int BACKGROUND_THREAD_TIMEOUT_MS = 5_000;
+ private static final long MAINTENANCE_JOB_PERIOD_MS = 10_000L;
+ private static final long MAINTENANCE_JOB_FLEX_MS = 1_000L;
+ private static final long CURRENT_EPOCH_ID = 1L;
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
private static final JobScheduler JOB_SCHEDULER = CONTEXT.getSystemService(JobScheduler.class);
private static final Flags TEST_FLAGS = FlagsFactory.getFlagsForTest();
@@ -74,10 +85,20 @@ public class MaintenanceJobServiceTest {
@Mock AppUpdateManager mMockAppUpdateManager;
@Mock JobParameters mMockJobParameters;
@Mock Flags mMockFlags;
+ @Mock JobScheduler mMockJobScheduler;
+ private AdSelectionEntryDao mAdSelectionEntryDao;
+ @Spy private FledgeMaintenanceTasksWorker mFledgeMaintenanceTasksWorkerSpy;
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
+ mAdSelectionEntryDao =
+ Room.inMemoryDatabaseBuilder(
+ ApplicationProvider.getApplicationContext(),
+ AdSelectionDatabase.class)
+ .build()
+ .adSelectionEntryDao();
+
+ mFledgeMaintenanceTasksWorkerSpy = new FledgeMaintenanceTasksWorker(mAdSelectionEntryDao);
// Start a mockitoSession to mock static method
mStaticMockSession =
@@ -85,6 +106,8 @@ public class MaintenanceJobServiceTest {
.spyStatic(MaintenanceJobService.class)
.spyStatic(TopicsWorker.class)
.spyStatic(FlagsFactory.class)
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
.startMocking();
// Mock JobScheduler invocation in EpochJobService
@@ -97,7 +120,9 @@ public class MaintenanceJobServiceTest {
@After
public void teardown() {
JOB_SCHEDULER.cancelAll();
- mStaticMockSession.finishMocking();
+ if (mStaticMockSession != null) {
+ mStaticMockSession.finishMocking();
+ }
}
@Test
@@ -109,9 +134,86 @@ public class MaintenanceJobServiceTest {
mBlockedTopicsManager,
mMockAppUpdateManager,
TEST_FLAGS);
+ // Killswitch is off.
+ doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
+ doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId();
+ doReturn(TEST_FLAGS.getAdSelectionExpirationWindowS())
+ .when(mMockFlags)
+ .getAdSelectionExpirationWindowS();
+ // Mock static method FlagsFactory.getFlags() to return Mock Flags.
+ ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+
+ // Mock static method AppUpdateWorker.getInstance, let it return the local
+ // appUpdateWorker in order to get a test instance.
+ ExtendedMockito.doReturn(topicsWorker)
+ .when(() -> TopicsWorker.getInstance(any(Context.class)));
+
+ mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker(
+ mFledgeMaintenanceTasksWorkerSpy);
+
+ mSpyMaintenanceJobService.onStartJob(mMockJobParameters);
+
+ // Grant some time to allow background thread to execute
+ Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS);
+
+ ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class)));
+ verify(mMockAppUpdateManager)
+ .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID));
+ verify(mMockAppUpdateManager)
+ .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong());
+
+ // Ensure Fledge job was done
+ verify(mFledgeMaintenanceTasksWorkerSpy).clearExpiredAdSelectionData();
+ }
+
+ @Test
+ public void testOnStartJob_TopicsKillSwitchOn() throws InterruptedException {
+ // Killswitch is off.
+ doReturn(true).when(mMockFlags).getTopicsKillSwitch();
+ doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
+ doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId();
+
+ // Mock static method FlagsFactory.getFlags() to return Mock Flags.
+ ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+ doReturn(TEST_FLAGS.getAdSelectionExpirationWindowS())
+ .when(mMockFlags)
+ .getAdSelectionExpirationWindowS();
+
+ // Inject FledgeMaintenanceTasksWorker since the test can't get it the standard way
+ mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker(
+ mFledgeMaintenanceTasksWorkerSpy);
+
+ mSpyMaintenanceJobService.onStartJob(mMockJobParameters);
+
+ // Grant some time to allow background thread to execute
+ Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS);
+
+ // Verify that topics job is not done
+ ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class)), never());
+ verify(mMockAppUpdateManager, never())
+ .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID));
+ verify(mMockAppUpdateManager, never())
+ .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong());
+
+ // Verifying that FLEDGE job was done
+ verify(mFledgeMaintenanceTasksWorkerSpy).clearExpiredAdSelectionData();
+ }
+
+ @Test
+ public void testOnStartJob_SelectAdsKillSwitchOn() throws InterruptedException {
+ final TopicsWorker topicsWorker =
+ new TopicsWorker(
+ mMockEpochManager,
+ mMockCacheManager,
+ mBlockedTopicsManager,
+ mMockAppUpdateManager,
+ TEST_FLAGS);
// Killswitch is off.
doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ doReturn(true).when(mMockFlags).getFledgeSelectAdsKillSwitch();
+ doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId();
// Mock static method FlagsFactory.getFlags() to return Mock Flags.
ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
@@ -127,29 +229,36 @@ public class MaintenanceJobServiceTest {
Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS);
ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class)));
- verify(mMockAppUpdateManager).reconcileUninstalledApps(any(Context.class));
+ verify(mMockAppUpdateManager)
+ .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID));
verify(mMockAppUpdateManager)
.reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong());
+ verify(mFledgeMaintenanceTasksWorkerSpy, never()).clearExpiredAdSelectionData();
}
@Test
- public void testOnStartJob_killSwitchOn() throws InterruptedException {
+ public void testOnStartJob_killSwitchOn() {
// Killswitch on.
doReturn(true).when(mMockFlags).getTopicsKillSwitch();
+ doReturn(true).when(mMockFlags).getFledgeSelectAdsKillSwitch();
// Mock static method FlagsFactory.getFlags() to return Mock Flags.
ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
doNothing().when(mSpyMaintenanceJobService).jobFinished(mMockJobParameters, false);
+ // Inject FledgeMaintenanceTasksWorker since the test can't get it the standard way
+ mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker(
+ mFledgeMaintenanceTasksWorkerSpy);
+
// Schedule the job to assert after starting that the scheduled job has been cancelled
JobInfo existingJobInfo =
new JobInfo.Builder(
MAINTENANCE_JOB_ID,
new ComponentName(CONTEXT, EpochJobService.class))
.setRequiresCharging(true)
- .setPeriodic(
- /* maintenanceJobPeriodMs */ 10000, /* maintenanceJobFlexMs */ 1000)
+ .setPeriodic(MAINTENANCE_JOB_PERIOD_MS, MAINTENANCE_JOB_FLEX_MS)
+ .setPersisted(true)
.build();
JOB_SCHEDULER.schedule(existingJobInfo);
assertNotNull(JOB_SCHEDULER.getPendingJob(MAINTENANCE_JOB_ID));
@@ -161,25 +270,145 @@ public class MaintenanceJobServiceTest {
verify(mSpyMaintenanceJobService).jobFinished(mMockJobParameters, false);
verifyNoMoreInteractions(staticMockMarker(TopicsWorker.class));
+ verify(mFledgeMaintenanceTasksWorkerSpy, never()).clearExpiredAdSelectionData();
}
@Test
- public void testOnStartJob_globalKillswitchOverridesAll() throws InterruptedException {
- // Global Killswitch is on.
- doReturn(true).when(mMockFlags).getGlobalKillSwitch();
+ public void testOnStartJob_killSwitchOnDoesFledgeJobWhenTopicsJobThrowsException()
+ throws Exception {
+ final TopicsWorker topicsWorker =
+ new TopicsWorker(
+ mMockEpochManager,
+ mMockCacheManager,
+ mBlockedTopicsManager,
+ mMockAppUpdateManager,
+ TEST_FLAGS);
+ // Killswitch is off.
+ doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
+ doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId();
+ doReturn(TEST_FLAGS.getAdSelectionExpirationWindowS())
+ .when(mMockFlags)
+ .getAdSelectionExpirationWindowS();
+
+ // Mock static method FlagsFactory.getFlags() to return Mock Flags.
+ ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+
+ // Mock static method AppUpdateWorker.getInstance, let it return the local
+ // appUpdateWorker in order to get a test instance.
+ ExtendedMockito.doReturn(topicsWorker)
+ .when(() -> TopicsWorker.getInstance(any(Context.class)));
+
+ // Inject FledgeMaintenanceTasksWorker since the test can't get it the standard way
+ mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker(
+ mFledgeMaintenanceTasksWorkerSpy);
+
+ // Simulating a failure in Topics job
+ ExtendedMockito.doThrow(new IllegalStateException())
+ .when(mMockAppUpdateManager)
+ .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID));
- // Killswitch off.
+ mSpyMaintenanceJobService.onStartJob(mMockJobParameters);
+
+ // Grant some time to allow background thread to execute
+ Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS);
+
+ ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class)));
+ verify(mMockAppUpdateManager)
+ .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID));
+ // Verify that this is not called because we threw an exception
+ verify(mMockAppUpdateManager, never())
+ .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong());
+
+ // Verifying that FLEDGE job was done
+ verify(mFledgeMaintenanceTasksWorkerSpy).clearExpiredAdSelectionData();
+ }
+
+ @Test
+ public void testOnStartJob_killSwitchOnDoesTopicsJobWhenFledgeThrowsException()
+ throws Exception {
+ final TopicsWorker topicsWorker =
+ new TopicsWorker(
+ mMockEpochManager,
+ mMockCacheManager,
+ mBlockedTopicsManager,
+ mMockAppUpdateManager,
+ TEST_FLAGS);
+ // Killswitch is off.
doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
+ doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId();
// Mock static method FlagsFactory.getFlags() to return Mock Flags.
ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+ // Mock static method AppUpdateWorker.getInstance, let it return the local
+ // appUpdateWorker in order to get a test instance.
+ ExtendedMockito.doReturn(topicsWorker)
+ .when(() -> TopicsWorker.getInstance(any(Context.class)));
+
+ // Inject FledgeMaintenanceTasksWorker since the test can't get it the standard way
+ mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker(
+ mFledgeMaintenanceTasksWorkerSpy);
+
+ // Simulating a failure in Fledge job
+ ExtendedMockito.doThrow(new IllegalStateException())
+ .when(mFledgeMaintenanceTasksWorkerSpy)
+ .clearExpiredAdSelectionData();
+
+ mSpyMaintenanceJobService.onStartJob(mMockJobParameters);
+
+ // Grant some time to allow background thread to execute
+ Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS);
+
+ ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class)));
+ verify(mMockAppUpdateManager)
+ .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID));
+ verify(mMockAppUpdateManager)
+ .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong());
+
+ verify(mFledgeMaintenanceTasksWorkerSpy).clearExpiredAdSelectionData();
+ }
+
+ @Test
+ public void testOnStartJob_globalKillswitchOverridesAll() throws InterruptedException {
+ Flags flagsGlobalKillSwitchOn =
+ new Flags() {
+ @Override
+ public boolean getGlobalKillSwitch() {
+ return true;
+ }
+ };
+
+ // Setting mock flags to use flags with global switch overridden
+ doReturn(flagsGlobalKillSwitchOn.getTopicsKillSwitch())
+ .when(mMockFlags)
+ .getTopicsKillSwitch();
+ doReturn(flagsGlobalKillSwitchOn.getTopicsKillSwitch())
+ .when(mMockFlags)
+ .getFledgeSelectAdsKillSwitch();
+ doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId();
+
+ // Mock static method FlagsFactory.getFlags() to return Mock Flags.
+ ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+
+ ExtendedMockito.doNothing()
+ .when(mSpyMaintenanceJobService)
+ .jobFinished(mMockJobParameters, false);
+
mSpyMaintenanceJobService.onStartJob(mMockJobParameters);
// Grant some time to allow background thread to execute
Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS);
- // When the kill switch is on, the MaintenanceJobService exits early and do nothing.
+ verify(() -> TopicsWorker.getInstance(any(Context.class)), never());
+ verify(mMockAppUpdateManager, never())
+ .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID));
+ verify(mMockAppUpdateManager, never())
+ .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong());
+
+ // Ensure Fledge job not was done
+ verify(mFledgeMaintenanceTasksWorkerSpy, never()).clearExpiredAdSelectionData();
}
@Test
@@ -256,7 +485,7 @@ public class MaintenanceJobServiceTest {
}
@Test
- public void testScheduleIfNeeded_scheduledWithKillSwichOn() {
+ public void testScheduleIfNeeded_scheduledWithKillSwitchOn() {
// Killswitch is on.
doReturn(true).when(mMockFlags).getTopicsKillSwitch();
@@ -267,4 +496,16 @@ public class MaintenanceJobServiceTest {
assertThat(EpochJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isFalse();
assertThat(JOB_SCHEDULER.getPendingJob(MAINTENANCE_JOB_ID)).isNull();
}
+
+ @Test
+ public void testSchedule_jobInfoIsPersisted() {
+ final ArgumentCaptor<JobInfo> argumentCaptor = ArgumentCaptor.forClass(JobInfo.class);
+
+ MaintenanceJobService.schedule(
+ CONTEXT, mMockJobScheduler, MAINTENANCE_JOB_PERIOD_MS, MAINTENANCE_JOB_FLEX_MS);
+
+ verify(mMockJobScheduler, times(1)).schedule(argumentCaptor.capture());
+ assertThat(argumentCaptor.getValue()).isNotNull();
+ assertThat(argumentCaptor.getValue().isPersisted()).isTrue();
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
index 5fc24c523..e9351fac7 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
@@ -19,8 +19,10 @@ package com.android.adservices.service;
import static com.android.adservices.service.Flags.ADID_KILL_SWITCH;
import static com.android.adservices.service.Flags.ADSERVICES_ENABLED;
import static com.android.adservices.service.Flags.APPSETID_KILL_SWITCH;
+import static com.android.adservices.service.Flags.ASYNC_REGISTRATION_JOB_QUEUE_INTERVAL_MS;
import static com.android.adservices.service.Flags.CLASSIFIER_DESCRIPTION_MAX_LENGTH;
import static com.android.adservices.service.Flags.CLASSIFIER_DESCRIPTION_MAX_WORDS;
+import static com.android.adservices.service.Flags.CLASSIFIER_FORCE_USE_BUNDLED_FILES;
import static com.android.adservices.service.Flags.CLASSIFIER_NUMBER_OF_TOP_LABELS;
import static com.android.adservices.service.Flags.CLASSIFIER_THRESHOLD;
import static com.android.adservices.service.Flags.DEFAULT_CLASSIFIER_TYPE;
@@ -30,14 +32,21 @@ import static com.android.adservices.service.Flags.DISABLE_TOPICS_ENROLLMENT_CHE
import static com.android.adservices.service.Flags.DOWNLOADER_CONNECTION_TIMEOUT_MS;
import static com.android.adservices.service.Flags.DOWNLOADER_MAX_DOWNLOAD_THREADS;
import static com.android.adservices.service.Flags.DOWNLOADER_READ_TIMEOUT_MS;
+import static com.android.adservices.service.Flags.ENABLE_DATABASE_SCHEMA_VERSION_3;
+import static com.android.adservices.service.Flags.ENABLE_TOPIC_CONTRIBUTORS_CHECK;
import static com.android.adservices.service.Flags.ENFORCE_FOREGROUND_STATUS_FLEDGE_CUSTOM_AUDIENCE;
import static com.android.adservices.service.Flags.ENFORCE_FOREGROUND_STATUS_FLEDGE_OVERRIDES;
import static com.android.adservices.service.Flags.ENFORCE_FOREGROUND_STATUS_FLEDGE_REPORT_IMPRESSION;
import static com.android.adservices.service.Flags.ENFORCE_FOREGROUND_STATUS_FLEDGE_RUN_AD_SELECTION;
import static com.android.adservices.service.Flags.ENFORCE_FOREGROUND_STATUS_TOPICS;
import static com.android.adservices.service.Flags.ENFORCE_ISOLATE_MAX_HEAP_SIZE;
+import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS;
import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_CA_MS;
import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_CONCURRENT_BIDDING_COUNT;
+import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S;
+import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_OFF_DEVICE_ENABLED;
+import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS;
+import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED;
import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS;
import static com.android.adservices.service.Flags.FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS;
import static com.android.adservices.service.Flags.FLEDGE_BACKGROUND_FETCH_ELIGIBLE_UPDATE_BASE_INTERVAL_S;
@@ -96,6 +105,7 @@ import static com.android.adservices.service.Flags.MEASUREMENT_JOB_AGGREGATE_FAL
import static com.android.adservices.service.Flags.MEASUREMENT_JOB_AGGREGATE_REPORTING_KILL_SWITCH;
import static com.android.adservices.service.Flags.MEASUREMENT_JOB_ATTRIBUTION_KILL_SWITCH;
import static com.android.adservices.service.Flags.MEASUREMENT_JOB_DELETE_EXPIRED_KILL_SWITCH;
+import static com.android.adservices.service.Flags.MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH;
import static com.android.adservices.service.Flags.MEASUREMENT_JOB_EVENT_FALLBACK_REPORTING_KILL_SWITCH;
import static com.android.adservices.service.Flags.MEASUREMENT_JOB_EVENT_REPORTING_KILL_SWITCH;
import static com.android.adservices.service.Flags.MEASUREMENT_KILL_SWITCH;
@@ -105,6 +115,7 @@ import static com.android.adservices.service.Flags.MEASUREMENT_NETWORK_READ_TIME
import static com.android.adservices.service.Flags.MEASUREMENT_RECEIVER_DELETE_PACKAGES_KILL_SWITCH;
import static com.android.adservices.service.Flags.MEASUREMENT_RECEIVER_INSTALL_ATTRIBUTION_KILL_SWITCH;
import static com.android.adservices.service.Flags.MEASUREMENT_REGISTRATION_INPUT_EVENT_VALID_WINDOW_MS;
+import static com.android.adservices.service.Flags.MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH;
import static com.android.adservices.service.Flags.NUMBER_OF_EPOCHS_TO_KEEP_IN_HISTORY;
import static com.android.adservices.service.Flags.PPAPI_APP_ALLOW_LIST;
import static com.android.adservices.service.Flags.PPAPI_APP_SIGNATURE_ALLOW_LIST;
@@ -122,6 +133,7 @@ import static com.android.adservices.service.PhFlags.KEY_ADSERVICES_ENABLED;
import static com.android.adservices.service.PhFlags.KEY_APPSETID_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_CLASSIFIER_DESCRIPTION_MAX_LENGTH;
import static com.android.adservices.service.PhFlags.KEY_CLASSIFIER_DESCRIPTION_MAX_WORDS;
+import static com.android.adservices.service.PhFlags.KEY_CLASSIFIER_FORCE_USE_BUNDLED_FILES;
import static com.android.adservices.service.PhFlags.KEY_CLASSIFIER_NUMBER_OF_TOP_LABELS;
import static com.android.adservices.service.PhFlags.KEY_CLASSIFIER_THRESHOLD;
import static com.android.adservices.service.PhFlags.KEY_CLASSIFIER_TYPE;
@@ -131,10 +143,18 @@ import static com.android.adservices.service.PhFlags.KEY_DISABLE_TOPICS_ENROLLME
import static com.android.adservices.service.PhFlags.KEY_DOWNLOADER_CONNECTION_TIMEOUT_MS;
import static com.android.adservices.service.PhFlags.KEY_DOWNLOADER_MAX_DOWNLOAD_THREADS;
import static com.android.adservices.service.PhFlags.KEY_DOWNLOADER_READ_TIMEOUT_MS;
+import static com.android.adservices.service.PhFlags.KEY_ENABLE_DATABASE_SCHEMA_VERSION_3;
+import static com.android.adservices.service.PhFlags.KEY_ENABLE_TOPIC_CONTRIBUTORS_CHECK;
import static com.android.adservices.service.PhFlags.KEY_ENFORCE_FOREGROUND_STATUS_TOPICS;
import static com.android.adservices.service.PhFlags.KEY_ENFORCE_ISOLATE_MAX_HEAP_SIZE;
+import static com.android.adservices.service.PhFlags.KEY_ENROLLMENT_BLOCKLIST_IDS;
+import static com.android.adservices.service.PhFlags.KEY_FLEDE_AD_SELECTION_OFF_DEVICE_ENABLED;
+import static com.android.adservices.service.PhFlags.KEY_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS;
import static com.android.adservices.service.PhFlags.KEY_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_CA_MS;
import static com.android.adservices.service.PhFlags.KEY_FLEDGE_AD_SELECTION_CONCURRENT_BIDDING_COUNT;
+import static com.android.adservices.service.PhFlags.KEY_FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S;
+import static com.android.adservices.service.PhFlags.KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS;
+import static com.android.adservices.service.PhFlags.KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED;
import static com.android.adservices.service.PhFlags.KEY_FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS;
import static com.android.adservices.service.PhFlags.KEY_FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS;
import static com.android.adservices.service.PhFlags.KEY_FLEDGE_BACKGROUND_FETCH_ELIGIBLE_UPDATE_BASE_INTERVAL_S;
@@ -194,6 +214,7 @@ import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_JOB_AGGREGA
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_JOB_AGGREGATE_REPORTING_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_JOB_ATTRIBUTION_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_JOB_DELETE_EXPIRED_KILL_SWITCH;
+import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_JOB_EVENT_FALLBACK_REPORTING_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_JOB_EVENT_REPORTING_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_KILL_SWITCH;
@@ -203,9 +224,11 @@ import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_NETWORK_REA
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_RECEIVER_DELETE_PACKAGES_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_RECEIVER_INSTALL_ATTRIBUTION_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_REGISTRATION_INPUT_EVENT_VALID_WINDOW_MS;
+import static com.android.adservices.service.PhFlags.KEY_MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH;
import static com.android.adservices.service.PhFlags.KEY_NUMBER_OF_EPOCHS_TO_KEEP_IN_HISTORY;
import static com.android.adservices.service.PhFlags.KEY_PPAPI_APP_ALLOW_LIST;
import static com.android.adservices.service.PhFlags.KEY_PPAPI_APP_SIGNATURE_ALLOW_LIST;
+import static com.android.adservices.service.PhFlags.KEY_REGISTRATION_JOB_QUEUE_INTERVAL_MS;
import static com.android.adservices.service.PhFlags.KEY_SDK_REQUEST_PERMITS_PER_SECOND;
import static com.android.adservices.service.PhFlags.KEY_TOPICS_EPOCH_JOB_FLEX_MS;
import static com.android.adservices.service.PhFlags.KEY_TOPICS_EPOCH_JOB_PERIOD_MS;
@@ -508,6 +531,23 @@ public class PhFlagsTest {
}
@Test
+ public void testGetClassifierForceUseBundledFiles() {
+ // Without any overriding, the value is the hard coded constant.
+ assertThat(FlagsFactory.getFlags().getClassifierForceUseBundledFiles())
+ .isEqualTo(CLASSIFIER_FORCE_USE_BUNDLED_FILES);
+
+ boolean phOverridingValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_CLASSIFIER_FORCE_USE_BUNDLED_FILES,
+ Boolean.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getClassifierForceUseBundledFiles()).isEqualTo(phOverridingValue);
+ }
+
+ @Test
public void testGetMaintenanceJobPeriodMs() {
// Without any overriding, the value is the hard coded constant.
assertThat(FlagsFactory.getFlags().getMaintenanceJobPeriodMs())
@@ -620,6 +660,23 @@ public class PhFlagsTest {
}
@Test
+ public void testGetAdSelectionBiddingTimeoutPerBuyerMs() {
+ // without any overriding, the value is hard coded constant
+ assertThat(FlagsFactory.getFlags().getAdSelectionBiddingTimeoutPerBuyerMs())
+ .isEqualTo(FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS);
+
+ final long phOverrideValue = 5000;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_BUYER_MS,
+ Long.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getAdSelectionBiddingTimeoutPerBuyerMs()).isEqualTo(phOverrideValue);
+ }
+
+ @Test
public void testGetAdSelectionScoringTimeoutMs() {
// without any overriding, the value is hard coded constant
assertThat(FlagsFactory.getFlags().getAdSelectionScoringTimeoutMs())
@@ -654,6 +711,22 @@ public class PhFlagsTest {
}
@Test
+ public void testGetOffDeviceAdSelectionOverallTimeoutMs() {
+ assertThat(FlagsFactory.getFlags().getAdSelectionOffDeviceOverallTimeoutMs())
+ .isEqualTo(FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS);
+
+ final int phOverrideValue = 4;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_OVERALL_TIMEOUT_MS,
+ Integer.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getAdSelectionOffDeviceOverallTimeoutMs()).isEqualTo(phOverrideValue);
+ }
+
+ @Test
public void testGetDownloaderConnectionTimeoutMs() {
// without any overriding, the value is hard coded constant
assertThat(FlagsFactory.getFlags().getDownloaderConnectionTimeoutMs())
@@ -2226,6 +2299,64 @@ public class PhFlagsTest {
}
@Test
+ public void testGetMeasurementJobDeleteUninstalledKillSwitch() {
+ // without any overriding, the value is hard coded constant
+ assertThat(FlagsFactory.getFlags().getMeasurementJobDeleteUninstalledKillSwitch())
+ .isEqualTo(MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH);
+
+ final boolean phOverrideValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH,
+ Boolean.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getMeasurementJobDeleteUninstalledKillSwitch())
+ .isEqualTo(phOverrideValue);
+
+ Flags flags = FlagsFactory.getFlagsForTest();
+ assertThat(flags.getMeasurementJobDeleteUninstalledKillSwitch())
+ .isEqualTo(MEASUREMENT_JOB_DELETE_UNINSTALLED_KILL_SWITCH);
+ }
+
+ @Test
+ public void testGetMeasurementJobDeleteUninstalledKillSwitch_measurementOverride() {
+ // without any overriding, the value is hard coded constant
+ assertThat(FlagsFactory.getFlags().getMeasurementJobDeleteUninstalledKillSwitch())
+ .isEqualTo(MEASUREMENT_JOB_DELETE_EXPIRED_KILL_SWITCH);
+
+ final boolean phOverrideValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_MEASUREMENT_KILL_SWITCH,
+ Boolean.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getMeasurementJobDeleteUninstalledKillSwitch())
+ .isEqualTo(phOverrideValue);
+ }
+
+ @Test
+ public void testGetMeasurementJobDeleteUninstalledKillSwitch_globalOverride() {
+ // without any overriding, the value is hard coded constant
+ assertThat(FlagsFactory.getFlags().getMeasurementJobDeleteUninstalledKillSwitch())
+ .isEqualTo(MEASUREMENT_JOB_DELETE_EXPIRED_KILL_SWITCH);
+
+ final boolean phOverrideValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_GLOBAL_KILL_SWITCH,
+ Boolean.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getMeasurementJobDeleteUninstalledKillSwitch())
+ .isEqualTo(phOverrideValue);
+ }
+
+ @Test
public void testGetMeasurementJobEventFallbackReportingKillSwitch() {
// without any overriding, the value is hard coded constant
assertThat(FlagsFactory.getFlags().getMeasurementJobEventFallbackReportingKillSwitch())
@@ -2455,6 +2586,77 @@ public class PhFlagsTest {
}
@Test
+ public void testGetAdIdKillSwitch_globalOverride() {
+ // test that global killswitch override has no effect on
+ // AdIdKillswitch.
+ assertThat(FlagsFactory.getFlags().getAdIdKillSwitch()).isEqualTo(ADID_KILL_SWITCH);
+
+ final boolean phOverrideValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_GLOBAL_KILL_SWITCH,
+ Boolean.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getAdIdKillSwitch()).isEqualTo(ADID_KILL_SWITCH);
+ }
+
+ @Test
+ public void testGetMeasurementRegistrationJobQueueKillSwitch() {
+ // without any overrides the Registration Job Queue kill switch should be off
+ assertThat(FlagsFactory.getFlags().getRegistrationJobQueueKillSwitch())
+ .isEqualTo(MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH);
+
+ // Now overriding with the value from PH.
+ final boolean phOverrideValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH,
+ Boolean.toString(phOverrideValue),
+ /* makeDefault */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getRegistrationJobQueueKillSwitch()).isEqualTo(phOverrideValue);
+ }
+
+ @Test
+ public void testGetMeasurementRegistrationJobQueueKillSwitch_measurementOverride() {
+ // without any overrides the Registration Job Queue kill switch should be off
+ assertThat(FlagsFactory.getFlags().getRegistrationJobQueueKillSwitch())
+ .isEqualTo(MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH);
+
+ // Now overriding with the value from PH.
+ final boolean phOverrideValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_MEASUREMENT_KILL_SWITCH,
+ Boolean.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getRegistrationJobQueueKillSwitch()).isEqualTo(phOverrideValue);
+ }
+
+ @Test
+ public void testGetMeasurementRegistrationJobQueueKillSwitch_globalOverride() {
+ // without any overrides the Registration Job Queue kill switch should be off
+ assertThat(FlagsFactory.getFlags().getRegistrationJobQueueKillSwitch())
+ .isEqualTo(MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH);
+
+ // Now overriding with the value from PH.
+ final boolean phOverrideValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_GLOBAL_KILL_SWITCH,
+ Boolean.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getRegistrationJobQueueKillSwitch()).isEqualTo(phOverrideValue);
+ }
+
+ @Test
public void testGetAdIdKillSwitch() {
// Without any overriding, the value is the hard coded constant.
assertThat(FlagsFactory.getFlags().getAdIdKillSwitch()).isEqualTo(ADID_KILL_SWITCH);
@@ -2543,8 +2745,25 @@ public class PhFlagsTest {
Flags phFlags = FlagsFactory.getFlags();
assertThat(phFlags.getGlobalKillSwitch()).isEqualTo(phOverridingValue);
- // Global Killswitch is on and overrides the getAdIdKillswitch.
- assertThat(FlagsFactory.getFlags().getAdIdKillSwitch()).isEqualTo(true);
+ // Global Killswitch is on, but is ignored by the getAdIdKillswitch.
+ assertThat(FlagsFactory.getFlags().getAdIdKillSwitch()).isEqualTo(false);
+ }
+
+ @Test
+ public void testGetAppSetIdKillSwitch_globalOverride() {
+ // test that global killswitch override has no effect on
+ // AppSetIdKillswitch.
+ assertThat(FlagsFactory.getFlags().getAppSetIdKillSwitch()).isEqualTo(APPSETID_KILL_SWITCH);
+
+ final boolean phOverrideValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_GLOBAL_KILL_SWITCH,
+ Boolean.toString(phOverrideValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getAppSetIdKillSwitch()).isEqualTo(APPSETID_KILL_SWITCH);
}
@Test
@@ -2567,8 +2786,8 @@ public class PhFlagsTest {
Flags phFlags = FlagsFactory.getFlags();
assertThat(phFlags.getGlobalKillSwitch()).isEqualTo(phOverridingValue);
- // Global Killswitch is on and overrides the getAppSetIdKillswitch.
- assertThat(FlagsFactory.getFlags().getAppSetIdKillSwitch()).isEqualTo(true);
+ // Global Killswitch is on, but is ignored by getAppSetIdKillswitch.
+ assertThat(FlagsFactory.getFlags().getAppSetIdKillSwitch()).isEqualTo(false);
}
@Test
@@ -2890,6 +3109,55 @@ public class PhFlagsTest {
}
@Test
+ public void testGetOffDeviceAdSelectionEnabled() {
+ assertThat(FlagsFactory.getFlags().getAdSelectionOffDeviceEnabled())
+ .isEqualTo(FLEDGE_AD_SELECTION_OFF_DEVICE_ENABLED);
+
+ final boolean phOverridingValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_FLEDE_AD_SELECTION_OFF_DEVICE_ENABLED,
+ Boolean.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getAdSelectionOffDeviceEnabled()).isEqualTo(phOverridingValue);
+ }
+
+ @Test
+ public void testgetFledgeAdselectionExpirationWindow() {
+ // Without any overriding, the value is the hard coded constant.
+ assertThat(FlagsFactory.getFlags().getAdSelectionExpirationWindowS())
+ .isEqualTo(FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S);
+
+ final int phOverridingValue = 4;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_FLEDGE_AD_SELECTION_EXPIRATION_WINDOW_S,
+ Integer.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getAdSelectionExpirationWindowS()).isEqualTo(phOverridingValue);
+ }
+
+ @Test
+ public void testGetRegistrationJobQueueIntervalMs() {
+ assertThat(FlagsFactory.getFlags().getRegistrationJobQueueIntervalMs())
+ .isEqualTo(ASYNC_REGISTRATION_JOB_QUEUE_INTERVAL_MS);
+
+ final long phOverridingValue = 1L;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_REGISTRATION_JOB_QUEUE_INTERVAL_MS,
+ Long.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getRegistrationJobQueueIntervalMs()).isEqualTo(phOverridingValue);
+ }
+
+ @Test
public void testDump() throws FileNotFoundException {
// Trigger the dump to verify no crash
PrintWriter printWriter =
@@ -2926,5 +3194,104 @@ public class PhFlagsTest {
assertThat(phFlags.getMaxResponseBasedRegistrationPayloadSizeBytes())
.isEqualTo(phOverrideValue);
}
+
+ @Test
+ public void testGetOffDeviceAdSelectionRequestCompressionEnabled() {
+ assertThat(FlagsFactory.getFlags().getAdSelectionOffDeviceRequestCompressionEnabled())
+ .isEqualTo(FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED);
+
+ final boolean phOverridingValue = false;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED,
+ Boolean.toString(phOverridingValue),
+ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getAdSelectionOffDeviceRequestCompressionEnabled())
+ .isEqualTo(phOverridingValue);
+ }
+
+ @Test
+ public void testGetEnableTopicContributorsCheck() {
+ assertThat(FlagsFactory.getFlags().getEnableTopicContributorsCheck())
+ .isEqualTo(ENABLE_TOPIC_CONTRIBUTORS_CHECK);
+
+ final boolean phOverridingValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_ENABLE_TOPIC_CONTRIBUTORS_CHECK,
+ Boolean.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getEnableTopicContributorsCheck()).isEqualTo(phOverridingValue);
+ }
+
+ @Test
+ public void testGetEnableDatabaseSchemaVersion3() {
+ assertThat(FlagsFactory.getFlags().getEnableDatabaseSchemaVersion3())
+ .isEqualTo(ENABLE_DATABASE_SCHEMA_VERSION_3);
+
+ final boolean phOverridingValue = true;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_ENABLE_DATABASE_SCHEMA_VERSION_3,
+ Boolean.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getEnableDatabaseSchemaVersion3()).isEqualTo(phOverridingValue);
+ }
+
+ @Test
+ public void testEnrollmentBlocklist_singleEnrollment() {
+ Flags phFlags = FlagsFactory.getFlags();
+
+ String blocklistedEnrollmentId = "enrollmentId1";
+ setEnrollmentBlocklist(blocklistedEnrollmentId);
+
+ assertThat(phFlags.getEnrollmentBlocklist()).contains(blocklistedEnrollmentId);
+ }
+
+ @Test
+ public void testEnrollmentBlocklist_multipleEnrollments() {
+ Flags phFlags = FlagsFactory.getFlags();
+
+ String enrollmentId1 = "enrollmentId1";
+ String enrollmentId2 = "enrollmentId2";
+ String enrollmentId3 = "enrollmentId3";
+
+ String blocklistedEnrollmentId =
+ String.format("%s,%s,%s", enrollmentId1, enrollmentId2, enrollmentId3);
+ setEnrollmentBlocklist(blocklistedEnrollmentId);
+
+ assertThat(phFlags.getEnrollmentBlocklist())
+ .containsExactly(enrollmentId1, enrollmentId2, enrollmentId3);
+ }
+
+ @Test
+ public void testEnrollmentBlocklist_malformedList() {
+ Flags phFlags = FlagsFactory.getFlags();
+
+ String enrollmentId1 = "enrollmentId1";
+ String enrollmentId2 = "enrollmentId2";
+ String enrollmentId3 = "enrollmentId3";
+
+ String blocklistedEnrollmentId =
+ String.format("%s%s%s", enrollmentId1, enrollmentId2, enrollmentId3);
+ setEnrollmentBlocklist(blocklistedEnrollmentId);
+
+ assertThat(phFlags.getEnrollmentBlocklist())
+ .containsNoneOf(enrollmentId1, enrollmentId2, enrollmentId3);
+ }
+
+ private void setEnrollmentBlocklist(String blocklistFlag) {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_ENROLLMENT_BLOCKLIST_IDS,
+ blocklistFlag,
+ false);
+ }
// CHECKSTYLE:ON IndentationCheck
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdServiceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdServiceImplTest.java
index 4f9ecfffc..16dfda42a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdServiceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdServiceImplTest.java
@@ -19,12 +19,15 @@ package com.android.adservices.service.adid;
import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_ID;
import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_PERMISSION_NOT_REQUESTED;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_ADID;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -48,6 +51,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException;
+import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.stats.Clock;
@@ -93,6 +97,7 @@ public class AdIdServiceImplTest {
@Mock private Clock mClock;
@Mock private Context mMockSdkContext;
@Mock private Context mMockAppContext;
+ @Mock private Throttler mMockThrottler;
@Mock private AdIdServiceImpl mAdIdServiceImpl;
@Mock private AppImportanceFilter mMockAppImportanceFilter;
@@ -100,7 +105,10 @@ public class AdIdServiceImplTest {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mAdIdWorker = new AdIdWorker(mContext, mMockFlags);
+ mAdIdWorker =
+ Mockito.spy(AdIdWorker.getInstance(ApplicationProvider.getApplicationContext()));
+ Mockito.doReturn(null).when(mAdIdWorker).getService();
+
when(mClock.elapsedRealtime()).thenReturn(150L, 200L);
mCallerMetadata = new CallerMetadata.Builder().setBinderElapsedTimestamp(100L).build();
mRequest =
@@ -115,6 +123,10 @@ public class AdIdServiceImplTest {
// Put this test app into bypass list to bypass Allow-list check.
when(mMockFlags.getPpapiAppAllowList()).thenReturn(ADID_API_ALLOW_LIST);
+ // Rate Limit is not reached.
+ when(mMockThrottler.tryAcquire(eq(Throttler.ApiKey.ADID_API_APP_PACKAGE_NAME), anyString()))
+ .thenReturn(true);
+
// Initialize mock static.
mStaticMockitoSession =
ExtendedMockito.mockitoSession().mockStatic(Binder.class).startMocking();
@@ -134,6 +146,21 @@ public class AdIdServiceImplTest {
}
@Test
+ public void checkThrottler_rateLimitReached_forAppPackageName() throws InterruptedException {
+ // App calls AdId API directly, not via an SDK.
+ GetAdIdParam request =
+ new GetAdIdParam.Builder()
+ .setAppPackageName(TEST_APP_PACKAGE_NAME)
+ .setSdkPackageName(SDK_PACKAGE_NAME)
+ .build();
+
+ // Rate Limit Reached.
+ when(mMockThrottler.tryAcquire(eq(Throttler.ApiKey.ADID_API_APP_PACKAGE_NAME), anyString()))
+ .thenReturn(false);
+ invokeGetAdIdAndVerifyError(mContext, STATUS_RATE_LIMIT_REACHED, request);
+ }
+
+ @Test
public void testEnforceForeground_sandboxCaller() throws Exception {
// Mock AppImportanceFilter to throw Exception when invoked. This is to verify getAdId()
// doesn't throw if caller is via Sandbox.
@@ -215,7 +242,7 @@ public class AdIdServiceImplTest {
when(mPackageManager.checkPermission(ACCESS_ADSERVICES_AD_ID, SDK_PACKAGE_NAME))
.thenReturn(PackageManager.PERMISSION_DENIED);
when(Binder.getCallingUidOrThrow()).thenReturn(SANDBOX_UID);
- invokeGetAdIdAndVerifyError(mMockSdkContext, STATUS_UNAUTHORIZED);
+ invokeGetAdIdAndVerifyError(mMockSdkContext, STATUS_PERMISSION_NOT_REQUESTED);
}
@Test
@@ -281,6 +308,7 @@ public class AdIdServiceImplTest {
mAdServicesLogger,
mClock,
mMockFlags,
+ mMockThrottler,
mMockAppImportanceFilter);
mAdIdServiceImpl.getAdId(
request,
@@ -360,6 +388,7 @@ public class AdIdServiceImplTest {
mAdServicesLogger,
mClock,
mMockFlags,
+ mMockThrottler,
mMockAppImportanceFilter);
}
@@ -371,6 +400,7 @@ public class AdIdServiceImplTest {
mAdServicesLogger,
mClock,
mMockFlags,
+ mMockThrottler,
mMockAppImportanceFilter);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdWorkerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdWorkerTest.java
index 8388d2e6c..9f0498d6d 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdWorkerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adid/AdIdWorkerTest.java
@@ -46,7 +46,7 @@ public class AdIdWorkerTest {
AdIdWorker spyWorker =
Mockito.spy(AdIdWorker.getInstance(ApplicationProvider.getApplicationContext()));
- Mockito.when(spyWorker.getService()).thenReturn(mInterface);
+ Mockito.doReturn(mInterface).when(spyWorker).getService();
spyWorker.getAdId(
"testPackageName",
@@ -72,12 +72,11 @@ public class AdIdWorkerTest {
@Test
public void testGetAdIdOnError() throws Exception {
mTestSuccess = false;
-
CompletableFuture<Integer> future = new CompletableFuture<>();
AdIdWorker spyWorker =
Mockito.spy(AdIdWorker.getInstance(ApplicationProvider.getApplicationContext()));
- Mockito.when(spyWorker.getService()).thenReturn(mInterface);
+ Mockito.doReturn(mInterface).when(spyWorker).getService();
spyWorker.getAdId(
"testPackageName",
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdBidGeneratorImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdBidGeneratorImplTest.java
index 618fd879e..fa70f438a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdBidGeneratorImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdBidGeneratorImplTest.java
@@ -17,7 +17,6 @@
package com.android.adservices.service.adselection;
import static com.android.adservices.service.adselection.AdBidGeneratorImpl.BIDDING_TIMED_OUT;
-import static com.android.adservices.service.adselection.AdBidGeneratorImpl.MISSING_BIDDING_LOGIC;
import static com.android.adservices.service.adselection.AdBidGeneratorImpl.MISSING_TRUSTED_BIDDING_SIGNALS;
import static org.junit.Assert.assertEquals;
@@ -83,6 +82,7 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
public class AdBidGeneratorImplTest {
public static final List<Double> BIDS =
@@ -106,7 +106,6 @@ public class AdBidGeneratorImplTest {
private static final AdSelectionSignals EMPTY_AD_SELECTION_SIGNALS = AdSelectionSignals.EMPTY;
private static final AdSelectionSignals EMPTY_BUYER_SIGNALS = AdSelectionSignals.EMPTY;
private static final AdSelectionSignals EMPTY_CONTEXTUAL_SIGNALS = AdSelectionSignals.EMPTY;
- private static final AdSelectionSignals EMPTY_USER_SIGNALS = AdSelectionSignals.EMPTY;
private static final AdSelectionSignals TRUSTED_BIDDING_SIGNALS =
AdSelectionSignals.fromString(
"{\n" + "\t\"max_bid_limit\": 20,\n" + "\t\"ad_type\": \"retail\"\n" + "}");
@@ -121,9 +120,11 @@ public class AdBidGeneratorImplTest {
private ListeningExecutorService mLightweightExecutorService;
private ListeningExecutorService mBackgroundExecutorService;
private ListeningExecutorService mBlockingExecutorService;
+ private ScheduledThreadPoolExecutor mScheduledExecutor;
private final Context mContext = ApplicationProvider.getApplicationContext();
@Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
@Mock AdSelectionScriptEngine mAdSelectionScriptEngine;
+ private JsFetcher mJsFetcher;
private Uri mDecisionLogicUri;
private Dispatcher mDefaultDispatcher;
private AdBidGeneratorImpl mAdBidGenerator;
@@ -150,6 +151,7 @@ public class AdBidGeneratorImplTest {
mLightweightExecutorService = AdServicesExecutors.getLightWeightExecutor();
mBackgroundExecutorService = AdServicesExecutors.getBackgroundExecutor();
mBlockingExecutorService = AdServicesExecutors.getBlockingExecutor();
+ mScheduledExecutor = AdServicesExecutors.getScheduler();
mAdServicesHttpsClient =
new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor());
mCustomAudienceDao =
@@ -221,17 +223,24 @@ public class AdBidGeneratorImplTest {
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
mAdBidGenerator =
new AdBidGeneratorImpl(
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
-
+ mIsolateSettings,
+ mJsFetcher);
Mockito.when(
mAdSelectionScriptEngine.generateBids(
mBuyerDecisionLogicJs,
@@ -240,7 +249,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals))
.thenReturn(FluentFuture.from(Futures.immediateFuture(AD_WITH_BIDS)));
// When the call to runAdBiddingPerCA, and the computation of future is complete,
@@ -249,8 +257,7 @@ public class AdBidGeneratorImplTest {
mCustomAudienceWithAds,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- AdSelectionConfigFixture.anAdSelectionConfig());
+ EMPTY_CONTEXTUAL_SIGNALS);
AdBiddingOutcome expectedAdBiddingOutcome =
AdBiddingOutcome.builder()
.setAdWithBid(AD_WITH_BIDS.get(2))
@@ -266,7 +273,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals);
mMockWebServerRule.verifyMockServerRequests(
mServer,
@@ -313,16 +319,24 @@ public class AdBidGeneratorImplTest {
// Resetting adBidGenerator to use the new dev context
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
mAdBidGenerator =
new AdBidGeneratorImpl(
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
// Given we are using a direct executor and mock the returned result from the
// AdSelectionScriptEngine.generateBids for preparing the test,
@@ -334,7 +348,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals))
.thenReturn(FluentFuture.from(Futures.immediateFuture(AD_WITH_BIDS)));
// When the call to runAdBiddingPerCA, and the computation of future is complete,
@@ -343,8 +356,7 @@ public class AdBidGeneratorImplTest {
mCustomAudienceWithAds,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- adSelectionConfig);
+ EMPTY_CONTEXTUAL_SIGNALS);
AdBiddingOutcome expectedAdBiddingOutcome =
AdBiddingOutcome.builder()
.setAdWithBid(AD_WITH_BIDS.get(2))
@@ -361,7 +373,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals);
mMockWebServerRule.verifyMockServerRequests(
mServer, 0, Collections.emptyList(), mRequestMatcherExactMatch);
@@ -375,16 +386,24 @@ public class AdBidGeneratorImplTest {
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
mAdBidGenerator =
new AdBidGeneratorImpl(
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
Mockito.when(
mAdSelectionScriptEngine.generateBids(
@@ -394,7 +413,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals))
.thenReturn(FluentFuture.from(Futures.immediateFuture(AD_WITH_NON_POSITIVE_BIDS)));
// When the call to runAdBiddingPerCA, and the computation of future is complete,
@@ -403,8 +421,7 @@ public class AdBidGeneratorImplTest {
mCustomAudienceWithAds,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- AdSelectionConfigFixture.anAdSelectionConfig());
+ EMPTY_CONTEXTUAL_SIGNALS);
// Then we can test the result by assertion,
assertNull(result.get());
Mockito.verify(mAdSelectionScriptEngine)
@@ -415,7 +432,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals);
mMockWebServerRule.verifyMockServerRequests(
mServer,
@@ -433,7 +449,12 @@ public class AdBidGeneratorImplTest {
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
-
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
Flags flagsWithSmallerLimits =
new Flags() {
@Override
@@ -446,11 +467,13 @@ public class AdBidGeneratorImplTest {
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
flagsWithSmallerLimits,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
Mockito.when(
mAdSelectionScriptEngine.generateBids(
mBuyerDecisionLogicJs,
@@ -459,7 +482,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals))
.thenAnswer((invocation) -> generateBidsWithDelay(flagsWithSmallerLimits));
// When the call to runAdBiddingPerCA, and the computation of future is complete,
@@ -468,8 +490,7 @@ public class AdBidGeneratorImplTest {
mCustomAudienceWithAds,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- AdSelectionConfigFixture.anAdSelectionConfig());
+ EMPTY_CONTEXTUAL_SIGNALS);
// Then we can test the result by assertion
ExecutionException thrown = assertThrows(ExecutionException.class, result::get);
assertTrue(thrown.getMessage().contains(BIDDING_TIMED_OUT));
@@ -481,7 +502,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals);
mMockWebServerRule.verifyMockServerRequests(
mServer,
@@ -498,16 +518,24 @@ public class AdBidGeneratorImplTest {
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
mAdBidGenerator =
new AdBidGeneratorImpl(
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
JSONException jsonException = new JSONException("");
Mockito.when(
@@ -518,7 +546,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals))
.thenThrow(jsonException);
// When the call to runBidding, and the computation of future is complete.
@@ -529,7 +556,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
mCustomAudienceSignals,
- EMPTY_USER_SIGNALS,
EMPTY_AD_SELECTION_SIGNALS);
ExecutionException outException = assertThrows(ExecutionException.class, result::get);
@@ -562,6 +588,12 @@ public class AdBidGeneratorImplTest {
mServer = mMockWebServerRule.startMockWebServer(dispatcher);
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
List<String> emptyTrustedBiddingKeys = Collections.EMPTY_LIST;
DBTrustedBiddingData trustedBiddingData =
new DBTrustedBiddingData.Builder()
@@ -587,11 +619,13 @@ public class AdBidGeneratorImplTest {
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
Mockito.when(
mAdSelectionScriptEngine.generateBids(
@@ -601,7 +635,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
customAudienceSignals))
.thenReturn(FluentFuture.from(Futures.immediateFuture(AD_WITH_BIDS)));
// When the call to runAdBiddingPerCA, and the computation of future is complete,
@@ -610,8 +643,7 @@ public class AdBidGeneratorImplTest {
customAudienceWithAds,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- AdSelectionConfigFixture.anAdSelectionConfig());
+ EMPTY_CONTEXTUAL_SIGNALS);
AdBiddingOutcome expectedAdBiddingOutcome =
AdBiddingOutcome.builder()
.setAdWithBid(AD_WITH_BIDS.get(2))
@@ -627,7 +659,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
customAudienceSignals);
mMockWebServerRule.verifyMockServerRequests(
mServer,
@@ -655,20 +686,28 @@ public class AdBidGeneratorImplTest {
};
mServer = mMockWebServerRule.startMockWebServer(dispatcher);
IllegalStateException missingJSLogicException =
- new IllegalStateException(MISSING_BIDDING_LOGIC);
+ new IllegalStateException(JsFetcher.MISSING_BIDDING_LOGIC);
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
mAdBidGenerator =
new AdBidGeneratorImpl(
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
// When the call to runAdBiddingPerCA, and the computation of future is complete,
FluentFuture<AdBiddingOutcome> result =
@@ -676,8 +715,7 @@ public class AdBidGeneratorImplTest {
mCustomAudienceWithAds,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- AdSelectionConfigFixture.anAdSelectionConfig());
+ EMPTY_CONTEXTUAL_SIGNALS);
ExecutionException outException = assertThrows(ExecutionException.class, result::get);
assertEquals(outException.getCause().getMessage(), missingJSLogicException.getMessage());
mMockWebServerRule.verifyMockServerRequests(
@@ -707,16 +745,24 @@ public class AdBidGeneratorImplTest {
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
mAdBidGenerator =
new AdBidGeneratorImpl(
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
// When the call to runAdBiddingPerCA, and the computation of future is complete,
FluentFuture<AdBiddingOutcome> result =
@@ -724,8 +770,7 @@ public class AdBidGeneratorImplTest {
mCustomAudienceWithAds,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- AdSelectionConfigFixture.anAdSelectionConfig());
+ EMPTY_CONTEXTUAL_SIGNALS);
ExecutionException outException = assertThrows(ExecutionException.class, result::get);
assertEquals(outException.getCause().getMessage(), missingSignalsException.getMessage());
mMockWebServerRule.verifyMockServerRequests(
@@ -741,16 +786,24 @@ public class AdBidGeneratorImplTest {
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
mAdBidGenerator =
new AdBidGeneratorImpl(
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
// Given we are using a direct executor and mock the returned result from the
// AdSelectionScriptEngine.generateBids for preparing the test,
@@ -762,7 +815,6 @@ public class AdBidGeneratorImplTest {
EMPTY_BUYER_SIGNALS,
TRUSTED_BIDDING_SIGNALS,
EMPTY_CONTEXTUAL_SIGNALS,
- EMPTY_USER_SIGNALS,
mCustomAudienceSignals))
.thenThrow(new JSONException(""));
// When the call to runBidding, and the computation of future is complete.
@@ -771,8 +823,7 @@ public class AdBidGeneratorImplTest {
mCustomAudienceWithAds,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- AdSelectionConfigFixture.anAdSelectionConfig());
+ EMPTY_CONTEXTUAL_SIGNALS);
assertNull(result.get());
mMockWebServerRule.verifyMockServerRequests(
mServer,
@@ -787,24 +838,31 @@ public class AdBidGeneratorImplTest {
CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ mJsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mAdServicesHttpsClient);
mAdBidGenerator =
new AdBidGeneratorImpl(
mContext,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mAdSelectionScriptEngine,
mAdServicesHttpsClient,
customAudienceDevOverridesHelper,
mFlags,
- mIsolateSettings);
+ mIsolateSettings,
+ mJsFetcher);
FluentFuture<AdBiddingOutcome> result =
mAdBidGenerator.runAdBiddingPerCA(
CUSTOM_AUDIENCE_WITH_EMPTY_ADS,
EMPTY_AD_SELECTION_SIGNALS,
EMPTY_BUYER_SIGNALS,
- EMPTY_CONTEXTUAL_SIGNALS,
- AdSelectionConfigFixture.anAdSelectionConfig());
+ EMPTY_CONTEXTUAL_SIGNALS);
// The result is an early return with a FluentFuture of Null, after checking the Ads list is
// empty.
assertNull(result.get());
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionE2ETest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionE2ETest.java
index bcaad1edc..c8b2f434e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionE2ETest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionE2ETest.java
@@ -16,8 +16,10 @@
package com.android.adservices.service.adselection;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
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.adselection.AdSelectionRunner.AD_SELECTION_THROTTLED;
@@ -30,18 +32,19 @@ import static com.android.adservices.service.adselection.AdsScoreGeneratorImpl.M
import static com.android.adservices.service.adselection.AdsScoreGeneratorImpl.MISSING_TRUSTED_SCORING_SIGNALS;
import static com.android.adservices.service.adselection.AdsScoreGeneratorImpl.SCORING_TIMED_OUT;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS;
-import static com.android.adservices.stats.FledgeApiCallStatsMatcher.aCallStatForFledgeApiWithStatus;
-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.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
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.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.geq;
import android.adservices.adselection.AdSelectionCallback;
import android.adservices.adselection.AdSelectionConfig;
@@ -50,6 +53,7 @@ import android.adservices.adselection.AdSelectionInput;
import android.adservices.adselection.AdSelectionResponse;
import android.adservices.common.AdServicesStatusUtils;
import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.CallerMetadata;
import android.adservices.common.CallingAppUidSupplierProcessImpl;
import android.adservices.common.CommonFixture;
import android.adservices.common.FledgeErrorResponse;
@@ -59,6 +63,8 @@ import android.adservices.http.MockWebServerRule;
import android.content.Context;
import android.net.Uri;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
@@ -66,6 +72,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.LogUtil;
import com.android.adservices.MockWebServerRuleFactory;
import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.adselection.AdSelectionDatabase;
import com.android.adservices.data.adselection.AdSelectionEntryDao;
import com.android.adservices.data.adselection.DBAdSelectionOverride;
@@ -75,6 +82,7 @@ import com.android.adservices.data.customaudience.CustomAudienceDatabase;
import com.android.adservices.data.customaudience.DBCustomAudience;
import com.android.adservices.data.customaudience.DBCustomAudienceOverride;
import com.android.adservices.data.customaudience.DBTrustedBiddingData;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.AdServicesHttpsClient;
@@ -94,6 +102,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.google.common.collect.ImmutableList;
import com.google.mockwebserver.Dispatcher;
import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
import com.google.mockwebserver.RecordedRequest;
import org.junit.After;
@@ -114,12 +123,14 @@ import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* This test the actual flow of Ad Selection internal flow without any mocking. The dependencies in
* this test are invoked and used in real time.
*/
public class AdSelectionE2ETest {
+ public static final String TAG = "adservices";
private static final String ERROR_SCORE_AD_LOGIC_MISSING = "scoreAd is not defined";
private static final AdTechIdentifier BUYER_1 = AdSelectionConfigFixture.BUYER_1;
@@ -127,6 +138,7 @@ public class AdSelectionE2ETest {
private static final AdTechIdentifier BUYER_3 = AdSelectionConfigFixture.BUYER_3;
private static final String BUYER = "buyer";
private static final String AD_URI_PREFIX = "http://www.domain.com/adverts/123/";
+ private static final String DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX = "";
private static final String BUYER_BIDDING_LOGIC_URI_PATH = "/buyer/bidding/logic/";
private static final String BUYER_TRUSTED_SIGNAL_URI_PATH = "/kv/buyer/signals/";
@@ -139,7 +151,7 @@ public class AdSelectionE2ETest {
private static final String READ_BID_FROM_AD_METADATA_JS =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}";
@@ -172,38 +184,51 @@ public class AdSelectionE2ETest {
AdTechIdentifier.fromString("developer.android.com");
private static final Uri DECISION_LOGIC_URI_INCONSISTENT =
Uri.parse("https://developer%$android.com/test/decisions_logic_uris");
-
+ private static final long BINDER_ELAPSED_TIME_MS = 100L;
private static final String CALLER_PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME;
- @Spy private final AdServicesLogger mAdServicesLoggerSpy = AdServicesLoggerImpl.getInstance();
private static final String MY_APP_PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME;
+ private final AdServicesLogger mAdServicesLoggerMock =
+ ExtendedMockito.mock(AdServicesLoggerImpl.class);
+ private final Flags mFlags = new AdSelectionE2ETestFlags();
+
@Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
- private Context mContext = ApplicationProvider.getApplicationContext();
// Mocking DevContextFilter to test behavior with and without override api authorization
@Mock DevContextFilter mDevContextFilter;
@Mock AppImportanceFilter mAppImportanceFilter;
- @Mock private ConsentManager mConsentManagerMock;
+ @Mock CallerMetadata mMockCallerMetadata;
+
+ @Spy
+ FledgeAllowListsFilter mFledgeAllowListsFilterSpy =
+ new FledgeAllowListsFilter(mFlags, mAdServicesLoggerMock);
+
+ private Context mContext = ApplicationProvider.getApplicationContext();
@Spy
FledgeAuthorizationFilter mFledgeAuthorizationFilterSpy =
- FledgeAuthorizationFilter.create(mContext, mAdServicesLoggerSpy);
+ new FledgeAuthorizationFilter(
+ mContext.getPackageManager(),
+ new EnrollmentDao(mContext, DbTestUtil.getDbHelperForTest()),
+ mAdServicesLoggerMock);
+ @Mock private ConsentManager mConsentManagerMock;
private MockitoSession mStaticMockSession = null;
private ExecutorService mLightweightExecutorService;
private ExecutorService mBackgroundExecutorService;
+ private ScheduledThreadPoolExecutor mScheduledExecutor;
private CustomAudienceDao mCustomAudienceDao;
- private AdSelectionEntryDao mAdSelectionEntryDao;
+ @Spy private AdSelectionEntryDao mAdSelectionEntryDaoSpy;
private AdServicesHttpsClient mAdServicesHttpsClient;
private AdSelectionConfig mAdSelectionConfig;
private AdSelectionServiceImpl mAdSelectionService;
private Dispatcher mDispatcher;
- private final Flags mFlags = new AdSelectionE2ETestFlags();
-
- @Spy
- FledgeAllowListsFilter mFledgeAllowListsFilterSpy =
- new FledgeAllowListsFilter(mFlags, mAdServicesLoggerSpy);
@Before
public void setUp() throws Exception {
+ mAdSelectionEntryDaoSpy =
+ Room.inMemoryDatabaseBuilder(mContext, AdSelectionDatabase.class)
+ .build()
+ .adSelectionEntryDao();
+
// Test applications don't have the required permissions to read config P/H flags, and
// injecting mocked flags everywhere is annoying and non-trivial for static methods
mStaticMockSession =
@@ -216,35 +241,33 @@ public class AdSelectionE2ETest {
// Initialize dependencies for the AdSelectionService
mLightweightExecutorService = AdServicesExecutors.getLightWeightExecutor();
mBackgroundExecutorService = AdServicesExecutors.getBackgroundExecutor();
+ mScheduledExecutor = AdServicesExecutors.getScheduler();
mCustomAudienceDao =
Room.inMemoryDatabaseBuilder(mContext, CustomAudienceDatabase.class)
.build()
.customAudienceDao();
- mAdSelectionEntryDao =
- Room.inMemoryDatabaseBuilder(mContext, AdSelectionDatabase.class)
- .build()
- .adSelectionEntryDao();
-
mAdServicesHttpsClient =
new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor());
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
-
+ when(mMockCallerMetadata.getBinderElapsedTimestamp())
+ .thenReturn(SystemClock.elapsedRealtime() - BINDER_ELAPSED_TIME_MS);
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
- mAdSelectionEntryDao,
+ mAdSelectionEntryDaoSpy,
mCustomAudienceDao,
mAdServicesHttpsClient,
mDevContextFilter,
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mContext,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -307,7 +330,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionSuccess() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Logger calls come after the callback is returned
CountDownLatch loggerLatch = new CountDownLatch(1);
@@ -316,8 +339,8 @@ public class AdSelectionE2ETest {
loggerLatch.countDown();
return null;
})
- .when(mAdServicesLoggerSpy)
- .logApiCallStats(any());
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -348,24 +371,22 @@ public class AdSelectionE2ETest {
loggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_SUCCESS),
+ geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionWithRevokedUserConsentSuccess() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent();
// Logger calls come after the callback is returned
CountDownLatch loggerLatch = new CountDownLatch(1);
@@ -374,8 +395,8 @@ public class AdSelectionE2ETest {
loggerLatch.countDown();
return null;
})
- .when(mAdServicesLoggerSpy)
- .logApiCallStats(any());
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -405,25 +426,22 @@ public class AdSelectionE2ETest {
loggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertFalse(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
Uri.EMPTY.toString(),
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, STATUS_USER_CONSENT_REVOKED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- STATUS_USER_CONSENT_REVOKED));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_USER_CONSENT_REVOKED),
+ geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMultipleCAsSuccess() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -490,7 +508,7 @@ public class AdSelectionE2ETest {
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
@@ -499,7 +517,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionSucceedsWithOverride() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -527,7 +545,7 @@ public class AdSelectionE2ETest {
.setDecisionLogicJS(USE_BID_AS_SCORE_JS)
.setTrustedScoringSignals(TRUSTED_SCORING_SIGNALS.toString())
.build();
- mAdSelectionEntryDao.persistAdSelectionOverride(adSelectionOverride);
+ mAdSelectionEntryDaoSpy.persistAdSelectionOverride(adSelectionOverride);
// Set dev override for custom audience
DBCustomAudienceOverride dbCustomAudienceOverride =
@@ -551,16 +569,17 @@ public class AdSelectionE2ETest {
// Creating new instance of service with new DevContextFilter
mAdSelectionService =
new AdSelectionServiceImpl(
- mAdSelectionEntryDao,
+ mAdSelectionEntryDaoSpy,
mCustomAudienceDao,
mAdServicesHttpsClient,
mDevContextFilter,
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mContext,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -579,7 +598,7 @@ public class AdSelectionE2ETest {
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
@@ -588,7 +607,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionActiveCAs() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(0.9, 0.45);
@@ -602,6 +621,7 @@ public class AdSelectionE2ETest {
DBCustomAudience dBCustomAudienceInactive =
createDBCustomAudience(
BUYER_2,
+ DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME,
@@ -610,6 +630,7 @@ public class AdSelectionE2ETest {
DBCustomAudience dBCustomAudienceExpired =
createDBCustomAudience(
BUYER_3,
+ DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_3),
bidsForBuyer3,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
@@ -629,7 +650,7 @@ public class AdSelectionE2ETest {
invokeRunAdSelection(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad1",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
@@ -638,7 +659,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionNoCAsActive() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -648,6 +669,7 @@ public class AdSelectionE2ETest {
DBCustomAudience dBCustomAudienceInactive =
createDBCustomAudience(
BUYER_1,
+ DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
bidsForBuyer1,
CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME,
@@ -656,6 +678,7 @@ public class AdSelectionE2ETest {
DBCustomAudience dBCustomAudienceExpired =
createDBCustomAudience(
BUYER_2,
+ DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
bidsForBuyer2,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
@@ -665,6 +688,7 @@ public class AdSelectionE2ETest {
DBCustomAudience dBCustomAudienceOutdated =
createDBCustomAudience(
BUYER_3,
+ DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_3),
bidsForBuyer3,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
@@ -698,7 +722,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionNoCAsFailure() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Do not populate CustomAudience DAO
mMockWebServerRule.startMockWebServer(mDispatcher);
@@ -711,7 +735,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionNoBuyersFailure() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Do not populate buyers in AdSelectionConfig
mAdSelectionConfig =
@@ -739,7 +763,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionPartialAdsExcludedBidding() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
mMockWebServerRule.startMockWebServer(mDispatcher);
// Setting bids which are partially non-positive
@@ -769,7 +793,7 @@ public class AdSelectionE2ETest {
invokeRunAdSelection(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad2",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
@@ -795,7 +819,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionMissingBiddingLogicFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Setting bids->scores
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -862,7 +886,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionMissingScoringLogicFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Setting bids->scores
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -929,7 +953,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionErrorFetchingScoringLogicFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Setting bids->scores
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -995,7 +1019,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionPartialMissingBiddingLogic() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Setting bids->scores
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -1056,7 +1080,7 @@ public class AdSelectionE2ETest {
invokeRunAdSelection(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad2",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
@@ -1065,7 +1089,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionPartialNonPositiveScoring() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Setting bids, in this case the odd bids will be made negative by scoring logic
List<Double> bidsForBuyer1 = ImmutableList.of(1.0, 2.0);
@@ -1132,7 +1156,7 @@ public class AdSelectionE2ETest {
invokeRunAdSelection(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad2",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
@@ -1141,7 +1165,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionNonPositiveScoringFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Setting bids, in this case the odd bids will be made negative by scoring logic
List<Double> bidsForBuyer1 = ImmutableList.of(1.0, 9.0);
@@ -1212,9 +1236,9 @@ public class AdSelectionE2ETest {
}
@Test
- public void testRunAdSelectionBiddingTimesOut() throws Exception {
+ public void testRunAdSelectionBiddingTimesOutForCA() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Flags flagsWithSmallerLimits =
new Flags() {
@@ -1244,16 +1268,17 @@ public class AdSelectionE2ETest {
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
- mAdSelectionEntryDao,
+ mAdSelectionEntryDaoSpy,
mCustomAudienceDao,
mAdServicesHttpsClient,
mDevContextFilter,
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mContext,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
flagsWithSmallerLimits,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1263,7 +1288,7 @@ public class AdSelectionE2ETest {
insertJsWait(2 * mFlags.getAdSelectionBiddingTimeoutPerCaMs());
String readBidFromAdMetadataWithDelayJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ jsWaitMoreThanAllowedForBiddingPerCa
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
@@ -1333,9 +1358,217 @@ public class AdSelectionE2ETest {
}
@Test
+ public void testRunAdSelectionBiddingTimesOutPartiallyBuyer() throws Exception {
+ doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+
+ Long lenientPerBuyerTimeOutLimit = 50000L;
+ Long tightPerBuyerTimeOutLimit = 2000L;
+
+ Flags flagsWithLenientBuyerBiddingLimits =
+ new Flags() {
+ @Override
+ public long getAdSelectionBiddingTimeoutPerBuyerMs() {
+ return lenientPerBuyerTimeOutLimit;
+ }
+
+ @Override
+ public boolean getEnforceIsolateMaxHeapSize() {
+ return false;
+ }
+
+ @Override
+ public boolean getDisableFledgeEnrollmentCheck() {
+ return true;
+ }
+
+ @Override
+ public float getSdkRequestPermitsPerSecond() {
+ // Unlimited rate for unit tests to avoid flake in tests due to rate
+ // limiting
+ return -1;
+ }
+
+ @Override
+ public long getAdSelectionOverallTimeoutMs() {
+ return lenientPerBuyerTimeOutLimit * 3;
+ }
+ };
+
+ MockWebServer server = mMockWebServerRule.startMockWebServer(mDispatcher);
+ List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+ List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+ DBCustomAudience dBCustomAudienceForBuyer1 =
+ createDBCustomAudience(
+ BUYER_1,
+ mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
+ bidsForBuyer1);
+ DBCustomAudience dBCustomAudienceForBuyer2 =
+ createDBCustomAudience(
+ BUYER_2,
+ mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_2),
+ bidsForBuyer2);
+
+ // Populating the Custom Audience DB
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(
+ dBCustomAudienceForBuyer1,
+ CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(
+ dBCustomAudienceForBuyer2,
+ CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
+
+ when(mDevContextFilter.createDevContext())
+ .thenReturn(DevContext.createForDevOptionsDisabled());
+
+ List<AdTechIdentifier> participatingBuyers = new ArrayList<>();
+ participatingBuyers.add(BUYER_1);
+ participatingBuyers.add(BUYER_2);
+ participatingBuyers.add(BUYER_3);
+
+ int largeCACountForBuyer = 300;
+ for (int i = 1; i <= largeCACountForBuyer; i++) {
+ DBCustomAudience dBCustomAudienceX =
+ createDBCustomAudience(
+ BUYER_3,
+ "-" + i,
+ mMockWebServerRule.uriForPath(BUYER_BIDDING_LOGIC_URI_PATH + BUYER_1),
+ bidsForBuyer1,
+ CustomAudienceFixture.VALID_ACTIVATION_TIME,
+ CustomAudienceFixture.VALID_EXPIRATION_TIME,
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI);
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(
+ dBCustomAudienceX,
+ CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_3));
+ }
+
+ AdSelectionConfig adSelectionConfig =
+ AdSelectionConfigFixture.anAdSelectionConfigBuilder()
+ .setCustomAudienceBuyers(participatingBuyers)
+ .setSeller(
+ AdTechIdentifier.fromString(
+ mMockWebServerRule
+ .uriForPath(SELLER_DECISION_LOGIC_URI_PATH)
+ .getHost()))
+ .setDecisionLogicUri(
+ mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
+ .setTrustedScoringSignalsUri(
+ mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
+ .build();
+
+ // Create an instance of AdSelection Service with lenient dependencies
+ mAdSelectionService =
+ new AdSelectionServiceImpl(
+ mAdSelectionEntryDaoSpy,
+ mCustomAudienceDao,
+ mAdServicesHttpsClient,
+ mDevContextFilter,
+ mAppImportanceFilter,
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ mContext,
+ mConsentManagerMock,
+ mAdServicesLoggerMock,
+ flagsWithLenientBuyerBiddingLimits,
+ CallingAppUidSupplierProcessImpl.create(),
+ mFledgeAuthorizationFilterSpy,
+ mFledgeAllowListsFilterSpy);
+
+ AdSelectionTestCallback resultsCallback =
+ invokeRunAdSelection(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
+
+ assertCallbackIsSuccessful(resultsCallback);
+ long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
+ assertEquals(
+ AD_URI_PREFIX + BUYER_2 + "/ad3",
+ resultsCallback.mAdSelectionResponse.getRenderUri().toString());
+
+ int networkRequestCountWithLenientTimeout = server.getRequestCount();
+
+ // Now we run the same Ad selection with tight per buyer timeout limits
+ Flags flagsWithTightBuyerBiddingLimits =
+ new Flags() {
+ @Override
+ public long getAdSelectionBiddingTimeoutPerBuyerMs() {
+ return tightPerBuyerTimeOutLimit;
+ }
+
+ @Override
+ public boolean getEnforceIsolateMaxHeapSize() {
+ return false;
+ }
+
+ @Override
+ public boolean getDisableFledgeEnrollmentCheck() {
+ return true;
+ }
+
+ @Override
+ public float getSdkRequestPermitsPerSecond() {
+ // Unlimited rate for unit tests to avoid flake in tests due to rate
+ // limiting
+ return -1;
+ }
+
+ @Override
+ public long getAdSelectionOverallTimeoutMs() {
+ return lenientPerBuyerTimeOutLimit * 3;
+ }
+ };
+
+ // Create an instance of AdSelection Service with tight dependencies
+ mAdSelectionService =
+ new AdSelectionServiceImpl(
+ mAdSelectionEntryDaoSpy,
+ mCustomAudienceDao,
+ mAdServicesHttpsClient,
+ mDevContextFilter,
+ mAppImportanceFilter,
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ mContext,
+ mConsentManagerMock,
+ mAdServicesLoggerMock,
+ flagsWithTightBuyerBiddingLimits,
+ CallingAppUidSupplierProcessImpl.create(),
+ mFledgeAuthorizationFilterSpy,
+ mFledgeAllowListsFilterSpy);
+
+ resultsCallback =
+ invokeRunAdSelection(mAdSelectionService, adSelectionConfig, CALLER_PACKAGE_NAME);
+
+ assertCallbackIsSuccessful(resultsCallback);
+ resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
+ assertEquals(
+ AD_URI_PREFIX + BUYER_2 + "/ad3",
+ resultsCallback.mAdSelectionResponse.getRenderUri().toString());
+
+ int networkRequestCountWithTightTimeout =
+ server.getRequestCount() - networkRequestCountWithLenientTimeout;
+
+ Log.v(
+ TAG,
+ String.format(
+ "Network calls with buyer timeout :%d, network calls with"
+ + " lenient timeout :%d",
+ networkRequestCountWithTightTimeout,
+ networkRequestCountWithLenientTimeout));
+ assertTrue(
+ String.format(
+ "Network calls with buyer timeout :%d, are not less than network calls with"
+ + " lenient timeout :%d",
+ networkRequestCountWithTightTimeout, networkRequestCountWithLenientTimeout),
+ networkRequestCountWithTightTimeout < networkRequestCountWithLenientTimeout);
+ }
+
+ @Test
public void testRunAdSelectionScoringTimesOut() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Flags flagsWithSmallerLimits =
new Flags() {
@@ -1365,16 +1598,17 @@ public class AdSelectionE2ETest {
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
- mAdSelectionEntryDao,
+ mAdSelectionEntryDaoSpy,
mCustomAudienceDao,
mAdServicesHttpsClient,
mDevContextFilter,
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mContext,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
flagsWithSmallerLimits,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1456,7 +1690,7 @@ public class AdSelectionE2ETest {
@Test
public void testAdSelectionConfigInvalidInput() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doNothing()
.when(mFledgeAuthorizationFilterSpy)
.assertAdTechAllowed(
@@ -1475,8 +1709,8 @@ public class AdSelectionE2ETest {
loggerLatch.countDown();
return null;
})
- .when(mAdServicesLoggerSpy)
- .logApiCallStats(any());
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
AdSelectionConfig invalidAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
@@ -1499,20 +1733,17 @@ public class AdSelectionE2ETest {
assertEquals(
"Error response code mismatch", STATUS_INVALID_ARGUMENT, response.getStatusCode());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, STATUS_INVALID_ARGUMENT);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- STATUS_INVALID_ARGUMENT));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_INVALID_ARGUMENT),
+ geq((int) BINDER_ELAPSED_TIME_MS));
}
@Test
public void testRunAdSelectionMissingBiddingSignalsFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Create a dispatcher without buyer trusted Signal end point
Dispatcher missingBiddingSignalsDispatcher =
@@ -1574,7 +1805,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionMissingScoringSignalsFailure() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Create a dispatcher without buyer trusted Signal end point
Dispatcher missingScoringSignalsDispatcher =
@@ -1630,7 +1861,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionMissingPartialBiddingSignalsSuccess() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Create a dispatcher with valid end points
Dispatcher missingBiddingSignalsDispatcher =
@@ -1690,7 +1921,7 @@ public class AdSelectionE2ETest {
invokeRunAdSelection(mAdSelectionService, mAdSelectionConfig, CALLER_PACKAGE_NAME);
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
// Given buyer 2 will be excluded from bidding for missing signals, Buyer 1 : Ad 2 will win
assertEquals(
AD_URI_PREFIX + BUYER_1 + "/ad2",
@@ -1700,7 +1931,7 @@ public class AdSelectionE2ETest {
@Test
public void testRunAdSelectionFailsWithInvalidPackageName() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Logger calls come after the callback is returned
CountDownLatch loggerLatch = new CountDownLatch(1);
@@ -1709,8 +1940,8 @@ public class AdSelectionE2ETest {
loggerLatch.countDown();
return null;
})
- .when(mAdServicesLoggerSpy)
- .logApiCallStats(any());
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -1744,10 +1975,7 @@ public class AdSelectionE2ETest {
Assert.assertFalse(resultsCallback.mIsSuccess);
FledgeErrorResponse response = resultsCallback.mFledgeErrorResponse;
- assertEquals(
- "Error response code mismatch",
- AdServicesStatusUtils.STATUS_UNAUTHORIZED,
- response.getStatusCode());
+ assertEquals("Error response code mismatch", STATUS_UNAUTHORIZED, response.getStatusCode());
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
@@ -1756,23 +1984,19 @@ public class AdSelectionE2ETest {
AdSelectionRunner.ERROR_AD_SELECTION_FAILURE,
AdServicesStatusUtils
.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE));
-
- // TODO(b/242139312): Remove atLeastOnce once this the double logging is addressed
- verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
+ // TODO(b/242139312): Remove atLeastOnce once this the double logging is addressed and
+ // update third argument value.
+ verify(mAdServicesLoggerMock, Mockito.atLeastOnce())
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_UNAUTHORIZED);
- verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_UNAUTHORIZED));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_UNAUTHORIZED),
+ geq(0));
}
@Test
public void testRunAdSelectionFailsWhenAppCannotUsePPApi() throws Exception {
doReturn(new AdSelectionE2ETestFlags()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doNothing()
.when(mFledgeAuthorizationFilterSpy)
.assertAdTechAllowed(
@@ -1792,8 +2016,8 @@ public class AdSelectionE2ETest {
loggerLatch.countDown();
return null;
})
- .when(mAdServicesLoggerSpy)
- .logApiCallStats(any());
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -1826,7 +2050,7 @@ public class AdSelectionE2ETest {
FledgeErrorResponse response = resultsCallback.mFledgeErrorResponse;
assertEquals(
"Error response code mismatch",
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED,
+ STATUS_CALLER_NOT_ALLOWED,
response.getStatusCode());
verifyErrorMessageIsCorrect(
@@ -1835,17 +2059,13 @@ public class AdSelectionE2ETest {
AdSelectionRunner.AD_SELECTION_ERROR_PATTERN,
AdSelectionRunner.ERROR_AD_SELECTION_FAILURE,
AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE));
-
- // TODO(b/242139312): Remove atLeastOnce once this the double logging is addressed
- verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
+ // TODO(b/242139312): Remove atLeastOnce once this the double logging is addressed and
+ // update third argument value.
+ verify(mAdServicesLoggerMock, Mockito.atLeastOnce())
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED);
- verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_CALLER_NOT_ALLOWED),
+ geq(0));
}
@Test
@@ -1859,21 +2079,22 @@ public class AdSelectionE2ETest {
};
doReturn(flagsWithEnrollmentCheckEnabled).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
- mAdSelectionEntryDao,
+ mAdSelectionEntryDaoSpy,
mCustomAudienceDao,
mAdServicesHttpsClient,
mDevContextFilter,
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mContext,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
flagsWithEnrollmentCheckEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1886,8 +2107,8 @@ public class AdSelectionE2ETest {
loggerLatch.countDown();
return null;
})
- .when(mAdServicesLoggerSpy)
- .logApiCallStats(any());
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -1920,7 +2141,7 @@ public class AdSelectionE2ETest {
FledgeErrorResponse response = resultsCallback.mFledgeErrorResponse;
assertEquals(
"Error response code mismatch",
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED,
+ STATUS_CALLER_NOT_ALLOWED,
response.getStatusCode());
verifyErrorMessageIsCorrect(
@@ -1929,23 +2150,19 @@ public class AdSelectionE2ETest {
AdSelectionRunner.AD_SELECTION_ERROR_PATTERN,
AdSelectionRunner.ERROR_AD_SELECTION_FAILURE,
AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE));
-
- // TODO(b/242139312): Remove atLeastOnce once this the double logging is addressed
- verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
+ // TODO(b/242139312): Remove atLeastOnce once this the double logging is addressed and
+ // update third argument value.
+ verify(mAdServicesLoggerMock, Mockito.atLeastOnce())
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED);
- verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_CALLER_NOT_ALLOWED),
+ geq(0));
}
@Test
public void testRunAdSelectionThrottledSubsequentCallFailure() throws Exception {
doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
class FlagsWithThrottling implements Flags {
@Override
@@ -1984,16 +2201,17 @@ public class AdSelectionE2ETest {
Flags throttlingFlags = new FlagsWithThrottling();
AdSelectionServiceImpl adSelectionServiceWithThrottling =
new AdSelectionServiceImpl(
- mAdSelectionEntryDao,
+ mAdSelectionEntryDaoSpy,
mCustomAudienceDao,
mAdServicesHttpsClient,
mDevContextFilter,
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mContext,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
throttlingFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2006,8 +2224,8 @@ public class AdSelectionE2ETest {
loggerLatch.countDown();
return null;
})
- .when(mAdServicesLoggerSpy)
- .logApiCallStats(any());
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -2045,7 +2263,7 @@ public class AdSelectionE2ETest {
loggerLatch.await();
assertCallbackIsSuccessful(resultsCallbackFirstCall);
long resultSelectionId = resultsCallbackFirstCall.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallbackFirstCall.mAdSelectionResponse.getRenderUri().toString());
@@ -2088,7 +2306,7 @@ public class AdSelectionE2ETest {
};
doReturn(flagsWithEnrollmentCheckEnabled).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doNothing()
.when(mFledgeAuthorizationFilterSpy)
.assertAdTechAllowed(
@@ -2100,16 +2318,17 @@ public class AdSelectionE2ETest {
// Create an instance of AdSelection Service with real dependencies
mAdSelectionService =
new AdSelectionServiceImpl(
- mAdSelectionEntryDao,
+ mAdSelectionEntryDaoSpy,
mCustomAudienceDao,
mAdServicesHttpsClient,
mDevContextFilter,
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mContext,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
flagsWithEnrollmentCheckEnabled,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2122,8 +2341,8 @@ public class AdSelectionE2ETest {
loggerLatch.countDown();
return null;
})
- .when(mAdServicesLoggerSpy)
- .logApiCallStats(any());
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
mMockWebServerRule.startMockWebServer(mDispatcher);
List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
@@ -2154,18 +2373,16 @@ public class AdSelectionE2ETest {
loggerLatch.await();
assertCallbackIsSuccessful(resultsCallback);
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
- assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
+ assertTrue(mAdSelectionEntryDaoSpy.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
AD_URI_PREFIX + BUYER_2 + "/ad3",
resultsCallback.mAdSelectionResponse.getRenderUri().toString());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_SUCCESS),
+ geq((int) BINDER_ELAPSED_TIME_MS));
}
/**
@@ -2181,6 +2398,7 @@ public class AdSelectionE2ETest {
*/
private DBCustomAudience createDBCustomAudience(
final AdTechIdentifier buyer,
+ final String nameSuffix,
final Uri biddingUri,
List<Double> bids,
Instant activationTime,
@@ -2202,7 +2420,7 @@ public class AdSelectionE2ETest {
return new DBCustomAudience.Builder()
.setOwner(buyer + CustomAudienceFixture.VALID_OWNER)
.setBuyer(buyer)
- .setName(buyer.toString() + CustomAudienceFixture.VALID_NAME)
+ .setName(buyer.toString() + CustomAudienceFixture.VALID_NAME + nameSuffix)
.setActivationTime(activationTime)
.setExpirationTime(expirationTime)
.setCreationTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
@@ -2213,7 +2431,7 @@ public class AdSelectionE2ETest {
.setUri(
mMockWebServerRule.uriForPath(
BUYER_TRUSTED_SIGNAL_URI_PATH))
- .setKeys(TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS)
+ .setKeys(TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
.build())
.setBiddingLogicUri(biddingUri)
.setAds(ads)
@@ -2231,6 +2449,7 @@ public class AdSelectionE2ETest {
final AdTechIdentifier buyer, final Uri biddingUri, List<Double> bids) {
return createDBCustomAudience(
buyer,
+ DEFAULT_CUSTOM_AUDIENCE_NAME_SUFFIX,
biddingUri,
bids,
CustomAudienceFixture.VALID_ACTIVATION_TIME,
@@ -2272,7 +2491,7 @@ public class AdSelectionE2ETest {
.setCallerPackageName(callerPackageName)
.build();
- adSelectionService.runAdSelection(input, adSelectionTestCallback);
+ adSelectionService.runAdSelection(input, mMockCallerMetadata, adSelectionTestCallback);
adSelectionTestCallback.mCountDownLatch.await();
return adSelectionTestCallback;
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionScriptEngineTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionScriptEngineTest.java
index 9b918d421..8e331e693 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionScriptEngineTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionScriptEngineTest.java
@@ -183,7 +183,7 @@ public class AdSelectionScriptEngineTest {
final List<AdWithBid> result =
generateBids(
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}",
@@ -192,7 +192,6 @@ public class AdSelectionScriptEngineTest {
AdSelectionSignals.EMPTY,
AdSelectionSignals.EMPTY,
AdSelectionSignals.EMPTY,
- AdSelectionSignals.EMPTY,
CUSTOM_AUDIENCE_SIGNALS_1);
assertThat(result).containsExactly(new AdWithBid(ad1, 1.1), new AdWithBid(ad2, 2.1));
}
@@ -207,7 +206,7 @@ public class AdSelectionScriptEngineTest {
final List<AdWithBid> result =
generateBids(
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 1, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}",
@@ -216,7 +215,6 @@ public class AdSelectionScriptEngineTest {
AdSelectionSignals.EMPTY,
AdSelectionSignals.EMPTY,
AdSelectionSignals.EMPTY,
- AdSelectionSignals.EMPTY,
CUSTOM_AUDIENCE_SIGNALS_1);
assertThat(result).isEmpty();
}
@@ -233,7 +231,7 @@ public class AdSelectionScriptEngineTest {
// The response for the second add doesn't include the bid so we cannot
// parse and AdWithBid
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " if (ad.metadata.result > 2) return {'status': 0, 'ad': ad };\n"
+ " else return {'status': 0, 'ad': ad, 'bid': 10 };\n"
@@ -243,7 +241,6 @@ public class AdSelectionScriptEngineTest {
AdSelectionSignals.EMPTY,
AdSelectionSignals.EMPTY,
AdSelectionSignals.EMPTY,
- AdSelectionSignals.EMPTY,
CUSTOM_AUDIENCE_SIGNALS_1);
assertThat(result).isEmpty();
}
@@ -339,7 +336,6 @@ public class AdSelectionScriptEngineTest {
AdSelectionSignals perBuyerSignals,
AdSelectionSignals trustedBiddingSignals,
AdSelectionSignals contextualSignals,
- AdSelectionSignals userSignals,
CustomAudienceSignals customAudienceSignals)
throws Exception {
return waitForFuture(
@@ -352,7 +348,6 @@ public class AdSelectionScriptEngineTest {
perBuyerSignals,
trustedBiddingSignals,
contextualSignals,
- userSignals,
customAudienceSignals);
});
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionServiceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionServiceImplTest.java
index abdb5e6f3..4de41741a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionServiceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionServiceImplTest.java
@@ -31,12 +31,13 @@ import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICE
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__REMOVE_AD_SELECTION_CONFIG_REMOTE_INFO_OVERRIDE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__RESET_ALL_AD_SELECTION_CONFIG_REMOTE_OVERRIDES;
-import static com.android.adservices.stats.FledgeApiCallStatsMatcher.aCallStatForFledgeApiWithStatus;
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.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
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.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -71,6 +72,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.MockWebServerRuleFactory;
import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.adselection.AdSelectionDatabase;
import com.android.adservices.data.adselection.AdSelectionEntryDao;
import com.android.adservices.data.adselection.CustomAudienceSignals;
@@ -79,6 +81,7 @@ import com.android.adservices.data.adselection.DBAdSelectionOverride;
import com.android.adservices.data.adselection.DBBuyerDecisionLogic;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.CustomAudienceDatabase;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AdServicesHttpsClient;
import com.android.adservices.service.common.AppImportanceFilter;
@@ -121,8 +124,10 @@ import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class AdSelectionServiceImplTest {
@@ -152,47 +157,58 @@ public class AdSelectionServiceImplTest {
AD_SERVICES_API_CALLED__API_NAME__REMOVE_AD_SELECTION_CONFIG_REMOTE_INFO_OVERRIDE;
private static final int SHORT_API_NAME_RESET_ALL_OVERRIDES =
AD_SERVICES_API_CALLED__API_NAME__RESET_ALL_AD_SELECTION_CONFIG_REMOTE_OVERRIDES;
+ private static final String TIMEOUT_MESSAGE = "Timed out:";
private final ExecutorService mLightweightExecutorService =
AdServicesExecutors.getLightWeightExecutor();
private final ExecutorService mBackgroundExecutorService =
AdServicesExecutors.getBackgroundExecutor();
+ private final ScheduledThreadPoolExecutor mScheduledExecutor =
+ AdServicesExecutors.getScheduler();
private final String mSellerReportingPath = "/reporting/seller";
private final String mBuyerReportingPath = "/reporting/buyer";
- private final String mFetchJavaScriptPath = "/fetchJavascript/";
+ private final String mFetchJavaScriptPathSeller = "/fetchJavascript/seller";
+ private final String mFetchJavaScriptPathBuyer = "/fetchJavascript/buyer";
private final String mFetchTrustedScoringSignalsPath = "/fetchTrustedSignals/";
private final AdTechIdentifier mContextualSignals =
AdTechIdentifier.fromString("{\"contextual_signals\":1}");
- private static final String TIMEOUT_MESSAGE = "Timed out:";
private final int mBytesPerPeriod = 1;
private final AdServicesHttpsClient mClient =
new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor());
- private Flags mFlags = new FlagsWithEnrollmentCheckEnabledSwitch(false);
- private MockitoSession mStaticMockSession = null;
- @Spy private final AdServicesLogger mAdServicesLoggerSpy = AdServicesLoggerImpl.getInstance();
+ private final AdServicesLogger mAdServicesLoggerMock =
+ ExtendedMockito.mock(AdServicesLoggerImpl.class);
+ @Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
+ // This object access some system APIs
+ @Mock public DevContextFilter mDevContextFilter;
+ @Mock public AppImportanceFilter mAppImportanceFilter;
@Spy
FledgeAuthorizationFilter mFledgeAuthorizationFilterSpy =
- FledgeAuthorizationFilter.create(CONTEXT, mAdServicesLoggerSpy);
+ new FledgeAuthorizationFilter(
+ CONTEXT.getPackageManager(),
+ new EnrollmentDao(CONTEXT, DbTestUtil.getDbHelperForTest()),
+ mAdServicesLoggerMock);
+
+ private Flags mFlags = new FlagsWithEnrollmentCheckEnabledSwitch(false);
@Spy
FledgeAllowListsFilter mFledgeAllowListsFilterSpy =
- new FledgeAllowListsFilter(mFlags, mAdServicesLoggerSpy);
+ new FledgeAllowListsFilter(mFlags, mAdServicesLoggerMock);
- @Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
- // This object access some system APIs
- @Mock public DevContextFilter mDevContextFilter;
- @Mock public AppImportanceFilter mAppImportanceFilter;
+ private MockitoSession mStaticMockSession = null;
@Mock private ConsentManager mConsentManagerMock;
private CustomAudienceDao mCustomAudienceDao;
private AdSelectionEntryDao mAdSelectionEntryDao;
private AdSelectionConfig.Builder mAdSelectionConfigBuilder;
+ private Uri mBiddingLogicUri;
+ private CustomAudienceSignals mCustomAudienceSignals;
+
@Before
public void setUp() {
mStaticMockSession =
ExtendedMockito.mockitoSession()
.spyStatic(JSScriptEngine.class)
- // mAdServicesLoggerSpy is not referenced in many tests
+ // mAdServicesLoggerMock is not referenced in many tests
.strictness(Strictness.LENIENT)
.initMocks(this)
.startMocking();
@@ -208,16 +224,36 @@ public class AdSelectionServiceImplTest {
.build()
.adSelectionEntryDao();
+ mBiddingLogicUri = (mMockWebServerRule.uriForPath(mFetchJavaScriptPathBuyer));
+
+ mCustomAudienceSignals =
+ CustomAudienceSignalsFixture.aCustomAudienceSignalsBuilder()
+ .setBuyer(AdTechIdentifier.fromString(mBiddingLogicUri.getHost()))
+ .build();
+
+ Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals =
+ Map.of(
+ AdTechIdentifier.fromString("test.com"),
+ AdSelectionSignals.fromString("{\"buyer_signals\":1}"),
+ AdTechIdentifier.fromString("test2.com"),
+ AdSelectionSignals.fromString("{\"buyer_signals\":2}"),
+ AdTechIdentifier.fromString("test3.com"),
+ AdSelectionSignals.fromString("{\"buyer_signals\":3}"),
+ AdTechIdentifier.fromString(mBiddingLogicUri.getHost()),
+ AdSelectionSignals.fromString("{\"buyer_signals\":0}"));
+
mAdSelectionConfigBuilder =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
- .uriForPath(mFetchJavaScriptPath)
+ .uriForPath(mFetchJavaScriptPathSeller)
.getHost()))
- .setDecisionLogicUri(mMockWebServerRule.uriForPath(mFetchJavaScriptPath))
+ .setDecisionLogicUri(
+ mMockWebServerRule.uriForPath(mFetchJavaScriptPathSeller))
.setTrustedScoringSignalsUri(
- mMockWebServerRule.uriForPath(mFetchTrustedScoringSignalsPath));
+ mMockWebServerRule.uriForPath(mFetchTrustedScoringSignalsPath))
+ .setPerBuyerSignals(perBuyerSignals);
}
@After
@@ -229,11 +265,13 @@ public class AdSelectionServiceImplTest {
@Test
public void testReportImpressionSuccess() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
+ Uri biddingLogicUri = (mMockWebServerRule.uriForPath(mFetchJavaScriptPathBuyer));
+
String sellerDecisionLogicJs =
"function reportResult(ad_selection_config, render_uri, bid, contextual_signals) {"
+ " \n"
@@ -260,19 +298,16 @@ public class AdSelectionServiceImplTest {
DBBuyerDecisionLogic dbBuyerDecisionLogic =
new DBBuyerDecisionLogic.Builder()
- .setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
+ .setBiddingLogicUri(biddingLogicUri)
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
- .setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
+ .setBiddingLogicUri(biddingLogicUri)
.setWinningAdRenderUri(RENDER_URI)
.setWinningAdBid(BID)
.setCreationTimestamp(ACTIVATION_TIME)
@@ -296,9 +331,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -314,26 +350,23 @@ public class AdSelectionServiceImplTest {
assertTrue(callback.mIsSuccess);
RecordedRequest fetchRequest = server.takeRequest();
- assertEquals(mFetchJavaScriptPath, fetchRequest.getPath());
+ assertEquals(mFetchJavaScriptPathSeller, fetchRequest.getPath());
List<String> notifications =
ImmutableList.of(server.takeRequest().getPath(), server.takeRequest().getPath());
assertThat(notifications).containsExactly(mSellerReportingPath, mBuyerReportingPath);
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_SUCCESS),
+ anyInt());
}
@Test
public void testReportImpressionWithRevokedUserConsentSuccess() throws Exception {
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent();
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -361,13 +394,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -393,9 +423,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -412,20 +443,16 @@ public class AdSelectionServiceImplTest {
assertTrue(callback.mIsSuccess);
assertEquals(0, server.getRequestCount());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_USER_CONSENT_REVOKED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_USER_CONSENT_REVOKED));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_USER_CONSENT_REVOKED),
+ anyInt());
}
@Test
public void testReportImpressionFailsWhenReportResultTakesTooLong() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -463,13 +490,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -495,9 +519,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -515,19 +540,16 @@ public class AdSelectionServiceImplTest {
assertEquals(callback.mFledgeErrorResponse.getStatusCode(), STATUS_INTERNAL_ERROR);
assertTrue(callback.mFledgeErrorResponse.getErrorMessage().contains(TIMEOUT_MESSAGE));
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_INTERNAL_ERROR),
+ anyInt());
}
@Test
public void testReportImpressionFailsWhenReportWinTakesTooLong() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -565,13 +587,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -597,9 +616,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -617,19 +637,16 @@ public class AdSelectionServiceImplTest {
assertEquals(callback.mFledgeErrorResponse.getStatusCode(), STATUS_INTERNAL_ERROR);
assertTrue(callback.mFledgeErrorResponse.getErrorMessage().contains(TIMEOUT_MESSAGE));
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_INTERNAL_ERROR),
+ anyInt());
}
@Test
public void testReportImpressionFailsWhenOverallJSTimesOut() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -673,13 +690,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -705,9 +719,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -725,19 +740,16 @@ public class AdSelectionServiceImplTest {
assertEquals(callback.mFledgeErrorResponse.getStatusCode(), STATUS_INTERNAL_ERROR);
assertTrue(callback.mFledgeErrorResponse.getErrorMessage().contains(TIMEOUT_MESSAGE));
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_INTERNAL_ERROR),
+ anyInt());
}
@Test
public void testReportImpressionFailsWhenJSFetchTakesTooLong() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -776,13 +788,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -808,9 +817,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -828,19 +838,16 @@ public class AdSelectionServiceImplTest {
assertEquals(callback.mFledgeErrorResponse.getStatusCode(), STATUS_INTERNAL_ERROR);
assertTrue(callback.mFledgeErrorResponse.getErrorMessage().contains(TIMEOUT_MESSAGE));
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_INTERNAL_ERROR),
+ anyInt());
}
@Test
public void testReportImpressionFailsWithInvalidAdSelectionId() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -874,13 +881,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -906,9 +910,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -926,20 +931,16 @@ public class AdSelectionServiceImplTest {
assertFalse(callback.mIsSuccess);
assertEquals(callback.mFledgeErrorResponse.getStatusCode(), STATUS_INVALID_ARGUMENT);
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INVALID_ARGUMENT);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INVALID_ARGUMENT));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_INVALID_ARGUMENT),
+ anyInt());
}
@Test
public void testReportImpressionBadSellerJavascriptFailsWithInternalError() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -973,13 +974,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -1004,9 +1002,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1024,19 +1023,16 @@ public class AdSelectionServiceImplTest {
assertFalse(callback.mIsSuccess);
assertEquals(callback.mFledgeErrorResponse.getStatusCode(), STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_INTERNAL_ERROR),
+ anyInt());
}
@Test
public void testReportImpressionBadBuyerJavascriptFailsWithInternalError() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -1070,13 +1066,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(inValidBuyerDecisionLogicJsMissingCurlyBracket)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -1102,9 +1095,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1122,19 +1116,16 @@ public class AdSelectionServiceImplTest {
assertFalse(callback.mIsSuccess);
assertEquals(callback.mFledgeErrorResponse.getStatusCode(), STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_INTERNAL_ERROR),
+ anyInt());
}
@Test
public void testReportImpressionUseDevOverrideForSellerJS() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -1168,13 +1159,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -1220,9 +1208,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1241,19 +1230,16 @@ public class AdSelectionServiceImplTest {
assertThat(notifications).containsExactly(mSellerReportingPath, mBuyerReportingPath);
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_SUCCESS),
+ anyInt());
}
@Test
public void testOverrideAdSelectionConfigRemoteInfoSuccess() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
DevContext.builder()
@@ -1270,9 +1256,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1294,16 +1281,14 @@ public class AdSelectionServiceImplTest {
adSelectionConfig),
TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy).logFledgeApiCallStats(SHORT_API_NAME_OVERRIDE, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(SHORT_API_NAME_OVERRIDE, STATUS_SUCCESS));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(eq(SHORT_API_NAME_OVERRIDE), eq(STATUS_SUCCESS), anyInt());
}
@Test
public void testOverrideAdSelectionConfigRemoteInfoWithRevokedUserConsentSuccess()
throws Exception {
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -1321,9 +1306,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1345,17 +1331,14 @@ public class AdSelectionServiceImplTest {
adSelectionConfig),
TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_OVERRIDE, STATUS_USER_CONSENT_REVOKED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_OVERRIDE, STATUS_USER_CONSENT_REVOKED));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_OVERRIDE), eq(STATUS_USER_CONSENT_REVOKED), anyInt());
}
@Test
public void testOverrideAdSelectionConfigRemoteInfoFailsWithDevOptionsDisabled() {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
@@ -1368,9 +1351,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1393,17 +1377,14 @@ public class AdSelectionServiceImplTest {
adSelectionConfig),
TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_OVERRIDE, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_OVERRIDE, STATUS_INTERNAL_ERROR));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_OVERRIDE), eq(STATUS_INTERNAL_ERROR), anyInt());
}
@Test
public void testRemoveAdSelectionConfigRemoteInfoOverrideSuccess() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
DevContext.builder()
@@ -1420,9 +1401,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1455,18 +1437,15 @@ public class AdSelectionServiceImplTest {
mAdSelectionEntryDao.doesAdSelectionOverrideExistForPackageName(
adSelectionConfigId, TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_REMOVE_OVERRIDE, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_REMOVE_OVERRIDE, STATUS_SUCCESS));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_REMOVE_OVERRIDE), eq(STATUS_SUCCESS), anyInt());
}
@Test
public void testRemoveAdSelectionConfigRemoteInfoOverrideWithRevokedUserConsentSuccess()
throws Exception {
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -1484,9 +1463,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1519,17 +1499,16 @@ public class AdSelectionServiceImplTest {
mAdSelectionEntryDao.doesAdSelectionOverrideExistForPackageName(
adSelectionConfigId, TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_REMOVE_OVERRIDE, STATUS_USER_CONSENT_REVOKED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_REMOVE_OVERRIDE, STATUS_USER_CONSENT_REVOKED));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_REMOVE_OVERRIDE),
+ eq(STATUS_USER_CONSENT_REVOKED),
+ anyInt());
}
@Test
public void testRemoveAdSelectionConfigRemoteInfoOverrideFailsWithDevOptionsDisabled() {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
@@ -1542,9 +1521,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1576,18 +1556,15 @@ public class AdSelectionServiceImplTest {
mAdSelectionEntryDao.doesAdSelectionOverrideExistForPackageName(
adSelectionConfigId, TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_REMOVE_OVERRIDE, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_REMOVE_OVERRIDE, STATUS_INTERNAL_ERROR));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_REMOVE_OVERRIDE), eq(STATUS_INTERNAL_ERROR), anyInt());
}
@Test
public void testRemoveAdSelectionConfigRemoteInfoOverrideDoesNotDeleteWithIncorrectPackageName()
throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
String incorrectPackageName = "com.google.ppapi.test.incorrect";
when(mDevContextFilter.createDevContext())
@@ -1606,9 +1583,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1641,18 +1619,15 @@ public class AdSelectionServiceImplTest {
mAdSelectionEntryDao.doesAdSelectionOverrideExistForPackageName(
adSelectionConfigId, TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_REMOVE_OVERRIDE, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_REMOVE_OVERRIDE, STATUS_SUCCESS));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_REMOVE_OVERRIDE), eq(STATUS_SUCCESS), anyInt());
}
@Test
public void testResetAllAdSelectionConfigRemoteOverridesDoesNotDeleteWithIncorrectPackageName()
throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
String incorrectPackageName = "com.google.ppapi.test.incorrect";
when(mDevContextFilter.createDevContext())
@@ -1671,9 +1646,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1750,17 +1726,14 @@ public class AdSelectionServiceImplTest {
mAdSelectionEntryDao.doesAdSelectionOverrideExistForPackageName(
adSelectionConfigId3, TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_RESET_ALL_OVERRIDES, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_RESET_ALL_OVERRIDES, STATUS_SUCCESS));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_RESET_ALL_OVERRIDES), eq(STATUS_SUCCESS), anyInt());
}
@Test
public void testResetAllAdSelectionConfigRemoteOverridesSuccess() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
DevContext.builder()
@@ -1777,9 +1750,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1856,18 +1830,15 @@ public class AdSelectionServiceImplTest {
mAdSelectionEntryDao.doesAdSelectionOverrideExistForPackageName(
adSelectionConfigId3, TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_RESET_ALL_OVERRIDES, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_RESET_ALL_OVERRIDES, STATUS_SUCCESS));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_RESET_ALL_OVERRIDES), eq(STATUS_SUCCESS), anyInt());
}
@Test
public void testResetAllAdSelectionConfigRemoteOverridesWithRevokedUserConsentSuccess()
throws Exception {
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -1885,9 +1856,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -1964,18 +1936,16 @@ public class AdSelectionServiceImplTest {
mAdSelectionEntryDao.doesAdSelectionOverrideExistForPackageName(
adSelectionConfigId3, TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- SHORT_API_NAME_RESET_ALL_OVERRIDES, STATUS_USER_CONSENT_REVOKED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_RESET_ALL_OVERRIDES, STATUS_USER_CONSENT_REVOKED));
+ eq(SHORT_API_NAME_RESET_ALL_OVERRIDES),
+ eq(STATUS_USER_CONSENT_REVOKED),
+ anyInt());
}
@Test
public void testResetAllAdSelectionConfigRemoteOverridesFailsWithDevOptionsDisabled() {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
@@ -1988,9 +1958,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2065,12 +2036,11 @@ public class AdSelectionServiceImplTest {
mAdSelectionEntryDao.doesAdSelectionOverrideExistForPackageName(
adSelectionConfigId3, TEST_PACKAGE_NAME));
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(SHORT_API_NAME_RESET_ALL_OVERRIDES, STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- SHORT_API_NAME_RESET_ALL_OVERRIDES, STATUS_INTERNAL_ERROR));
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(SHORT_API_NAME_RESET_ALL_OVERRIDES),
+ eq(STATUS_INTERNAL_ERROR),
+ anyInt());
}
@Test
@@ -2087,9 +2057,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2101,7 +2072,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testReportImpressionForegroundCheckEnabledFails_throwsException() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
@@ -2122,9 +2093,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2151,7 +2123,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testReportImpressionForegroundCheckDisabled_acceptBackgroundApp() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(DevContext.createForDevOptionsDisabled());
@@ -2178,9 +2150,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
FlagsWithOverriddenFledgeChecks.createFlagsWithFledgeChecksDisabled(),
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2205,7 +2178,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testOverrideAdSelectionForegroundCheckEnabledFails_throwsException()
throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -2230,9 +2203,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2260,9 +2234,9 @@ public class AdSelectionServiceImplTest {
@Test
public void testOverrideAdSelectionForegroundCheckDisabled_acceptBackgroundApp()
throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -2287,9 +2261,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
FlagsWithOverriddenFledgeChecks.createFlagsWithFledgeChecksDisabled(),
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2309,7 +2284,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testRemoveOverrideForegroundCheckEnabledFails_throwsException() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -2333,9 +2308,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2358,7 +2334,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testRemoveOverrideForegroundCheckDisabled_acceptBackgroundApp() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -2382,9 +2358,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
FlagsWithOverriddenFledgeChecks.createFlagsWithFledgeChecksDisabled(),
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2401,7 +2378,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testResetAllOverridesForegroundCheckEnabledFails_throwsException()
throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -2425,9 +2402,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2448,7 +2426,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testResetAllOverridesForegroundCheckDisabled_acceptBackgroundApp()
throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
when(mDevContextFilter.createDevContext())
.thenReturn(
@@ -2472,9 +2450,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
FlagsWithOverriddenFledgeChecks.createFlagsWithFledgeChecksDisabled(),
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2487,7 +2466,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testReportImpressionFailsWithInvalidPackageName() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
String otherPackageName = CommonFixture.TEST_PACKAGE_NAME + "incorrectPackage";
@@ -2523,13 +2502,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -2555,9 +2531,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2582,7 +2559,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testReportImpressionFailsWhenAppCannotUsePPApi() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doThrow(new FledgeAuthorizationFilter.AdTechNotAllowedException())
.when(mFledgeAllowListsFilterSpy)
.assertAppCanUsePpapi(
@@ -2621,13 +2598,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -2653,9 +2627,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2676,20 +2651,16 @@ public class AdSelectionServiceImplTest {
callback.mFledgeErrorResponse.getErrorMessage());
// TODO(b/242139312): Remove atLeastOnce once this the double logging is addressed
- Mockito.verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
+ Mockito.verify(mAdServicesLoggerMock, Mockito.atLeastOnce())
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_CALLER_NOT_ALLOWED);
- verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_CALLER_NOT_ALLOWED));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_CALLER_NOT_ALLOWED),
+ anyInt());
}
@Test
public void testReportImpressionFailsWhenAdTechFailsEnrollmentCheck() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Reset flags to perform enrollment check
mFlags = new FlagsWithEnrollmentCheckEnabledSwitch(true);
@@ -2727,13 +2698,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -2759,9 +2727,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2782,20 +2751,16 @@ public class AdSelectionServiceImplTest {
callback.mFledgeErrorResponse.getErrorMessage());
// TODO(b/242139312): Remove atLeastOnce once this the double logging is addressed
- Mockito.verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
+ Mockito.verify(mAdServicesLoggerMock, Mockito.atLeastOnce())
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_CALLER_NOT_ALLOWED);
- verify(mAdServicesLoggerSpy, Mockito.atLeastOnce())
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_CALLER_NOT_ALLOWED));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_CALLER_NOT_ALLOWED),
+ anyInt());
}
@Test
public void testReportImpressionSucceedsWhenAdTechPassesEnrollmentCheck() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Reset flags to perform enrollment check
mFlags = new FlagsWithEnrollmentCheckEnabledSwitch(true);
@@ -2833,13 +2798,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -2873,9 +2835,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -2891,21 +2854,18 @@ public class AdSelectionServiceImplTest {
assertTrue(callback.mIsSuccess);
RecordedRequest fetchRequest = server.takeRequest();
- assertEquals(mFetchJavaScriptPath, fetchRequest.getPath());
+ assertEquals(mFetchJavaScriptPathSeller, fetchRequest.getPath());
List<String> notifications =
ImmutableList.of(server.takeRequest().getPath(), server.takeRequest().getPath());
assertThat(notifications).containsExactly(mSellerReportingPath, mBuyerReportingPath);
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_SUCCESS),
+ anyInt());
}
private AdSelectionOverrideTestCallback callAddOverride(
@@ -2924,7 +2884,9 @@ public class AdSelectionServiceImplTest {
resultLatch.countDown();
return null;
};
- doAnswer(countDownAnswer).when(mAdServicesLoggerSpy).logApiCallStats(any());
+ doAnswer(countDownAnswer)
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
adSelectionService.overrideAdSelectionConfigRemoteInfo(
adSelectionConfig, decisionLogicJS, trustedScoringSignals, callback);
@@ -2945,7 +2907,9 @@ public class AdSelectionServiceImplTest {
resultLatch.countDown();
return null;
};
- doAnswer(countDownAnswer).when(mAdServicesLoggerSpy).logApiCallStats(any());
+ doAnswer(countDownAnswer)
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
adSelectionService.removeAdSelectionConfigRemoteInfoOverride(adSelectionConfig, callback);
resultLatch.await();
@@ -2954,7 +2918,7 @@ public class AdSelectionServiceImplTest {
@Test
public void testAdSelectionConfigInvalidSellerAndSellerUris() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -2982,13 +2946,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -3016,9 +2977,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mFlags,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -3038,20 +3000,16 @@ public class AdSelectionServiceImplTest {
assertEquals(
"Error response code mismatch", STATUS_INVALID_ARGUMENT, response.getStatusCode());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INVALID_ARGUMENT);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_INVALID_ARGUMENT));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_INVALID_ARGUMENT),
+ anyInt());
}
@Test
public void testReportImpressionSuccessThrottledSubsequentCallFailure() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
@@ -3086,13 +3044,10 @@ public class AdSelectionServiceImplTest {
.setBuyerDecisionLogicJs(buyerDecisionLogicJs)
.build();
- CustomAudienceSignals customAudienceSignals =
- CustomAudienceSignalsFixture.aCustomAudienceSignals();
-
DBAdSelection dbAdSelection =
new DBAdSelection.Builder()
.setAdSelectionId(AD_SELECTION_ID)
- .setCustomAudienceSignals(customAudienceSignals)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
.setContextualSignals(mContextualSignals.toString())
.setBiddingLogicUri(BUYER_BIDDING_LOGIC_URI)
.setWinningAdRenderUri(RENDER_URI)
@@ -3157,9 +3112,10 @@ public class AdSelectionServiceImplTest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
flagsWithThrottling,
CallingAppUidSupplierProcessImpl.create(),
mFledgeAuthorizationFilterSpy,
@@ -3182,16 +3138,13 @@ public class AdSelectionServiceImplTest {
assertTrue(callbackFirstCall.mIsSuccess);
RecordedRequest fetchRequest = server.takeRequest();
- assertEquals(mFetchJavaScriptPath, fetchRequest.getPath());
+ assertEquals(mFetchJavaScriptPathSeller, fetchRequest.getPath());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION, STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION,
- STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_SUCCESS),
+ anyInt());
assertFalse(callbackSubsequentCall.mIsSuccess);
assertEquals(
@@ -3203,6 +3156,312 @@ public class AdSelectionServiceImplTest {
resetThrottlerToNoRateLimits();
}
+ @Test
+ public void testReportImpressionDoestNotReportWhenUrisDoNotMatchDomain() throws Exception {
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+
+ // Instantiate a server with different domain from buyer and seller for reporting
+ MockWebServer reportingServer = new MockWebServer();
+ reportingServer.play();
+ Uri sellerReportingUri = Uri.parse(reportingServer.getUrl(mSellerReportingPath).toString());
+ Uri buyerReportingUri = Uri.parse(reportingServer.getUrl(mBuyerReportingPath).toString());
+
+ Uri biddingLogicUri = (mMockWebServerRule.uriForPath(mFetchJavaScriptPathBuyer));
+
+ String sellerDecisionLogicJs =
+ "function reportResult(ad_selection_config, render_uri, bid, contextual_signals) {"
+ + " \n"
+ + " return {'status': 0, 'results': {'signals_for_buyer':"
+ + " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
+ + sellerReportingUri
+ + "' } };\n"
+ + "}";
+
+ String buyerDecisionLogicJs =
+ "function reportWin(ad_selection_signals, per_buyer_signals, signals_for_buyer,"
+ + " contextual_signals, custom_audience_signals) { \n"
+ + " return {'status': 0, 'results': {'reporting_uri': '"
+ + buyerReportingUri
+ + "' } };\n"
+ + "}";
+
+ MockWebServer server =
+ mMockWebServerRule.startMockWebServer(
+ List.of(new MockResponse().setBody(sellerDecisionLogicJs)));
+
+ DBBuyerDecisionLogic dbBuyerDecisionLogic =
+ new DBBuyerDecisionLogic.Builder()
+ .setBiddingLogicUri(biddingLogicUri)
+ .setBuyerDecisionLogicJs(buyerDecisionLogicJs)
+ .build();
+
+ DBAdSelection dbAdSelection =
+ new DBAdSelection.Builder()
+ .setAdSelectionId(AD_SELECTION_ID)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
+ .setContextualSignals(mContextualSignals.toString())
+ .setBiddingLogicUri(biddingLogicUri)
+ .setWinningAdRenderUri(RENDER_URI)
+ .setWinningAdBid(BID)
+ .setCreationTimestamp(ACTIVATION_TIME)
+ .setCallerPackageName(CommonFixture.TEST_PACKAGE_NAME)
+ .build();
+
+ mAdSelectionEntryDao.persistAdSelection(dbAdSelection);
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(dbBuyerDecisionLogic);
+
+ AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
+
+ when(mDevContextFilter.createDevContext())
+ .thenReturn(DevContext.createForDevOptionsDisabled());
+
+ AdSelectionServiceImpl adSelectionService =
+ new AdSelectionServiceImpl(
+ mAdSelectionEntryDao,
+ mCustomAudienceDao,
+ mClient,
+ mDevContextFilter,
+ mAppImportanceFilter,
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ CONTEXT,
+ mConsentManagerMock,
+ mAdServicesLoggerMock,
+ mFlags,
+ CallingAppUidSupplierProcessImpl.create(),
+ mFledgeAuthorizationFilterSpy,
+ mFledgeAllowListsFilterSpy);
+
+ ReportImpressionInput input =
+ new ReportImpressionInput.Builder()
+ .setAdSelectionId(AD_SELECTION_ID)
+ .setAdSelectionConfig(adSelectionConfig)
+ .setCallerPackageName(TEST_PACKAGE_NAME)
+ .build();
+ ReportImpressionTestCallback callback = callReportImpression(adSelectionService, input);
+ assertTrue(callback.mIsSuccess);
+ RecordedRequest fetchRequest = server.takeRequest();
+ assertEquals(mFetchJavaScriptPathSeller, fetchRequest.getPath());
+
+ // Assert that reporting didn't happen
+ assertEquals(reportingServer.getRequestCount(), 0);
+
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_SUCCESS),
+ anyInt());
+ }
+
+ @Test
+ public void testReportImpressionOnlyReportsBuyerWhenSellerReportingUriDoesNotMatchDomain()
+ throws Exception {
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+
+ Uri buyerReportingUri = mMockWebServerRule.uriForPath(mBuyerReportingPath);
+
+ // Instantiate a server with a different domain than seller
+ MockWebServer sellerServer = new MockWebServer();
+ sellerServer.play();
+ Uri sellerReportingUri = Uri.parse(sellerServer.getUrl(mSellerReportingPath).toString());
+
+ Uri biddingLogicUri = (mMockWebServerRule.uriForPath(mFetchJavaScriptPathBuyer));
+
+ String sellerDecisionLogicJs =
+ "function reportResult(ad_selection_config, render_uri, bid, contextual_signals) {"
+ + " \n"
+ + " return {'status': 0, 'results': {'signals_for_buyer':"
+ + " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
+ + sellerReportingUri
+ + "' } };\n"
+ + "}";
+
+ String buyerDecisionLogicJs =
+ "function reportWin(ad_selection_signals, per_buyer_signals, signals_for_buyer,"
+ + " contextual_signals, custom_audience_signals) { \n"
+ + " return {'status': 0, 'results': {'reporting_uri': '"
+ + buyerReportingUri
+ + "' } };\n"
+ + "}";
+
+ MockWebServer server =
+ mMockWebServerRule.startMockWebServer(
+ List.of(
+ new MockResponse().setBody(sellerDecisionLogicJs),
+ new MockResponse()));
+
+ DBBuyerDecisionLogic dbBuyerDecisionLogic =
+ new DBBuyerDecisionLogic.Builder()
+ .setBiddingLogicUri(biddingLogicUri)
+ .setBuyerDecisionLogicJs(buyerDecisionLogicJs)
+ .build();
+
+ DBAdSelection dbAdSelection =
+ new DBAdSelection.Builder()
+ .setAdSelectionId(AD_SELECTION_ID)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
+ .setContextualSignals(mContextualSignals.toString())
+ .setBiddingLogicUri(biddingLogicUri)
+ .setWinningAdRenderUri(RENDER_URI)
+ .setWinningAdBid(BID)
+ .setCreationTimestamp(ACTIVATION_TIME)
+ .setCallerPackageName(CommonFixture.TEST_PACKAGE_NAME)
+ .build();
+
+ mAdSelectionEntryDao.persistAdSelection(dbAdSelection);
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(dbBuyerDecisionLogic);
+
+ AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
+
+ when(mDevContextFilter.createDevContext())
+ .thenReturn(DevContext.createForDevOptionsDisabled());
+
+ AdSelectionServiceImpl adSelectionService =
+ new AdSelectionServiceImpl(
+ mAdSelectionEntryDao,
+ mCustomAudienceDao,
+ mClient,
+ mDevContextFilter,
+ mAppImportanceFilter,
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ CONTEXT,
+ mConsentManagerMock,
+ mAdServicesLoggerMock,
+ mFlags,
+ CallingAppUidSupplierProcessImpl.create(),
+ mFledgeAuthorizationFilterSpy,
+ mFledgeAllowListsFilterSpy);
+
+ ReportImpressionInput input =
+ new ReportImpressionInput.Builder()
+ .setAdSelectionId(AD_SELECTION_ID)
+ .setAdSelectionConfig(adSelectionConfig)
+ .setCallerPackageName(TEST_PACKAGE_NAME)
+ .build();
+ ReportImpressionTestCallback callback = callReportImpression(adSelectionService, input);
+ assertTrue(callback.mIsSuccess);
+ RecordedRequest fetchRequest = server.takeRequest();
+ assertEquals(mFetchJavaScriptPathSeller, fetchRequest.getPath());
+
+ assertEquals(mBuyerReportingPath, server.takeRequest().getPath());
+
+ // Assert that buyer reporting didn't happen
+ assertEquals(sellerServer.getRequestCount(), 0);
+
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_SUCCESS),
+ anyInt());
+ }
+
+ @Test
+ public void testReportImpressionOnlyReportsSellerWhenBuyerReportingUriDoesNotMatchDomain()
+ throws Exception {
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+
+ Uri sellerReportingUri = mMockWebServerRule.uriForPath(mSellerReportingPath);
+
+ // Instantiate a server with a different domain than buyer
+ MockWebServer buyerServer = new MockWebServer();
+ buyerServer.play();
+ Uri buyerReportingUri = Uri.parse(buyerServer.getUrl(mBuyerReportingPath).toString());
+
+ Uri biddingLogicUri = (mMockWebServerRule.uriForPath(mFetchJavaScriptPathBuyer));
+
+ String sellerDecisionLogicJs =
+ "function reportResult(ad_selection_config, render_uri, bid, contextual_signals) {"
+ + " \n"
+ + " return {'status': 0, 'results': {'signals_for_buyer':"
+ + " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
+ + sellerReportingUri
+ + "' } };\n"
+ + "}";
+
+ String buyerDecisionLogicJs =
+ "function reportWin(ad_selection_signals, per_buyer_signals, signals_for_buyer,"
+ + " contextual_signals, custom_audience_signals) { \n"
+ + " return {'status': 0, 'results': {'reporting_uri': '"
+ + buyerReportingUri
+ + "' } };\n"
+ + "}";
+
+ MockWebServer server =
+ mMockWebServerRule.startMockWebServer(
+ List.of(
+ new MockResponse().setBody(sellerDecisionLogicJs),
+ new MockResponse()));
+
+ DBBuyerDecisionLogic dbBuyerDecisionLogic =
+ new DBBuyerDecisionLogic.Builder()
+ .setBiddingLogicUri(biddingLogicUri)
+ .setBuyerDecisionLogicJs(buyerDecisionLogicJs)
+ .build();
+
+ DBAdSelection dbAdSelection =
+ new DBAdSelection.Builder()
+ .setAdSelectionId(AD_SELECTION_ID)
+ .setCustomAudienceSignals(mCustomAudienceSignals)
+ .setContextualSignals(mContextualSignals.toString())
+ .setBiddingLogicUri(biddingLogicUri)
+ .setWinningAdRenderUri(RENDER_URI)
+ .setWinningAdBid(BID)
+ .setCreationTimestamp(ACTIVATION_TIME)
+ .setCallerPackageName(CommonFixture.TEST_PACKAGE_NAME)
+ .build();
+
+ mAdSelectionEntryDao.persistAdSelection(dbAdSelection);
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(dbBuyerDecisionLogic);
+
+ AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
+
+ when(mDevContextFilter.createDevContext())
+ .thenReturn(DevContext.createForDevOptionsDisabled());
+
+ AdSelectionServiceImpl adSelectionService =
+ new AdSelectionServiceImpl(
+ mAdSelectionEntryDao,
+ mCustomAudienceDao,
+ mClient,
+ mDevContextFilter,
+ mAppImportanceFilter,
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ CONTEXT,
+ mConsentManagerMock,
+ mAdServicesLoggerMock,
+ mFlags,
+ CallingAppUidSupplierProcessImpl.create(),
+ mFledgeAuthorizationFilterSpy,
+ mFledgeAllowListsFilterSpy);
+
+ ReportImpressionInput input =
+ new ReportImpressionInput.Builder()
+ .setAdSelectionId(AD_SELECTION_ID)
+ .setAdSelectionConfig(adSelectionConfig)
+ .setCallerPackageName(TEST_PACKAGE_NAME)
+ .build();
+ ReportImpressionTestCallback callback = callReportImpression(adSelectionService, input);
+ assertTrue(callback.mIsSuccess);
+ RecordedRequest fetchRequest = server.takeRequest();
+ assertEquals(mFetchJavaScriptPathSeller, fetchRequest.getPath());
+
+ assertEquals(mSellerReportingPath, server.takeRequest().getPath());
+
+ // Assert that buyer reporting didn't happen
+ assertEquals(buyerServer.getRequestCount(), 0);
+
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__REPORT_IMPRESSION),
+ eq(STATUS_SUCCESS),
+ anyInt());
+ }
+
/**
* Given Throttler is singleton, & shared across tests, this method should be invoked after
* tests that impose restrictive rate limits.
@@ -3225,7 +3484,9 @@ public class AdSelectionServiceImplTest {
resultLatch.countDown();
return null;
};
- doAnswer(countDownAnswer).when(mAdServicesLoggerSpy).logApiCallStats(any());
+ doAnswer(countDownAnswer)
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
adSelectionService.resetAllAdSelectionConfigRemoteOverrides(callback);
resultLatch.await();
@@ -3245,7 +3506,9 @@ public class AdSelectionServiceImplTest {
resultLatch.countDown();
return null;
};
- doAnswer(countDownAnswer).when(mAdServicesLoggerSpy).logApiCallStats(any());
+ doAnswer(countDownAnswer)
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
adSelectionService.reportImpression(requestParams, callback);
resultLatch.await();
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdsScoreGeneratorImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdsScoreGeneratorImplTest.java
index bfbed2b9a..66b2c175d 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdsScoreGeneratorImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdsScoreGeneratorImplTest.java
@@ -70,6 +70,7 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.stream.Collectors;
public class AdsScoreGeneratorImplTest {
@@ -86,6 +87,7 @@ public class AdsScoreGeneratorImplTest {
private ListeningExecutorService mLightweightExecutorService;
private ListeningExecutorService mBackgroundExecutorService;
private ListeningExecutorService mBlockingExecutorService;
+ private ScheduledThreadPoolExecutor mSchedulingExecutor;
private AdServicesHttpsClient mWebClient;
private String mSellerDecisionLogicJs;
@@ -114,6 +116,7 @@ public class AdsScoreGeneratorImplTest {
mLightweightExecutorService = AdServicesExecutors.getLightWeightExecutor();
mBackgroundExecutorService = AdServicesExecutors.getBackgroundExecutor();
mBlockingExecutorService = AdServicesExecutors.getBlockingExecutor();
+ mSchedulingExecutor = AdServicesExecutors.getScheduler();
mWebClient = new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor());
mAdBiddingOutcomeBuyer1 =
@@ -203,6 +206,7 @@ public class AdsScoreGeneratorImplTest {
mMockAdSelectionScriptEngine,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mSchedulingExecutor,
mWebClient,
mDevContext,
mAdSelectionEntryDao,
@@ -367,6 +371,7 @@ public class AdsScoreGeneratorImplTest {
mMockAdSelectionScriptEngine,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mSchedulingExecutor,
mWebClient,
mDevContext,
mAdSelectionEntryDao,
@@ -467,6 +472,7 @@ public class AdsScoreGeneratorImplTest {
mMockAdSelectionScriptEngine,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mSchedulingExecutor,
mWebClient,
mDevContext,
mAdSelectionEntryDao,
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceBiddingSignalsArgumentTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceBiddingSignalsArgumentTest.java
index 95742d7bf..2a8af7cdc 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceBiddingSignalsArgumentTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceBiddingSignalsArgumentTest.java
@@ -16,7 +16,7 @@
package com.android.adservices.service.adselection;
-import static com.android.adservices.service.adselection.AdSelectionScriptEngine.CUSTOM_AUDIENCE_SIGNALS_ARG_NAME;
+import static com.android.adservices.service.adselection.AdSelectionScriptEngine.CUSTOM_AUDIENCE_BIDDING_SIGNALS_ARG_NAME;
import static com.android.adservices.service.js.JSScriptArgument.jsonArg;
import static com.android.adservices.service.js.JSScriptArgument.numericArg;
import static com.android.adservices.service.js.JSScriptArgument.recordArg;
@@ -42,9 +42,11 @@ public class CustomAudienceBiddingSignalsArgumentTest {
public void testConversionToScriptArgument() throws JSONException {
JSScriptArgument caSignalJsArgument =
CustomAudienceBiddingSignalsArgument.asScriptArgument(
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME, mCustomAudienceSignals1);
+ CUSTOM_AUDIENCE_BIDDING_SIGNALS_ARG_NAME, mCustomAudienceSignals1);
matchCustomAudienceSignals(
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME, caSignalJsArgument, mCustomAudienceSignals1);
+ CUSTOM_AUDIENCE_BIDDING_SIGNALS_ARG_NAME,
+ caSignalJsArgument,
+ mCustomAudienceSignals1);
}
private CustomAudienceSignals createCustomAudience(final String uniqueCASignalsPostfix) {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceReportingSignalsArgumentTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceReportingSignalsArgumentTest.java
new file mode 100644
index 000000000..1a71a8e78
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceReportingSignalsArgumentTest.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.adselection;
+
+import static com.android.adservices.service.adselection.CustomAudienceReportingSignalsArgument.NAME_FIELD_NAME;
+import static com.android.adservices.service.adselection.ReportImpressionScriptEngine.CUSTOM_AUDIENCE_REPORTING_SIGNALS_ARG_NAME;
+import static com.android.adservices.service.js.JSScriptArgument.recordArg;
+import static com.android.adservices.service.js.JSScriptArgument.stringArg;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+
+import com.android.adservices.data.adselection.CustomAudienceSignals;
+import com.android.adservices.service.js.JSScriptArgument;
+
+import org.json.JSONException;
+import org.junit.Test;
+
+import java.time.Instant;
+
+public class CustomAudienceReportingSignalsArgumentTest {
+ private final CustomAudienceSignals mCustomAudienceSignals = createCustomAudienceSignals("1");
+
+ @Test
+ public void testConversionToScriptArgument() throws JSONException {
+ JSScriptArgument caSignalJsArgument =
+ CustomAudienceReportingSignalsArgument.asScriptArgument(
+ CUSTOM_AUDIENCE_REPORTING_SIGNALS_ARG_NAME, mCustomAudienceSignals);
+ matchCustomAudienceSignals(
+ CUSTOM_AUDIENCE_REPORTING_SIGNALS_ARG_NAME,
+ caSignalJsArgument,
+ mCustomAudienceSignals);
+ }
+
+ private CustomAudienceSignals createCustomAudienceSignals(final String uniqueCASignalsPostfix) {
+ return new CustomAudienceSignals.Builder()
+ .setOwner("test_owner" + uniqueCASignalsPostfix)
+ .setBuyer(AdTechIdentifier.fromString("test_buyer" + uniqueCASignalsPostfix))
+ .setName("test_name" + uniqueCASignalsPostfix)
+ .setActivationTime(Instant.now())
+ .setExpirationTime(Instant.now())
+ .setUserBiddingSignals(
+ AdSelectionSignals.fromString(
+ "{\"user_bidding_signals\":" + uniqueCASignalsPostfix + "}"))
+ .build();
+ }
+
+ private void matchCustomAudienceSignals(
+ final String argName,
+ final JSScriptArgument expectedJsArgument,
+ final CustomAudienceSignals actualSignals) {
+ assertThat(expectedJsArgument)
+ .isEqualTo(recordArg(argName, stringArg(NAME_FIELD_NAME, actualSignals.getName())));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceScoringSignalsArgumentTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceScoringSignalsArgumentTest.java
index 8c976451a..8d5eb16ca 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceScoringSignalsArgumentTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/CustomAudienceScoringSignalsArgumentTest.java
@@ -16,7 +16,7 @@
package com.android.adservices.service.adselection;
-import static com.android.adservices.service.adselection.AdSelectionScriptEngine.CUSTOM_AUDIENCE_SIGNALS_ARG_NAME;
+import static com.android.adservices.service.adselection.AdSelectionScriptEngine.CUSTOM_AUDIENCE_SCORING_SIGNALS_ARG_NAME;
import static com.android.adservices.service.js.JSScriptArgument.recordArg;
import static com.android.adservices.service.js.JSScriptArgument.stringArg;
@@ -44,9 +44,11 @@ public class CustomAudienceScoringSignalsArgumentTest {
public void testConversionToScriptArgument() throws JSONException {
JSScriptArgument caSignalJsArgument =
CustomAudienceScoringSignalsArgument.asScriptArgument(
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME, mCustomAudienceSignals1);
+ CUSTOM_AUDIENCE_SCORING_SIGNALS_ARG_NAME, mCustomAudienceSignals1);
matchCustomAudienceSignals(
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME, caSignalJsArgument, mCustomAudienceSignals1);
+ CUSTOM_AUDIENCE_SCORING_SIGNALS_ARG_NAME,
+ caSignalJsArgument,
+ mCustomAudienceSignals1);
}
@Test
@@ -55,16 +57,16 @@ public class CustomAudienceScoringSignalsArgumentTest {
ImmutableList.of(mCustomAudienceSignals1, mCustomAudienceSignals2);
JSScriptArgument customAudienceSignalsArgumentList =
CustomAudienceScoringSignalsArgument.asScriptArgument(
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME, customAudienceSignalsList);
+ CUSTOM_AUDIENCE_SCORING_SIGNALS_ARG_NAME, customAudienceSignalsList);
Assert.assertEquals(
"Custom Audience Signals JS argument name mismatch",
- CUSTOM_AUDIENCE_SIGNALS_ARG_NAME,
+ CUSTOM_AUDIENCE_SCORING_SIGNALS_ARG_NAME,
customAudienceSignalsArgumentList.name());
String expectedJsArgValue =
"const "
- + CUSTOM_AUDIENCE_SIGNALS_ARG_NAME
+ + CUSTOM_AUDIENCE_SCORING_SIGNALS_ARG_NAME
+ " = ["
+ createCAJsArrayListArgument(mCustomAudienceSignals1)
+ ","
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/JsFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/JsFetcherTest.java
new file mode 100644
index 000000000..879c6eb89
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/JsFetcherTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.adselection;
+
+import static org.junit.Assert.assertEquals;
+
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+import android.adservices.http.MockWebServerRule;
+import android.net.Uri;
+
+import androidx.room.Room;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.MockWebServerRuleFactory;
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.customaudience.CustomAudienceDatabase;
+import com.android.adservices.service.common.AdServicesHttpsClient;
+import com.android.adservices.service.devapi.CustomAudienceDevOverridesHelper;
+import com.android.adservices.service.devapi.DevContext;
+
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.mockwebserver.Dispatcher;
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
+import com.google.mockwebserver.RecordedRequest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+
+public class JsFetcherTest {
+ @Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private String mFetchJavaScriptPath = "/fetchJavascript/";
+ private Uri mFetchJsUri = mMockWebServerRule.uriForPath(mFetchJavaScriptPath);
+ private String mAppPackageName = "com.google.ppapi.test";
+
+ private DevContext mDevContext;
+ private CustomAudienceDao mCustomAudienceDao;
+ private ListeningExecutorService mLightweightExecutorService;
+ private ListeningExecutorService mBackgroundExecutorService;
+ private AdServicesHttpsClient mWebClient;
+
+ private MockWebServerRule.RequestMatcher<String> mRequestMatcherExactMatch;
+ private Dispatcher mDefaultDispatcher;
+ private MockWebServer mServer;
+
+ @Before
+ public void setUp() throws Exception {
+ mLightweightExecutorService = AdServicesExecutors.getLightWeightExecutor();
+ mBackgroundExecutorService = AdServicesExecutors.getBackgroundExecutor();
+ mWebClient = new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor());
+ mDevContext = DevContext.createForDevOptionsDisabled();
+ mCustomAudienceDao =
+ Room.inMemoryDatabaseBuilder(
+ ApplicationProvider.getApplicationContext(),
+ CustomAudienceDatabase.class)
+ .build()
+ .customAudienceDao();
+
+ mFetchJsUri = mMockWebServerRule.uriForPath(mFetchJavaScriptPath);
+ mDefaultDispatcher =
+ new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest request) {
+ if (mFetchJavaScriptPath.equals(request.getPath())) {
+ return new MockResponse().setBody("js");
+ }
+ return new MockResponse().setResponseCode(404);
+ }
+ };
+ mServer = mMockWebServerRule.startMockWebServer(mDefaultDispatcher);
+ mRequestMatcherExactMatch =
+ (actualRequest, expectedRequest) -> actualRequest.equals(expectedRequest);
+ }
+
+ @Test
+ public void testSuccessfulGetBuyerLogicWithoutOverride() {
+ mDevContext =
+ DevContext.builder()
+ .setDevOptionsEnabled(true)
+ .setCallingAppPackageName(mAppPackageName)
+ .build();
+ CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
+ new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+
+ JsFetcher jsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mWebClient);
+ jsFetcher.getBuyerDecisionLogic(
+ mFetchJsUri,
+ CustomAudienceFixture.VALID_OWNER,
+ CommonFixture.VALID_BUYER_1,
+ CustomAudienceFixture.VALID_NAME);
+
+ mMockWebServerRule.verifyMockServerRequests(
+ mServer, 0, Collections.emptyList(), mRequestMatcherExactMatch);
+ }
+
+ @Test
+ public void testSuccessfulGetBuyerLogicWithOverride() throws Exception {
+ mDevContext =
+ DevContext.builder()
+ .setDevOptionsEnabled(false)
+ .setCallingAppPackageName(mAppPackageName)
+ .build();
+ CustomAudienceDevOverridesHelper customAudienceDevOverridesHelper =
+ new CustomAudienceDevOverridesHelper(mDevContext, mCustomAudienceDao);
+ JsFetcher jsFetcher =
+ new JsFetcher(
+ mBackgroundExecutorService,
+ mLightweightExecutorService,
+ customAudienceDevOverridesHelper,
+ mWebClient);
+
+ FluentFuture<String> buyerDecisionLogicFuture =
+ jsFetcher.getBuyerDecisionLogic(
+ mFetchJsUri,
+ CustomAudienceFixture.VALID_OWNER,
+ CommonFixture.VALID_BUYER_1,
+ CustomAudienceFixture.VALID_NAME);
+ String buyerDecisionLogic = waitForFuture(() -> buyerDecisionLogicFuture);
+
+ assertEquals(buyerDecisionLogic, "js");
+ mMockWebServerRule.verifyMockServerRequests(
+ mServer,
+ 1,
+ Collections.singletonList(mFetchJavaScriptPath),
+ mRequestMatcherExactMatch);
+ }
+
+ private <T> T waitForFuture(JsFetcherTest.ThrowingSupplier<ListenableFuture<T>> function)
+ throws Exception {
+ CountDownLatch resultLatch = new CountDownLatch(1);
+ ListenableFuture<T> futureResult = function.get();
+ futureResult.addListener(resultLatch::countDown, mLightweightExecutorService);
+ resultLatch.await();
+ return futureResult.get();
+ }
+
+ interface ThrowingSupplier<T> {
+ T get() throws Exception;
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionRunnerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/OnDeviceAdSelectionRunnerTest.java
index 25cb6ab9b..6a596c463 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/AdSelectionRunnerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/OnDeviceAdSelectionRunnerTest.java
@@ -16,19 +16,24 @@
package com.android.adservices.service.adselection;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
+
import static com.android.adservices.service.adselection.AdSelectionRunner.AD_SELECTION_THROTTLED;
import static com.android.adservices.service.adselection.AdSelectionRunner.AD_SELECTION_TIMED_OUT;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_AD_SELECTION_FAILURE;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_NO_CA_AVAILABLE;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_NO_VALID_BIDS_FOR_SCORING;
import static com.android.adservices.service.adselection.AdSelectionRunner.ERROR_NO_WINNING_AD_FOUND;
+import static com.android.adservices.service.adselection.AdSelectionRunner.JS_SANDBOX_IS_NOT_AVAILABLE;
import static com.android.adservices.service.common.Throttler.ApiKey.FLEDGE_API_SELECT_ADS;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS;
-import static com.android.adservices.stats.FledgeApiCallStatsMatcher.aCallStatForFledgeApiWithStatus;
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.doAnswer;
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.verifyZeroInteractions;
@@ -36,9 +41,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import android.adservices.adselection.AdBiddingOutcomeFixture;
import android.adservices.adselection.AdSelectionCallback;
@@ -57,12 +61,14 @@ import android.content.Context;
import android.net.Uri;
import android.os.Process;
import android.os.RemoteException;
+import android.webkit.WebView;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.customaudience.DBCustomAudienceFixture;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.adselection.AdSelectionDatabase;
import com.android.adservices.data.adselection.AdSelectionEntryDao;
import com.android.adservices.data.adselection.DBAdSelection;
@@ -70,6 +76,7 @@ import com.android.adservices.data.adselection.DBAdSelectionEntry;
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.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.AdServicesHttpsClient;
@@ -79,10 +86,13 @@ import com.android.adservices.service.common.FledgeAuthorizationFilter;
import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.consent.AdServicesApiConsent;
import com.android.adservices.service.consent.ConsentManager;
+import com.android.adservices.service.devapi.DevContext;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
+import com.android.adservices.service.stats.ApiServiceLatencyCalculator;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.Futures;
@@ -90,6 +100,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.Spy;
@@ -105,16 +116,19 @@ import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* This test covers strictly the unit of {@link AdSelectionRunner} The dependencies in this test are
* mocked and provide expected mock responses when invoked with desired input
*/
-public class AdSelectionRunnerTest {
- private static final String TAG = AdSelectionRunnerTest.class.getName();
+public class OnDeviceAdSelectionRunnerTest {
+ private static final String TAG = OnDeviceAdSelectionRunnerTest.class.getName();
private static final AdTechIdentifier BUYER_1 = AdSelectionConfigFixture.BUYER_1;
private static final AdTechIdentifier BUYER_2 = AdSelectionConfigFixture.BUYER_2;
@@ -129,6 +143,7 @@ public class AdSelectionRunnerTest {
Uri.parse("https://developer.android.com/test/decisions_logic_uris");
private static final Uri TRUSTED_SIGNALS_URI =
Uri.parse("https://developer.android.com/test/trusted_signals_uri");
+ private static final int RUN_AD_SELECTION_OVERALL_LATENCY_MS = 200;
private MockitoSession mStaticMockSession = null;
@Mock private AdsScoreGenerator mMockAdsScoreGenerator;
@@ -138,6 +153,7 @@ public class AdSelectionRunnerTest {
@Spy private Clock mClock = Clock.systemUTC();
@Mock private ConsentManager mConsentManagerMock;
@Mock private Throttler mMockThrottler;
+ @Mock private ApiServiceLatencyCalculator mMockApiServiceLatencyCalculator;
private Flags mFlags =
new Flags() {
@@ -155,14 +171,19 @@ public class AdSelectionRunnerTest {
private AdServicesHttpsClient mAdServicesHttpsClient;
private ExecutorService mLightweightExecutorService;
private ExecutorService mBackgroundExecutorService;
+ private ScheduledThreadPoolExecutor mScheduledExecutor;
private CustomAudienceDao mCustomAudienceDao;
private AdSelectionEntryDao mAdSelectionEntryDao;
private Supplier<Throttler> mThrottlerSupplier = () -> mMockThrottler;
- @Spy private AdServicesLogger mAdServicesLoggerSpy = AdServicesLoggerImpl.getInstance();
+ private AdServicesLogger mAdServicesLoggerMock =
+ ExtendedMockito.mock(AdServicesLoggerImpl.class);
private final FledgeAuthorizationFilter mFledgeAuthorizationFilter =
- FledgeAuthorizationFilter.create(mContext, mAdServicesLoggerSpy);
+ new FledgeAuthorizationFilter(
+ mContext.getPackageManager(),
+ new EnrollmentDao(mContext, DbTestUtil.getDbHelperForTest()),
+ mAdServicesLoggerMock);
private final FledgeAllowListsFilter mFledgeAllowListsFilter =
- new FledgeAllowListsFilter(mFlags, mAdServicesLoggerSpy);
+ new FledgeAllowListsFilter(mFlags, mAdServicesLoggerMock);
private AdSelectionConfig.Builder mAdSelectionConfigBuilder;
@@ -187,6 +208,7 @@ public class AdSelectionRunnerTest {
mStaticMockSession =
ExtendedMockito.mockitoSession()
.spyStatic(FlagsFactory.class)
+ .spyStatic(WebView.class)
.strictness(Strictness.LENIENT)
.initMocks(this)
.startMocking();
@@ -194,6 +216,7 @@ public class AdSelectionRunnerTest {
mContext = ApplicationProvider.getApplicationContext();
mLightweightExecutorService = AdServicesExecutors.getLightWeightExecutor();
mBackgroundExecutorService = AdServicesExecutors.getBackgroundExecutor();
+ mScheduledExecutor = AdServicesExecutors.getScheduler();
mAdServicesHttpsClient =
new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor());
mCustomAudienceDao =
@@ -237,7 +260,7 @@ public class AdSelectionRunnerTest {
private DBCustomAudience createDBCustomAudience(final AdTechIdentifier buyer) {
return DBCustomAudienceFixture.getValidBuilderByBuyer(buyer)
.setOwner(buyer.toString() + CustomAudienceFixture.VALID_OWNER)
- .setName(buyer.toString() + CustomAudienceFixture.VALID_NAME)
+ .setName(buyer + CustomAudienceFixture.VALID_NAME)
.setCreationTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
.setLastAdsAndBiddingDataUpdatedTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
.build();
@@ -247,8 +270,7 @@ public class AdSelectionRunnerTest {
public void testRunAdSelectionSuccess() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
- // Creating ad selection config for happy case with all the buyers in place
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
// Populating the Custom Audience DB
@@ -268,8 +290,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(mAdBiddingOutcomeForBuyer2)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
@@ -278,8 +299,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
// Getting ScoringOutcome-ForBuyerX corresponding to each BiddingOutcome-forBuyerX
when(mMockAdsScoreGenerator.runAdScoring(mAdBiddingOutcomeList, adSelectionConfig))
@@ -313,28 +333,30 @@ public class AdSelectionRunnerTest {
.build();
when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
-
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
AdSelectionTestCallback resultsCallback =
@@ -347,8 +369,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
@@ -356,8 +377,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdsScoreGenerator).runAdScoring(mAdBiddingOutcomeList, adSelectionConfig);
assertTrue(resultsCallback.mIsSuccess);
@@ -372,21 +392,18 @@ public class AdSelectionRunnerTest {
expectedAdSelectionResult,
mAdSelectionEntryDao.getAdSelectionEntityById(AD_SELECTION_ID));
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(AdServicesStatusUtils.STATUS_SUCCESS),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionWithRevokedUserConsentSuccess() throws AdServicesException {
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
@@ -400,34 +417,37 @@ public class AdSelectionRunnerTest {
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
- verify(mMockAdBidGenerator, never()).runAdBiddingPerCA(any(), any(), any(), any(), any());
+ verify(mMockAdBidGenerator, never()).runAdBiddingPerCA(any(), any(), any(), any());
verify(mMockAdsScoreGenerator, never()).runAdScoring(any(), any());
assertTrue(resultsCallback.mIsSuccess);
@@ -437,22 +457,19 @@ public class AdSelectionRunnerTest {
assertEquals(Uri.EMPTY, resultsCallback.mAdSelectionResponse.getRenderUri());
assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionMissingBuyerSignals() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config with missing Buyer signals to test the fallback
AdSelectionConfig adSelectionConfig =
@@ -473,16 +490,14 @@ public class AdSelectionRunnerTest {
mDBCustomAudienceForBuyer1,
adSelectionConfig.getAdSelectionSignals(),
AdSelectionSignals.EMPTY,
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(mAdBiddingOutcomeForBuyer2)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
adSelectionConfig.getAdSelectionSignals(),
AdSelectionSignals.EMPTY,
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
// Getting ScoringOutcome-ForBuyerX corresponding to each BiddingOutcome-forBuyerX
when(mMockAdsScoreGenerator.runAdScoring(mAdBiddingOutcomeList, adSelectionConfig))
@@ -513,27 +528,30 @@ public class AdSelectionRunnerTest {
.build();
when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
@@ -545,15 +563,13 @@ public class AdSelectionRunnerTest {
mDBCustomAudienceForBuyer1,
adSelectionConfig.getAdSelectionSignals(),
AdSelectionSignals.EMPTY,
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
adSelectionConfig.getAdSelectionSignals(),
AdSelectionSignals.EMPTY,
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdsScoreGenerator).runAdScoring(mAdBiddingOutcomeList, adSelectionConfig);
assertTrue(resultsCallback.mIsSuccess);
@@ -564,22 +580,18 @@ public class AdSelectionRunnerTest {
expectedDBAdSelectionResult.getWinningAdRenderUri(),
resultsCallback.mAdSelectionResponse.getRenderUri());
assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
-
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(AdServicesStatusUtils.STATUS_SUCCESS),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionNoCAs() {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
@@ -590,48 +602,47 @@ public class AdSelectionRunnerTest {
verifyZeroInteractions(mMockAdBidGenerator);
// If there was no bidding then we should not even attempt to run scoring
verifyZeroInteractions(mMockAdsScoreGenerator);
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
assertFalse(resultsCallback.mIsSuccess);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(), ERROR_NO_CA_AVAILABLE);
-
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_INTERNAL_ERROR),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionCallerNotInForeground_fails() {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
@@ -643,25 +654,27 @@ public class AdSelectionRunnerTest {
CALLER_UID, AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, null);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
@@ -672,8 +685,8 @@ public class AdSelectionRunnerTest {
@Test
public void testRunAdSelectionCallerNotInForegroundFlagDisabled_doesNotFailValidation() {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
- mFlags =
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+ Flags flags =
new Flags() {
@Override
public boolean getEnforceForegroundStatusForFledgeRunAdSelection() {
@@ -688,27 +701,30 @@ public class AdSelectionRunnerTest {
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
- mFlags,
+ flags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
@@ -725,7 +741,7 @@ public class AdSelectionRunnerTest {
public void testRunAdSelectionPartialBidding() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
@@ -748,8 +764,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(null)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
@@ -758,8 +773,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
// Getting ScoringOutcome-ForBuyerX corresponding to each BiddingOutcome-forBuyerX
// In this case we are only expected to get score for the first bidding,
@@ -771,27 +785,30 @@ public class AdSelectionRunnerTest {
Futures.immediateFuture(mAdScoringOutcomeList.subList(0, 1)))));
when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
@@ -829,8 +846,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
@@ -838,8 +854,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdsScoreGenerator).runAdScoring(partialBiddingOutcome, adSelectionConfig);
assertTrue(resultsCallback.mIsSuccess);
@@ -850,23 +865,19 @@ public class AdSelectionRunnerTest {
expectedDBAdSelectionResult.getWinningAdRenderUri(),
resultsCallback.mAdSelectionResponse.getRenderUri());
assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
-
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(AdServicesStatusUtils.STATUS_SUCCESS),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionBiddingFailure() {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
@@ -889,8 +900,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(null)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
@@ -899,32 +909,34 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
// If the result of bidding is empty, then we should not even attempt to run scoring
verifyZeroInteractions(mMockAdsScoreGenerator);
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
@@ -936,8 +948,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
@@ -945,30 +956,25 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
assertFalse(resultsCallback.mIsSuccess);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(),
ERROR_NO_VALID_BIDS_FOR_SCORING);
-
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_INTERNAL_ERROR),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionScoringFailure() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
@@ -990,8 +996,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(mAdBiddingOutcomeForBuyer2)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
@@ -1000,34 +1005,36 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
// Getting ScoringOutcome-ForBuyerX corresponding to each BiddingOutcome-forBuyerX
// In this case assuming we get an empty result
when(mMockAdsScoreGenerator.runAdScoring(mAdBiddingOutcomeList, adSelectionConfig))
.thenReturn((FluentFuture.from(Futures.immediateFuture(Collections.EMPTY_LIST))));
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
@@ -1039,8 +1046,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
@@ -1048,30 +1054,25 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdsScoreGenerator).runAdScoring(mAdBiddingOutcomeList, adSelectionConfig);
assertFalse(resultsCallback.mIsSuccess);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(), ERROR_NO_WINNING_AD_FOUND);
-
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_INTERNAL_ERROR),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionNegativeScoring() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
@@ -1093,8 +1094,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(mAdBiddingOutcomeForBuyer2)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
@@ -1103,8 +1103,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
AdScoringOutcome adScoringNegativeOutcomeForBuyer1 =
AdScoringOutcomeFixture.anAdScoringBuilder(BUYER_1, -2.0).build();
@@ -1117,27 +1116,30 @@ public class AdSelectionRunnerTest {
// In this case assuming we get a result with negative scores
when(mMockAdsScoreGenerator.runAdScoring(mAdBiddingOutcomeList, adSelectionConfig))
.thenReturn((FluentFuture.from(Futures.immediateFuture(negativeScoreOutcome))));
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
@@ -1149,8 +1151,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
@@ -1158,30 +1159,25 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdsScoreGenerator).runAdScoring(mAdBiddingOutcomeList, adSelectionConfig);
assertFalse(resultsCallback.mIsSuccess);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(), ERROR_NO_WINNING_AD_FOUND);
-
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_INTERNAL_ERROR),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionPartialNegativeScoring() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
@@ -1203,8 +1199,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(mAdBiddingOutcomeForBuyer2)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
@@ -1213,8 +1208,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
AdScoringOutcome adScoringNegativeOutcomeForBuyer1 =
AdScoringOutcomeFixture.anAdScoringBuilder(BUYER_1, 2.0).build();
@@ -1229,27 +1223,30 @@ public class AdSelectionRunnerTest {
.thenReturn((FluentFuture.from(Futures.immediateFuture(negativeScoreOutcome))));
when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
@@ -1263,8 +1260,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
@@ -1272,8 +1268,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdsScoreGenerator).runAdScoring(mAdBiddingOutcomeList, adSelectionConfig);
DBAdSelection expectedDBAdSelectionResult =
@@ -1308,23 +1303,19 @@ public class AdSelectionRunnerTest {
expectedDBAdSelectionResult.getWinningAdRenderUri(),
resultsCallback.mAdSelectionResponse.getRenderUri());
assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
-
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_SUCCESS);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_SUCCESS));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(AdServicesStatusUtils.STATUS_SUCCESS),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionScoringException() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig =
@@ -1349,8 +1340,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(mAdBiddingOutcomeForBuyer2)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
@@ -1359,34 +1349,36 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
// Getting ScoringOutcome-ForBuyerX corresponding to each BiddingOutcome-forBuyerX
// In this case we expect a JSON validation exception
when(mMockAdsScoreGenerator.runAdScoring(mAdBiddingOutcomeList, adSelectionConfig))
.thenThrow(new AdServicesException(ERROR_INVALID_JSON));
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
@@ -1398,8 +1390,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
verify(mMockAdBidGenerator)
.runAdBiddingPerCA(
mDBCustomAudienceForBuyer2,
@@ -1407,29 +1398,24 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
assertFalse(resultsCallback.mIsSuccess);
verifyErrorMessageIsCorrect(
resultsCallback.mFledgeErrorResponse.getErrorMessage(), ERROR_INVALID_JSON);
-
- verify(mAdServicesLoggerSpy)
+ verify(mMockApiServiceLatencyCalculator).getApiServiceOverallLatencyMs();
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS,
- AdServicesStatusUtils.STATUS_INTERNAL_ERROR));
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_INTERNAL_ERROR),
+ eq(RUN_AD_SELECTION_OVERALL_LATENCY_MS));
}
@Test
public void testRunAdSelectionOrchestrationTimesOut() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
Flags flagsWithSmallerLimits =
new Flags() {
@@ -1464,8 +1450,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer1.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
doReturn(FluentFuture.from(Futures.immediateFuture(mAdBiddingOutcomeForBuyer2)))
.when(mMockAdBidGenerator)
.runAdBiddingPerCA(
@@ -1474,8 +1459,7 @@ public class AdSelectionRunnerTest {
adSelectionConfig
.getPerBuyerSignals()
.get(mDBCustomAudienceForBuyer2.getBuyer()),
- AdSelectionSignals.EMPTY,
- adSelectionConfig);
+ AdSelectionSignals.EMPTY);
// Getting ScoringOutcome-ForBuyerX corresponding to each BiddingOutcome-forBuyerX
when(mMockAdsScoreGenerator.runAdScoring(mAdBiddingOutcomeList, adSelectionConfig))
@@ -1489,27 +1473,30 @@ public class AdSelectionRunnerTest {
new AnswersWithDelay(
2 * mFlags.getAdSelectionOverallTimeoutMs(),
new Returns(AD_SELECTION_ID)));
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
flagsWithSmallerLimits,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
@@ -1526,36 +1513,203 @@ public class AdSelectionRunnerTest {
}
@Test
+ public void testRunAdSelectionPerBuyerTimeout() throws AdServicesException {
+ Flags flagsWithSmallPerBuyerTimeout =
+ new Flags() {
+ @Override
+ public long getAdSelectionOverallTimeoutMs() {
+ return 5000;
+ }
+
+ @Override
+ public long getAdSelectionBiddingTimeoutPerBuyerMs() {
+ return 100;
+ }
+
+ @Override
+ public boolean getDisableFledgeEnrollmentCheck() {
+ return true;
+ }
+ };
+ when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
+ doReturn(flagsWithSmallPerBuyerTimeout).when(FlagsFactory::getFlags);
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+ AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
+
+ // Populating the Custom Audience DB
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(
+ mDBCustomAudienceForBuyer1,
+ CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_1));
+ mCustomAudienceDao.insertOrOverwriteCustomAudience(
+ mDBCustomAudienceForBuyer2,
+ CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER_2));
+
+ Callable<AdBiddingOutcome> delayedBiddingOutcomeForBuyer1 =
+ () -> {
+ TimeUnit.MILLISECONDS.sleep(
+ 10
+ * flagsWithSmallPerBuyerTimeout
+ .getAdSelectionBiddingTimeoutPerBuyerMs());
+ return mAdBiddingOutcomeForBuyer1;
+ };
+
+ doReturn(mLightweightExecutorService.submit(delayedBiddingOutcomeForBuyer1))
+ .when(mMockAdBidGenerator)
+ .runAdBiddingPerCA(
+ mDBCustomAudienceForBuyer1,
+ adSelectionConfig.getAdSelectionSignals(),
+ adSelectionConfig
+ .getPerBuyerSignals()
+ .get(mDBCustomAudienceForBuyer1.getBuyer()),
+ AdSelectionSignals.EMPTY);
+ doReturn(FluentFuture.from(Futures.immediateFuture(mAdBiddingOutcomeForBuyer2)))
+ .when(mMockAdBidGenerator)
+ .runAdBiddingPerCA(
+ mDBCustomAudienceForBuyer2,
+ adSelectionConfig.getAdSelectionSignals(),
+ adSelectionConfig
+ .getPerBuyerSignals()
+ .get(mDBCustomAudienceForBuyer2.getBuyer()),
+ AdSelectionSignals.EMPTY);
+
+ // bidding Outcome List should only have one bidding outcome
+ List<AdBiddingOutcome> adBiddingOutcomeList = ImmutableList.of(mAdBiddingOutcomeForBuyer2);
+
+ // Getting ScoringOutcome-ForBuyerX corresponding to each BiddingOutcome-forBuyerX
+ when(mMockAdsScoreGenerator.runAdScoring(adBiddingOutcomeList, adSelectionConfig))
+ .thenReturn(
+ (FluentFuture.from(
+ Futures.immediateFuture(
+ ImmutableList.of(mAdScoringOutcomeForBuyer2)))));
+
+ Instant adSelectionCreationTs = Clock.systemUTC().instant().truncatedTo(ChronoUnit.MILLIS);
+ when(mClock.instant()).thenReturn(adSelectionCreationTs);
+
+ DBAdSelectionEntry expectedAdSelectionResult =
+ new DBAdSelectionEntry.Builder()
+ .setAdSelectionId(AD_SELECTION_ID)
+ .setWinningAdBid(
+ mAdScoringOutcomeForBuyer2.getAdWithScore().getAdWithBid().getBid())
+ .setCustomAudienceSignals(
+ mAdScoringOutcomeForBuyer2
+ .getCustomAudienceBiddingInfo()
+ .getCustomAudienceSignals())
+ .setWinningAdRenderUri(
+ mAdScoringOutcomeForBuyer2
+ .getAdWithScore()
+ .getAdWithBid()
+ .getAdData()
+ .getRenderUri())
+ .setBuyerDecisionLogicJs(
+ mAdBiddingOutcomeForBuyer1
+ .getCustomAudienceBiddingInfo()
+ .getBuyerDecisionLogicJs())
+ // TODO(b/230569187) add contextual signals once supported in the main logic
+ .setContextualSignals("{}")
+ .setCreationTimestamp(adSelectionCreationTs)
+ .build();
+
+ when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
+
+ mAdSelectionRunner =
+ new OnDeviceAdSelectionRunner(
+ mContext,
+ mCustomAudienceDao,
+ mAdSelectionEntryDao,
+ mAdServicesHttpsClient,
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ mConsentManagerMock,
+ mMockAdsScoreGenerator,
+ mMockAdBidGenerator,
+ mMockAdSelectionIdGenerator,
+ mClock,
+ mAdServicesLoggerMock,
+ mAppImportanceFilter,
+ flagsWithSmallPerBuyerTimeout,
+ mThrottlerSupplier,
+ CALLER_UID,
+ mFledgeAuthorizationFilter,
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
+
+ assertFalse(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
+
+ AdSelectionTestCallback resultsCallback =
+ invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
+
+ verify(mMockAdBidGenerator)
+ .runAdBiddingPerCA(
+ mDBCustomAudienceForBuyer1,
+ adSelectionConfig.getAdSelectionSignals(),
+ adSelectionConfig
+ .getPerBuyerSignals()
+ .get(mDBCustomAudienceForBuyer1.getBuyer()),
+ AdSelectionSignals.EMPTY);
+ verify(mMockAdBidGenerator)
+ .runAdBiddingPerCA(
+ mDBCustomAudienceForBuyer2,
+ adSelectionConfig.getAdSelectionSignals(),
+ adSelectionConfig
+ .getPerBuyerSignals()
+ .get(mDBCustomAudienceForBuyer2.getBuyer()),
+ AdSelectionSignals.EMPTY);
+ verify(mMockAdsScoreGenerator).runAdScoring(adBiddingOutcomeList, adSelectionConfig);
+
+ assertTrue(resultsCallback.mIsSuccess);
+ assertEquals(
+ expectedAdSelectionResult.getAdSelectionId(),
+ resultsCallback.mAdSelectionResponse.getAdSelectionId());
+ assertEquals(
+ expectedAdSelectionResult.getWinningAdRenderUri(),
+ resultsCallback.mAdSelectionResponse.getRenderUri());
+ assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(AD_SELECTION_ID));
+ assertEquals(
+ expectedAdSelectionResult,
+ mAdSelectionEntryDao.getAdSelectionEntityById(AD_SELECTION_ID));
+
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(AdServicesStatusUtils.STATUS_SUCCESS),
+ anyInt());
+ }
+
+ @Test
public void testRunAdSelectionThrottledFailure() throws AdServicesException {
when(mClock.instant()).thenReturn(Clock.systemUTC().instant());
doReturn(mFlags).when(FlagsFactory::getFlags);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Creating ad selection config for happy case with all the buyers in place
AdSelectionConfig adSelectionConfig = mAdSelectionConfigBuilder.build();
// Throttle Ad Selection request
when(mMockThrottler.tryAcquire(eq(FLEDGE_API_SELECT_ADS), anyString())).thenReturn(false);
-
+ when(mMockApiServiceLatencyCalculator.getApiServiceOverallLatencyMs())
+ .thenReturn(RUN_AD_SELECTION_OVERALL_LATENCY_MS);
mAdSelectionRunner =
- new AdSelectionRunner(
+ new OnDeviceAdSelectionRunner(
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
mAdServicesHttpsClient,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
mConsentManagerMock,
mMockAdsScoreGenerator,
mMockAdBidGenerator,
mMockAdSelectionIdGenerator,
mClock,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlags,
mThrottlerSupplier,
CALLER_UID,
mFledgeAuthorizationFilter,
- mFledgeAllowListsFilter);
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
AdSelectionTestCallback resultsCallback =
invokeRunAdSelection(mAdSelectionRunner, adSelectionConfig, MY_APP_PACKAGE_NAME);
@@ -1569,6 +1723,36 @@ public class AdSelectionRunnerTest {
response.getStatusCode());
}
+ @Test
+ public void testAdSelectionRunnerInstanceNotCreatedIfJSSandboxNotInWebView() {
+ doReturn(null).when(WebView::getCurrentWebViewPackage);
+
+ ThrowingRunnable initializeAdSelectionRunner =
+ () ->
+ new OnDeviceAdSelectionRunner(
+ mContext,
+ mCustomAudienceDao,
+ mAdSelectionEntryDao,
+ mAdServicesHttpsClient,
+ mLightweightExecutorService,
+ mBackgroundExecutorService,
+ mScheduledExecutor,
+ mConsentManagerMock,
+ mAdServicesLoggerMock,
+ DevContext.createForDevOptionsDisabled(),
+ mAppImportanceFilter,
+ mFlags,
+ mThrottlerSupplier,
+ CALLER_UID,
+ mFledgeAuthorizationFilter,
+ mFledgeAllowListsFilter,
+ mMockApiServiceLatencyCalculator);
+
+ Throwable throwable =
+ assertThrows(IllegalArgumentException.class, initializeAdSelectionRunner);
+ verifyErrorMessageIsCorrect(throwable.getMessage(), JS_SANDBOX_IS_NOT_AVAILABLE);
+ }
+
private void verifyErrorMessageIsCorrect(
final String actualErrorMassage, final String expectedErrorReason) {
Assert.assertTrue(
@@ -1599,7 +1783,9 @@ public class AdSelectionRunnerTest {
countDownLatch.countDown();
return null;
};
- doAnswer(countDownAnswer).when(mAdServicesLoggerSpy).logApiCallStats(any());
+ doAnswer(countDownAnswer)
+ .when(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(anyInt(), anyInt(), anyInt());
AdSelectionInput input =
new AdSelectionInput.Builder()
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/PerBuyerBiddingRunnerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/PerBuyerBiddingRunnerTest.java
new file mode 100644
index 000000000..4d7ecc316
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/PerBuyerBiddingRunnerTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.adselection;
+
+import android.adservices.adselection.AdSelectionConfig;
+import android.adservices.adselection.AdSelectionConfigFixture;
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+import android.adservices.customaudience.TrustedBiddingDataFixture;
+import android.net.Uri;
+
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.data.common.DBAdData;
+import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.data.customaudience.DBTrustedBiddingData;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+public class PerBuyerBiddingRunnerTest {
+
+ private static final String TEST_BUYER = "buyer";
+ private static final String AD_URI_PREFIX = "http://www.domain.com/adverts/123/";
+ private static final String FAST_SUFFIX = "FAST";
+ private static final String SLOW_SUFFIX = "SLOW";
+ private static final long SHORT_SLEEP_MS = 10L;
+ private static final long LONG_SLEEP_MS = 10000L;
+ private static final long PER_BUYER_TIMEOUT_MS = 1000L;
+ private static final AdSelectionConfig AD_SELECTION_CONFIG =
+ AdSelectionConfigFixture.anAdSelectionConfigBuilder().build();
+
+ AdTechIdentifier mBuyer;
+ List<DBCustomAudience> mDBCustomAudienceList;
+
+ @Mock AdBidGenerator mAdBidGeneratorMock;
+ @Mock AdBiddingOutcome mAdBiddingOutcome;
+
+ private PerBuyerBiddingRunner mPerBuyerBiddingRunner;
+ private ScheduledThreadPoolExecutor mScheduledExecutor = AdServicesExecutors.getScheduler();
+ private ListeningExecutorService mBackgroundExecutorService =
+ AdServicesExecutors.getBackgroundExecutor();
+ private ListeningExecutorService mExecutor =
+ MoreExecutors.listeningDecorator((AdServicesExecutors.getLightWeightExecutor()));
+
+ private ResponseMatcher mSlowResponseMatcher = new ResponseMatcher(SLOW_SUFFIX);
+ private ResponseMatcher mFastResponseMatcher = new ResponseMatcher(FAST_SUFFIX);
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mBuyer = AdTechIdentifier.fromString(TEST_BUYER);
+ mDBCustomAudienceList = new ArrayList<>();
+ mPerBuyerBiddingRunner =
+ new PerBuyerBiddingRunner(
+ mAdBidGeneratorMock, mScheduledExecutor, mBackgroundExecutorService);
+
+ ExtendedMockito.doReturn(createDelayedBiddingOutcome(SHORT_SLEEP_MS))
+ .when(mAdBidGeneratorMock)
+ .runAdBiddingPerCA(
+ ExtendedMockito.argThat(mFastResponseMatcher),
+ ExtendedMockito.any(AdSelectionSignals.class),
+ ExtendedMockito.any(AdSelectionSignals.class),
+ ExtendedMockito.any(AdSelectionSignals.class));
+
+ ExtendedMockito.doReturn(createDelayedBiddingOutcome(LONG_SLEEP_MS))
+ .when(mAdBidGeneratorMock)
+ .runAdBiddingPerCA(
+ ExtendedMockito.argThat(mSlowResponseMatcher),
+ ExtendedMockito.any(AdSelectionSignals.class),
+ ExtendedMockito.any(AdSelectionSignals.class),
+ ExtendedMockito.any(AdSelectionSignals.class));
+ }
+
+ @Test
+ public void testPerBuyerBidding_AllCASucceed() throws InterruptedException {
+ int numSlowCustomAudiences = 0;
+ int numFastCustomAudiences = 20;
+ runAndValidatePerBuyerBidding(numSlowCustomAudiences, numFastCustomAudiences);
+ }
+
+ @Test
+ public void testPerBuyerBidding_PartialCATimeout() throws InterruptedException {
+ int numSlowCustomAudiences = 10;
+ int numFastCustomAudiences = 10;
+ runAndValidatePerBuyerBidding(numSlowCustomAudiences, numFastCustomAudiences);
+ }
+
+ @Test
+ public void testPerBuyerBidding_AllCATimeout() throws InterruptedException {
+ int numSlowCustomAudiences = 20;
+ int numFastCustomAudiences = 0;
+ runAndValidatePerBuyerBidding(numSlowCustomAudiences, numFastCustomAudiences);
+ }
+
+ private void runAndValidatePerBuyerBidding(
+ int numSlowCustomAudiences, int numFastCustomAudiences) throws InterruptedException {
+ List<DBCustomAudience> slowCustomAudiences =
+ createCustomAudienceList(numSlowCustomAudiences, SLOW_SUFFIX);
+ List<DBCustomAudience> fastCustomAudiences =
+ createCustomAudienceList(numFastCustomAudiences, FAST_SUFFIX);
+
+ List<DBCustomAudience> customAudienceList = new ArrayList<>();
+ customAudienceList.addAll(fastCustomAudiences);
+ customAudienceList.addAll(slowCustomAudiences);
+
+ List<ListenableFuture<AdBiddingOutcome>> biddingOutcomes =
+ mPerBuyerBiddingRunner.runBidding(
+ mBuyer, customAudienceList, PER_BUYER_TIMEOUT_MS, AD_SELECTION_CONFIG);
+
+ Thread.sleep(2 * PER_BUYER_TIMEOUT_MS);
+
+ int completedBids = 0;
+ int cancelledIncompleteBids = 0;
+
+ for (ListenableFuture<AdBiddingOutcome> bidOutcome : biddingOutcomes) {
+ if (bidOutcome.isCancelled()) {
+ cancelledIncompleteBids++;
+ } else {
+ completedBids++;
+ }
+ }
+
+ Assert.assertEquals(
+ "Number of timed out bids, does not match cancelled bids",
+ numSlowCustomAudiences,
+ cancelledIncompleteBids);
+ Assert.assertEquals(
+ "Number of completed bids, does not match successful bids",
+ numFastCustomAudiences,
+ completedBids);
+ }
+
+ private ListenableFuture<AdBiddingOutcome> createDelayedBiddingOutcome(long delayTime) {
+ return mExecutor.submit(
+ () -> {
+ Thread.sleep(delayTime);
+ return mAdBiddingOutcome;
+ });
+ }
+
+ private List<DBCustomAudience> createCustomAudienceList(int count, String suffix) {
+ List<DBCustomAudience> customAudienceList = new ArrayList<>();
+ for (int i = 1; i <= count; i++) {
+ customAudienceList.add(createDBCustomAudience(mBuyer, String.valueOf(i), suffix));
+ }
+ return customAudienceList;
+ }
+
+ /**
+ * @return a real Custom Audience object that can be persisted and used in bidding and scoring
+ */
+ private DBCustomAudience createDBCustomAudience(
+ final AdTechIdentifier buyer, final String namePrefix, final String nameSuffix) {
+
+ // Generate ads for with bids provided
+ List<DBAdData> ads = new ArrayList<>();
+ List<Double> bids = ImmutableList.of(1.0, 2.0);
+ // Create ads with the buyer name and bid number as the ad URI
+ // Add the bid value to the metadata
+ for (int i = 0; i < bids.size(); i++) {
+ ads.add(
+ new DBAdData(
+ Uri.parse(AD_URI_PREFIX + buyer + "/ad" + (i + 1)),
+ "{\"result\":" + bids.get(i) + "}"));
+ }
+
+ return new DBCustomAudience.Builder()
+ .setOwner(buyer + CustomAudienceFixture.VALID_OWNER)
+ .setBuyer(buyer)
+ .setName(
+ buyer.toString()
+ + namePrefix
+ + CustomAudienceFixture.VALID_NAME
+ + nameSuffix)
+ .setActivationTime(CustomAudienceFixture.VALID_ACTIVATION_TIME)
+ .setExpirationTime(CustomAudienceFixture.VALID_EXPIRATION_TIME)
+ .setCreationTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
+ .setLastAdsAndBiddingDataUpdatedTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
+ .setUserBiddingSignals(CustomAudienceFixture.VALID_USER_BIDDING_SIGNALS)
+ .setTrustedBiddingData(
+ new DBTrustedBiddingData.Builder()
+ .setUri(Uri.parse("https://www.example.com/trusted"))
+ .setKeys(TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
+ .build())
+ .setBiddingLogicUri(Uri.parse("https://www.example.com/logic"))
+ .setAds(ads)
+ .build();
+ }
+
+ private static class ResponseMatcher implements ArgumentMatcher<DBCustomAudience> {
+ private String mSuffix;
+
+ ResponseMatcher(String suffix) {
+ this.mSuffix = suffix;
+ }
+
+ @Override
+ public boolean matches(DBCustomAudience right) {
+ return right.getName().endsWith(mSuffix);
+ }
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java
new file mode 100644
index 000000000..4223ac1cf
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/adselection/TrustedServerAdSelectionRunnerTest.java
@@ -0,0 +1,494 @@
+/*
+ * 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.adselection;
+
+import static com.android.adservices.service.adselection.TrustedServerAdSelectionRunner.GZIP;
+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.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+
+import android.adservices.adselection.AdSelectionConfig;
+import android.adservices.adselection.AdSelectionConfigFixture;
+import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Process;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.customaudience.DBCustomAudienceFixture;
+import com.android.adservices.data.adselection.AdSelectionEntryDao;
+import com.android.adservices.data.adselection.DBAdSelection;
+import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.adselection.AdSelectionRunner.AdSelectionOrchestrationResult;
+import com.android.adservices.service.common.AppImportanceFilter;
+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.consent.ConsentManager;
+import com.android.adservices.service.proto.SellerFrontEndGrpc;
+import com.android.adservices.service.proto.SellerFrontEndGrpc.SellerFrontEndFutureStub;
+import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdRequest;
+import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdRequest.SelectWinningAdRawRequest;
+import com.android.adservices.service.proto.SellerFrontendService.SelectWinningAdResponse;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.ApiServiceLatencyCalculator;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import io.grpc.ManagedChannel;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+
+public class TrustedServerAdSelectionRunnerTest {
+ private static final AdTechIdentifier BUYER_1 = AdSelectionConfigFixture.BUYER_1;
+ private static final AdTechIdentifier BUYER_2 = AdSelectionConfigFixture.BUYER_2;
+ private static final Long AD_SELECTION_ID = 1234L;
+ private static final String MY_APP_PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME;
+ private static final AdTechIdentifier SELLER_VALID =
+ AdTechIdentifier.fromString("developer.android.com");
+ private static final Uri DECISION_LOGIC_URI =
+ Uri.parse("https://developer.android.com/test/decisions_logic_uris");
+ private static final Uri TRUSTED_SIGNALS_URI =
+ Uri.parse("https://developer.android.com/test/trusted_signals_uri");
+ private static final int CALLER_UID = Process.myUid();
+ private static final ListeningExecutorService sLightweightExecutorService =
+ AdServicesExecutors.getLightWeightExecutor();
+ private static final ListeningExecutorService sBackgroundExecutorService =
+ AdServicesExecutors.getBackgroundExecutor();
+ private static final ScheduledThreadPoolExecutor sScheduledExecutor =
+ AdServicesExecutors.getScheduler();
+
+ private static final AdSelectionConfig.Builder sAdSelectionConfigBuilder =
+ AdSelectionConfigFixture.anAdSelectionConfigBuilder()
+ .setSeller(SELLER_VALID)
+ .setCustomAudienceBuyers(Arrays.asList(BUYER_1, BUYER_2))
+ .setDecisionLogicUri(DECISION_LOGIC_URI)
+ .setTrustedScoringSignalsUri(TRUSTED_SIGNALS_URI);
+ private static final DBCustomAudience sDBCustomAudience = createDBCustomAudience(BUYER_1);
+
+ private static final AdScoringOutcome sAdScoringOutcome =
+ AdScoringOutcomeFixture.anAdScoringBuilder(BUYER_1, 2.0).build();
+ private static final SelectWinningAdResponse sSelectWinningAdResponse =
+ SelectWinningAdResponse.newBuilder()
+ .setRawResponse(
+ SelectWinningAdResponse.SelectWinningAdRawResponse.newBuilder()
+ .setAdRenderUrl("valid.example.com/testing/hello/test.com")
+ .setScore(1)
+ .setCustomAudienceName(CustomAudienceFixture.VALID_NAME)
+ .setBidPrice(1))
+ .build();
+
+ private MockitoSession mStaticMockSession = null;
+ private Context mContext = ApplicationProvider.getApplicationContext();
+ private Flags mFlags =
+ new Flags() {
+ @Override
+ public long getAdSelectionOverallTimeoutMs() {
+ return 300;
+ }
+ };
+ private TrustedServerAdSelectionRunner mAdSelectionRunner;
+
+ @Mock private AppImportanceFilter mAppImportanceFilter;
+ @Mock private Clock mClock;
+ @Mock private ConsentManager mConsentManagerMock;
+ @Mock private Supplier<Throttler> mThrottlerSupplier;
+ @Mock private AdServicesLogger mAdServicesLoggerSpy;
+ @Mock private FledgeAuthorizationFilter mFledgeAuthorizationFilter;
+ @Mock private FledgeAllowListsFilter mFledgeAllowListsFilter;
+ @Mock private CustomAudienceDao mCustomAudienceDao;
+ @Mock private AdSelectionEntryDao mAdSelectionEntryDao;
+ @Mock private JsFetcher mJsFetcher;
+ @Mock private AdSelectionIdGenerator mMockAdSelectionIdGenerator;
+ @Mock private OkHttpChannelBuilder mChannelBuilder;
+ @Mock private ManagedChannel mManagedChannel;
+ @Mock private SellerFrontEndFutureStub mStub;
+ private SellerFrontEndFutureStub mStubWithCompression =
+ Mockito.mock(SellerFrontEndFutureStub.class, "mStubWithCompression");
+ @Mock private ApiServiceLatencyCalculator mApiServiceLatencyCalculator;
+
+ @Before
+ public void setUp() {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .mockStatic(OkHttpChannelBuilder.class)
+ .mockStatic(SellerFrontEndGrpc.class)
+ .initMocks(this)
+ .startMocking();
+ }
+
+ private static DBCustomAudience createDBCustomAudience(final AdTechIdentifier buyer) {
+ return DBCustomAudienceFixture.getValidBuilderByBuyer(buyer)
+ .setOwner(buyer.toString() + CustomAudienceFixture.VALID_OWNER)
+ .setName(CustomAudienceFixture.VALID_NAME)
+ .setCreationTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
+ .setLastAdsAndBiddingDataUpdatedTime(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)
+ .build();
+ }
+
+ @Test
+ public void testRunAdSelectionSuccess() {
+ doReturn(mChannelBuilder)
+ .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt()));
+ doReturn(mManagedChannel).when(mChannelBuilder).build();
+ doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel));
+ doReturn(mStubWithCompression).when(mStub).withCompression(GZIP);
+ doReturn(Futures.immediateFuture(sSelectWinningAdResponse))
+ .when(mStubWithCompression)
+ .selectWinningAd(any(SelectWinningAdRequest.class));
+
+ AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build();
+ when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
+
+ FluentFuture js = FluentFuture.from(Futures.immediateFuture("js"));
+ when(mJsFetcher.getBuyerDecisionLogic(any(), any(), any(), any())).thenReturn(js);
+
+ mAdSelectionRunner =
+ new TrustedServerAdSelectionRunner(
+ mContext,
+ mCustomAudienceDao,
+ mAdSelectionEntryDao,
+ sLightweightExecutorService,
+ sBackgroundExecutorService,
+ sScheduledExecutor,
+ mConsentManagerMock,
+ mMockAdSelectionIdGenerator,
+ mClock,
+ mAdServicesLoggerSpy,
+ mAppImportanceFilter,
+ mFlags,
+ mThrottlerSupplier,
+ CALLER_UID,
+ mFledgeAuthorizationFilter,
+ mFledgeAllowListsFilter,
+ mJsFetcher,
+ mApiServiceLatencyCalculator);
+ AdSelectionOrchestrationResult adSelectionOrchestrationResult =
+ invokeRunAdSelection(
+ mAdSelectionRunner,
+ adSelectionConfig,
+ MY_APP_PACKAGE_NAME,
+ ImmutableList.of(sDBCustomAudience));
+
+ Uri expectedWinningAdRenderUri =
+ sAdScoringOutcome.getAdWithScore().getAdWithBid().getAdData().getRenderUri();
+ double expectedWinningAdBid = sAdScoringOutcome.getAdWithScore().getAdWithBid().getBid();
+
+ // Set adSelectionId/timestamp to anything to be able to build the object; an error is
+ // thrown if the fields aren't set.
+ adSelectionOrchestrationResult.mDbAdSelectionBuilder.setAdSelectionId(AD_SELECTION_ID);
+ adSelectionOrchestrationResult.mDbAdSelectionBuilder.setCreationTimestamp(Instant.now());
+ DBAdSelection dbAdSelection = adSelectionOrchestrationResult.mDbAdSelectionBuilder.build();
+
+ assertEquals(expectedWinningAdRenderUri, dbAdSelection.getWinningAdRenderUri());
+ assertEquals(expectedWinningAdBid, dbAdSelection.getWinningAdBid(), 0);
+
+ SellerFrontEndFutureStub unused = verify(mStub).withCompression(GZIP);
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ @Test
+ public void verifyNoCANameInBiddingSignalKeys() {
+ doReturn(mChannelBuilder)
+ .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt()));
+ doReturn(mManagedChannel).when(mChannelBuilder).build();
+ doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel));
+ doReturn(mStubWithCompression).when(mStub).withCompression(GZIP);
+ doReturn(Futures.immediateFuture(sSelectWinningAdResponse))
+ .when(mStubWithCompression)
+ .selectWinningAd(any(SelectWinningAdRequest.class));
+
+ AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build();
+ when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
+
+ FluentFuture js = FluentFuture.from(Futures.immediateFuture("js"));
+ when(mJsFetcher.getBuyerDecisionLogic(any(), any(), any(), any())).thenReturn(js);
+
+ mAdSelectionRunner =
+ new TrustedServerAdSelectionRunner(
+ mContext,
+ mCustomAudienceDao,
+ mAdSelectionEntryDao,
+ sLightweightExecutorService,
+ sBackgroundExecutorService,
+ sScheduledExecutor,
+ mConsentManagerMock,
+ mMockAdSelectionIdGenerator,
+ mClock,
+ mAdServicesLoggerSpy,
+ mAppImportanceFilter,
+ mFlags,
+ mThrottlerSupplier,
+ CALLER_UID,
+ mFledgeAuthorizationFilter,
+ mFledgeAllowListsFilter,
+ mJsFetcher,
+ mApiServiceLatencyCalculator);
+
+ // Add CA name to bidding data keys and later verify we don't send it to the server.
+ DBCustomAudience customAudience = createDBCustomAudience(BUYER_1);
+ customAudience.getTrustedBiddingData().getKeys().add(customAudience.getName());
+
+ ArgumentCaptor<SelectWinningAdRequest> captor =
+ ArgumentCaptor.forClass(SelectWinningAdRequest.class);
+
+ invokeRunAdSelection(
+ mAdSelectionRunner,
+ adSelectionConfig,
+ MY_APP_PACKAGE_NAME,
+ ImmutableList.of(customAudience));
+
+ // Verify the bidding signal keys list does *not* contain the CA name.
+ verify(mStubWithCompression).selectWinningAd(captor.capture());
+ SelectWinningAdRawRequest req = captor.getValue().getRawRequest();
+ List<String> biddingSignalKeys =
+ req.getRawBuyerInputMap()
+ .get(customAudience.getBuyer().toString())
+ .getCustomAudiences(0)
+ .getBiddingSignalsKeysList();
+ assertThat(biddingSignalKeys).doesNotContain(customAudience.getName());
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ @Test
+ public void verifyEmptyBiddingSignalKeys() {
+ doReturn(mChannelBuilder)
+ .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt()));
+ doReturn(mManagedChannel).when(mChannelBuilder).build();
+ doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel));
+ doReturn(mStubWithCompression).when(mStub).withCompression(GZIP);
+ doReturn(Futures.immediateFuture(sSelectWinningAdResponse))
+ .when(mStubWithCompression)
+ .selectWinningAd(any(SelectWinningAdRequest.class));
+
+ AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build();
+ when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
+
+ FluentFuture js = FluentFuture.from(Futures.immediateFuture("js"));
+ when(mJsFetcher.getBuyerDecisionLogic(any(), any(), any(), any())).thenReturn(js);
+
+ mAdSelectionRunner =
+ new TrustedServerAdSelectionRunner(
+ mContext,
+ mCustomAudienceDao,
+ mAdSelectionEntryDao,
+ sLightweightExecutorService,
+ sBackgroundExecutorService,
+ sScheduledExecutor,
+ mConsentManagerMock,
+ mMockAdSelectionIdGenerator,
+ mClock,
+ mAdServicesLoggerSpy,
+ mAppImportanceFilter,
+ mFlags,
+ mThrottlerSupplier,
+ CALLER_UID,
+ mFledgeAuthorizationFilter,
+ mFledgeAllowListsFilter,
+ mJsFetcher,
+ mApiServiceLatencyCalculator);
+
+ // Add CA name to bidding data keys and later verify we don't send it to the server.
+ DBCustomAudience customAudience = createDBCustomAudience(BUYER_1);
+ customAudience.getTrustedBiddingData().getKeys().clear();
+ customAudience.getTrustedBiddingData().getKeys().add(customAudience.getName());
+
+ ArgumentCaptor<SelectWinningAdRequest> captor =
+ ArgumentCaptor.forClass(SelectWinningAdRequest.class);
+
+ invokeRunAdSelection(
+ mAdSelectionRunner,
+ adSelectionConfig,
+ MY_APP_PACKAGE_NAME,
+ ImmutableList.of(customAudience));
+
+ // Verify the bidding signal keys list does *not* contain the CA name.
+ verify(mStubWithCompression).selectWinningAd(captor.capture());
+ SelectWinningAdRawRequest req = captor.getValue().getRawRequest();
+ List<String> biddingSignalKeys =
+ req.getRawBuyerInputMap()
+ .get(customAudience.getBuyer().toString())
+ .getCustomAudiences(0)
+ .getBiddingSignalsKeysList();
+ assertThat(biddingSignalKeys).isEmpty();
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void verifyRuntimeExceptionOnBuyerJsFetchFail() {
+ doReturn(mChannelBuilder)
+ .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt()));
+ doReturn(mManagedChannel).when(mChannelBuilder).build();
+ doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel));
+ doReturn(mStubWithCompression).when(mStub).withCompression(GZIP);
+ doReturn(Futures.immediateFuture(sSelectWinningAdResponse))
+ .when(mStubWithCompression)
+ .selectWinningAd(any(SelectWinningAdRequest.class));
+
+ AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build();
+ when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
+
+ doThrow(new IllegalStateException())
+ .when(mJsFetcher)
+ .getBuyerDecisionLogic(
+ sDBCustomAudience.getBiddingLogicUri(),
+ sDBCustomAudience.getOwner(),
+ sDBCustomAudience.getBuyer(),
+ sDBCustomAudience.getName());
+
+ mAdSelectionRunner =
+ new TrustedServerAdSelectionRunner(
+ mContext,
+ mCustomAudienceDao,
+ mAdSelectionEntryDao,
+ sLightweightExecutorService,
+ sBackgroundExecutorService,
+ sScheduledExecutor,
+ mConsentManagerMock,
+ mMockAdSelectionIdGenerator,
+ mClock,
+ mAdServicesLoggerSpy,
+ mAppImportanceFilter,
+ mFlags,
+ mThrottlerSupplier,
+ CALLER_UID,
+ mFledgeAuthorizationFilter,
+ mFledgeAllowListsFilter,
+ mJsFetcher,
+ mApiServiceLatencyCalculator);
+
+ invokeRunAdSelection(
+ mAdSelectionRunner,
+ adSelectionConfig,
+ MY_APP_PACKAGE_NAME,
+ ImmutableList.of(sDBCustomAudience));
+ }
+
+ @Test
+ public void verifyNoRequestCompressionWhenFlagDisabled() {
+ Flags flags =
+ new Flags() {
+ @Override
+ public boolean getAdSelectionOffDeviceRequestCompressionEnabled() {
+ return false;
+ }
+ };
+
+ doReturn(mChannelBuilder)
+ .when(() -> OkHttpChannelBuilder.forAddress(anyString(), anyInt()));
+ doReturn(mManagedChannel).when(mChannelBuilder).build();
+ doReturn(mStub).when(() -> SellerFrontEndGrpc.newFutureStub(mManagedChannel));
+ doReturn(Futures.immediateFuture(sSelectWinningAdResponse))
+ .when(mStub)
+ .selectWinningAd(any(SelectWinningAdRequest.class));
+
+ AdSelectionConfig adSelectionConfig = sAdSelectionConfigBuilder.build();
+ when(mMockAdSelectionIdGenerator.generateId()).thenReturn(AD_SELECTION_ID);
+
+ FluentFuture js = FluentFuture.from(Futures.immediateFuture("js"));
+ when(mJsFetcher.getBuyerDecisionLogic(any(), any(), any(), any())).thenReturn(js);
+
+ mAdSelectionRunner =
+ new TrustedServerAdSelectionRunner(
+ mContext,
+ mCustomAudienceDao,
+ mAdSelectionEntryDao,
+ sLightweightExecutorService,
+ sBackgroundExecutorService,
+ sScheduledExecutor,
+ mConsentManagerMock,
+ mMockAdSelectionIdGenerator,
+ mClock,
+ mAdServicesLoggerSpy,
+ mAppImportanceFilter,
+ flags,
+ mThrottlerSupplier,
+ CALLER_UID,
+ mFledgeAuthorizationFilter,
+ mFledgeAllowListsFilter,
+ mJsFetcher,
+ mApiServiceLatencyCalculator);
+ invokeRunAdSelection(
+ mAdSelectionRunner,
+ adSelectionConfig,
+ MY_APP_PACKAGE_NAME,
+ ImmutableList.of(sDBCustomAudience));
+
+ SellerFrontEndFutureStub unused = verify(mStub, times(0)).withCompression(GZIP);
+ }
+
+ private AdSelectionOrchestrationResult invokeRunAdSelection(
+ TrustedServerAdSelectionRunner adSelectionRunner,
+ AdSelectionConfig adSelectionConfig,
+ String callerPackageName,
+ List<DBCustomAudience> buyerCustomAudience) {
+
+ try {
+ ListenableFuture<AdSelectionOrchestrationResult> dbAdSelection =
+ adSelectionRunner.orchestrateAdSelection(
+ adSelectionConfig,
+ callerPackageName,
+ Futures.immediateFuture(buyerCustomAudience));
+ return dbAdSelection.get(1000, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (mStaticMockSession != null) {
+ mStaticMockSession.finishMocking();
+ }
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdServiceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdServiceImplTest.java
index 5c58c9be2..37c0a5100 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdServiceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdServiceImplTest.java
@@ -17,11 +17,14 @@
package com.android.adservices.service.appsetid;
import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_APPSETID;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -44,6 +47,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException;
+import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.stats.Clock;
@@ -89,6 +93,7 @@ public class AppSetIdServiceImplTest {
@Mock private Clock mClock;
@Mock private Context mMockSdkContext;
@Mock private Context mMockAppContext;
+ @Mock private Throttler mMockThrottler;
@Mock private AppSetIdServiceImpl mAppSetIdServiceImpl;
@Mock private AppImportanceFilter mMockAppImportanceFilter;
@@ -96,7 +101,11 @@ public class AppSetIdServiceImplTest {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mAppSetIdWorker = new AppSetIdWorker(mContext, mMockFlags);
+ mAppSetIdWorker =
+ Mockito.spy(
+ AppSetIdWorker.getInstance(ApplicationProvider.getApplicationContext()));
+ Mockito.doReturn(null).when(mAppSetIdWorker).getService();
+
when(mClock.elapsedRealtime()).thenReturn(150L, 200L);
mCallerMetadata = new CallerMetadata.Builder().setBinderElapsedTimestamp(100L).build();
mRequest =
@@ -111,6 +120,11 @@ public class AppSetIdServiceImplTest {
// Put this test app into bypass list to bypass Allow-list check.
when(mMockFlags.getPpapiAppAllowList()).thenReturn(APPSETID_API_ALLOW_LIST);
+ // Rate Limit is not reached.
+ when(mMockThrottler.tryAcquire(
+ eq(Throttler.ApiKey.APPSETID_API_APP_PACKAGE_NAME), anyString()))
+ .thenReturn(true);
+
// Initialize mock static.
mStaticMockitoSession =
ExtendedMockito.mockitoSession().mockStatic(Binder.class).startMocking();
@@ -130,6 +144,22 @@ public class AppSetIdServiceImplTest {
}
@Test
+ public void checkThrottler_rateLimitReached_forAppPackageName() throws InterruptedException {
+ // App calls AppSetId API directly, not via an SDK.
+ GetAppSetIdParam request =
+ new GetAppSetIdParam.Builder()
+ .setAppPackageName(TEST_APP_PACKAGE_NAME)
+ .setSdkPackageName(SDK_PACKAGE_NAME)
+ .build();
+
+ // Rate Limit Reached.
+ when(mMockThrottler.tryAcquire(
+ eq(Throttler.ApiKey.APPSETID_API_APP_PACKAGE_NAME), anyString()))
+ .thenReturn(false);
+ invokeGetAppSetIdAndVerifyError(mContext, STATUS_RATE_LIMIT_REACHED, request);
+ }
+
+ @Test
public void testEnforceForeground_sandboxCaller() throws Exception {
// Mock AppImportanceFilter to throw Exception when invoked. This is to verify getAppSetId()
// doesn't throw if caller is via Sandbox.
@@ -252,6 +282,7 @@ public class AppSetIdServiceImplTest {
mAdServicesLogger,
mClock,
mMockFlags,
+ mMockThrottler,
mMockAppImportanceFilter);
mAppSetIdServiceImpl.getAppSetId(
request,
@@ -334,6 +365,7 @@ public class AppSetIdServiceImplTest {
mAdServicesLogger,
mClock,
mMockFlags,
+ mMockThrottler,
mMockAppImportanceFilter);
}
@@ -345,6 +377,7 @@ public class AppSetIdServiceImplTest {
mAdServicesLogger,
mClock,
mMockFlags,
+ mMockThrottler,
mMockAppImportanceFilter);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdWorkerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdWorkerTest.java
index 9d3fb662d..bfb4dacd6 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdWorkerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsetid/AppSetIdWorkerTest.java
@@ -48,7 +48,7 @@ public class AppSetIdWorkerTest {
AppSetIdWorker spyWorker =
Mockito.spy(
AppSetIdWorker.getInstance(ApplicationProvider.getApplicationContext()));
- Mockito.when(spyWorker.getService()).thenReturn(mInterface);
+ Mockito.doReturn(mInterface).when(spyWorker).getService();
spyWorker.getAppSetId(
"testPackageName",
@@ -80,7 +80,7 @@ public class AppSetIdWorkerTest {
AppSetIdWorker spyWorker =
Mockito.spy(
AppSetIdWorker.getInstance(ApplicationProvider.getApplicationContext()));
- Mockito.when(spyWorker.getService()).thenReturn(mInterface);
+ Mockito.doReturn(mInterface).when(spyWorker).getService();
spyWorker.getAppSetId(
"testPackageName",
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdServicesCommonServiceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdServicesCommonServiceImplTest.java
index 49161b8b0..7a16568c7 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdServicesCommonServiceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdServicesCommonServiceImplTest.java
@@ -163,7 +163,7 @@ public class AdServicesCommonServiceImplTest {
ConsentNotificationJobService.schedule(
any(Context.class), any(Boolean.class)));
when(mFlags.getAdServicesEnabled()).thenReturn(true);
- ExtendedMockito.when(mConsentManager.getConsent(any(PackageManager.class)))
+ ExtendedMockito.when(mConsentManager.getConsent())
.thenReturn(AdServicesApiConsent.getConsent(true));
ExtendedMockito.doReturn(mConsentManager)
.when(() -> ConsentManager.getInstance(any(Context.class)));
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AllowListsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AllowListsTest.java
index 672e1c9ba..f14d5f4ec 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AllowListsTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AllowListsTest.java
@@ -57,6 +57,7 @@ public class AllowListsTest {
private static byte[] sSignature3;
private static String sHexString1;
private static String sHexString2;
+ private static String sHexString3;
@Mock PackageManager mMockPackageManager;
@Mock SigningInfo mMockSigningInfo;
@@ -86,29 +87,53 @@ public class AllowListsTest {
@Test
public void testAppCanUsePpapi_allowAll() {
+ assertThat(AllowLists.doesAllowListAllowAll(ALLOW_ALL)).isTrue();
assertThat(AllowLists.isPackageAllowListed(ALLOW_ALL, SOME_PACKAGE_NAME)).isTrue();
}
@Test
public void testAppCanUsePpapi_emptyAllowList() {
+ assertThat(AllowLists.doesAllowListAllowAll(EMPTY_LIST)).isFalse();
assertThat(AllowLists.isPackageAllowListed(EMPTY_LIST, SOME_PACKAGE_NAME)).isFalse();
+ assertThat(AllowLists.splitAllowList(EMPTY_LIST)).isEmpty();
}
@Test
public void testAppCanUsePpapi_notEmptyAllowList() {
String allowList = SOME_PACKAGE_NAME + ",AnotherPackageName";
+ assertThat(AllowLists.doesAllowListAllowAll(allowList)).isFalse();
assertThat(AllowLists.isPackageAllowListed(allowList, "notAllowedPackageName")).isFalse();
assertThat(AllowLists.isPackageAllowListed(allowList, SOME_PACKAGE_NAME)).isTrue();
assertThat(AllowLists.isPackageAllowListed(allowList, "AnotherPackageName")).isTrue();
+ assertThat(AllowLists.splitAllowList(allowList))
+ .containsExactly(SOME_PACKAGE_NAME, "AnotherPackageName");
}
@Test
- public void testAppCanUsePpapi_havingSpaceFail() {
- String allowList = SOME_PACKAGE_NAME + ", AnotherPackageName";
- assertThat(AllowLists.isPackageAllowListed(allowList, "notAllowedPackageName")).isFalse();
- assertThat(AllowLists.isPackageAllowListed(allowList, SOME_PACKAGE_NAME)).isTrue();
- // There is a space in the package name list.
- assertThat(AllowLists.isPackageAllowListed(allowList, "AnotherPackageName")).isFalse();
+ public void testAppCanUsePpapi_havingSpaces() {
+ // Allow list contains leading/trailing spaces
+ String listWithSpace =
+ SOME_PACKAGE_NAME + ", PackageName1,PackageName2 , PackageName3 ";
+ assertThat(AllowLists.isPackageAllowListed(listWithSpace, SOME_PACKAGE_NAME)).isTrue();
+ assertThat(AllowLists.isPackageAllowListed(listWithSpace, "PackageName1")).isTrue();
+ assertThat(AllowLists.isPackageAllowListed(listWithSpace, "PackageName2")).isTrue();
+ assertThat(AllowLists.isPackageAllowListed(listWithSpace, "PackageName3")).isTrue();
+ assertThat(AllowLists.splitAllowList(listWithSpace))
+ .containsExactly(SOME_PACKAGE_NAME, "PackageName1", "PackageName2", "PackageName3");
+ }
+
+ @Test
+ public void testAppCanUsePpapi_havingLineSeparators() {
+ // Allow list contains leading/trailing line separators
+ String listWithLineSeparator =
+ SOME_PACKAGE_NAME + ",\nPackageName1,PackageName2\n,\n\nPackageName3\n\n\n";
+ assertThat(AllowLists.isPackageAllowListed(listWithLineSeparator, SOME_PACKAGE_NAME))
+ .isTrue();
+ assertThat(AllowLists.isPackageAllowListed(listWithLineSeparator, "PackageName1")).isTrue();
+ assertThat(AllowLists.isPackageAllowListed(listWithLineSeparator, "PackageName2")).isTrue();
+ assertThat(AllowLists.isPackageAllowListed(listWithLineSeparator, "PackageName3")).isTrue();
+ assertThat(AllowLists.splitAllowList(listWithLineSeparator))
+ .containsExactly(SOME_PACKAGE_NAME, "PackageName1", "PackageName2", "PackageName3");
}
@Test
@@ -152,18 +177,37 @@ public class AllowListsTest {
}
@Test
- public void testSignatureAllowList_spaceBetweenSignatures() {
- String signatureAllowList = sHexString1 + ", " + sHexString2;
+ public void testSignatureAllowList_havingSpace() {
+ // Allow list contains leading/trailing spaces
+ String listWithSpace = sHexString1 + " , " + sHexString2 + ", " + sHexString3 + " ";
+ mockSignature(sSignature1);
+ assertThat(AllowLists.isSignatureAllowListed(mContext, listWithSpace, SOME_PACKAGE_NAME))
+ .isTrue();
+ mockSignature(sSignature2);
+ assertThat(AllowLists.isSignatureAllowListed(mContext, listWithSpace, SOME_PACKAGE_NAME))
+ .isTrue();
+ mockSignature(sSignature3);
+ assertThat(AllowLists.isSignatureAllowListed(mContext, listWithSpace, SOME_PACKAGE_NAME))
+ .isTrue();
+
+ // Allow list contains leading/trailing line separators
+ String listWithLineSeparator =
+ sHexString1 + "\n,\n" + sHexString2 + ",\n\n" + sHexString3 + "\n\n\n";
mockSignature(sSignature1);
assertThat(
AllowLists.isSignatureAllowListed(
- mContext, signatureAllowList, SOME_PACKAGE_NAME))
+ mContext, listWithLineSeparator, SOME_PACKAGE_NAME))
.isTrue();
mockSignature(sSignature2);
assertThat(
AllowLists.isSignatureAllowListed(
- mContext, signatureAllowList, SOME_PACKAGE_NAME))
- .isFalse();
+ mContext, listWithLineSeparator, SOME_PACKAGE_NAME))
+ .isTrue();
+ mockSignature(sSignature3);
+ assertThat(
+ AllowLists.isSignatureAllowListed(
+ mContext, listWithLineSeparator, SOME_PACKAGE_NAME))
+ .isTrue();
}
@Test
@@ -259,5 +303,6 @@ public class AllowListsTest {
sHexString1 = "0000000000000000000000000000000000000001";
sHexString2 = "0000000000000000000000000000000000000002";
+ sHexString3 = "0000000000000000000000000000000000000003";
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/BackgroundJobsManagerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/BackgroundJobsManagerTest.java
index 23bf20ca6..3cbe1ea9a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/BackgroundJobsManagerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/BackgroundJobsManagerTest.java
@@ -16,6 +16,7 @@
package com.android.adservices.service.common;
+import static com.android.adservices.service.AdServicesConfig.ASYNC_REGISTRATION_QUEUE_JOB_ID;
import static com.android.adservices.service.AdServicesConfig.CONSENT_NOTIFICATION_JOB_ID;
import static com.android.adservices.service.AdServicesConfig.FLEDGE_BACKGROUND_FETCH_JOB_ID;
import static com.android.adservices.service.AdServicesConfig.MAINTENANCE_JOB_ID;
@@ -27,6 +28,7 @@ import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_AGGREG
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_AGGREGATE_MAIN_REPORTING_JOB_ID;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_ATTRIBUTION_JOB_ID;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DELETE_EXPIRED_JOB_ID;
+import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DELETE_UNINSTALLED_JOB_ID;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_EVENT_FALLBACK_REPORTING_JOB_ID;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_EVENT_MAIN_REPORTING_JOB_ID;
import static com.android.adservices.service.AdServicesConfig.TOPICS_EPOCH_JOB_ID;
@@ -45,7 +47,9 @@ import com.android.adservices.download.MddJobService;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.MaintenanceJobService;
+import com.android.adservices.service.measurement.AsyncRegistrationQueueJobService;
import com.android.adservices.service.measurement.DeleteExpiredJobService;
+import com.android.adservices.service.measurement.DeleteUninstalledJobService;
import com.android.adservices.service.measurement.attribution.AttributionJobService;
import com.android.adservices.service.measurement.reporting.AggregateFallbackReportingJobService;
import com.android.adservices.service.measurement.reporting.AggregateReportingJobService;
@@ -76,6 +80,7 @@ public class BackgroundJobsManagerTest {
() -> {
ExtendedMockito.doReturn(false).when(mMockFlags).getMeasurementKillSwitch();
ExtendedMockito.doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ ExtendedMockito.doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
ExtendedMockito.doReturn(false)
.when(mMockFlags)
.getMddBackgroundTaskKillSwitch();
@@ -84,6 +89,7 @@ public class BackgroundJobsManagerTest {
assertMeasurementJobsScheduled(1);
assertTopicsJobsScheduled(1);
+ assertMaintenanceJobScheduled(1);
assertMddJobsScheduled(1);
});
}
@@ -94,6 +100,7 @@ public class BackgroundJobsManagerTest {
() -> {
ExtendedMockito.doReturn(true).when(mMockFlags).getMeasurementKillSwitch();
ExtendedMockito.doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ ExtendedMockito.doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
ExtendedMockito.doReturn(false)
.when(mMockFlags)
.getMddBackgroundTaskKillSwitch();
@@ -102,6 +109,7 @@ public class BackgroundJobsManagerTest {
assertMeasurementJobsScheduled(0);
assertTopicsJobsScheduled(1);
+ assertMaintenanceJobScheduled(1);
assertMddJobsScheduled(1);
});
}
@@ -112,6 +120,7 @@ public class BackgroundJobsManagerTest {
() -> {
ExtendedMockito.doReturn(false).when(mMockFlags).getMeasurementKillSwitch();
ExtendedMockito.doReturn(true).when(mMockFlags).getTopicsKillSwitch();
+ ExtendedMockito.doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
ExtendedMockito.doReturn(false)
.when(mMockFlags)
.getMddBackgroundTaskKillSwitch();
@@ -120,6 +129,7 @@ public class BackgroundJobsManagerTest {
assertMeasurementJobsScheduled(1);
assertTopicsJobsScheduled(0);
+ assertMaintenanceJobScheduled(1);
assertMddJobsScheduled(1);
});
}
@@ -130,6 +140,7 @@ public class BackgroundJobsManagerTest {
() -> {
ExtendedMockito.doReturn(false).when(mMockFlags).getMeasurementKillSwitch();
ExtendedMockito.doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ ExtendedMockito.doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
ExtendedMockito.doReturn(true)
.when(mMockFlags)
.getMddBackgroundTaskKillSwitch();
@@ -138,11 +149,52 @@ public class BackgroundJobsManagerTest {
assertMeasurementJobsScheduled(1);
assertTopicsJobsScheduled(1);
+ assertMaintenanceJobScheduled(1);
assertMddJobsScheduled(0);
});
}
@Test
+ public void testScheduleAllBackgroundJobs_selectAdsKillSwitchOn() throws Exception {
+ runWithMocks(
+ () -> {
+ ExtendedMockito.doReturn(false).when(mMockFlags).getMeasurementKillSwitch();
+ ExtendedMockito.doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ ExtendedMockito.doReturn(true).when(mMockFlags).getFledgeSelectAdsKillSwitch();
+ ExtendedMockito.doReturn(false)
+ .when(mMockFlags)
+ .getMddBackgroundTaskKillSwitch();
+
+ BackgroundJobsManager.scheduleAllBackgroundJobs(Mockito.mock(Context.class));
+
+ assertMeasurementJobsScheduled(1);
+ assertTopicsJobsScheduled(1);
+ assertMaintenanceJobScheduled(1);
+ assertMddJobsScheduled(1);
+ });
+ }
+
+ @Test
+ public void testScheduleAllBackgroundJobs_topicsAndSelectAdsKillSwitchOn() throws Exception {
+ runWithMocks(
+ () -> {
+ ExtendedMockito.doReturn(false).when(mMockFlags).getMeasurementKillSwitch();
+ ExtendedMockito.doReturn(true).when(mMockFlags).getTopicsKillSwitch();
+ ExtendedMockito.doReturn(true).when(mMockFlags).getFledgeSelectAdsKillSwitch();
+ ExtendedMockito.doReturn(false)
+ .when(mMockFlags)
+ .getMddBackgroundTaskKillSwitch();
+
+ BackgroundJobsManager.scheduleAllBackgroundJobs(Mockito.mock(Context.class));
+
+ assertMeasurementJobsScheduled(1);
+ assertTopicsJobsScheduled(0);
+ assertMaintenanceJobScheduled(0);
+ assertMddJobsScheduled(1);
+ });
+ }
+
+ @Test
public void testUnscheduleAllBackgroundJobs() {
// Execute
JobScheduler mockJobScheduler = mock(JobScheduler.class);
@@ -153,6 +205,7 @@ public class BackgroundJobsManagerTest {
verify(mockJobScheduler, times(1)).cancel(eq(TOPICS_EPOCH_JOB_ID));
verify(mockJobScheduler, times(1)).cancel(eq(MEASUREMENT_EVENT_MAIN_REPORTING_JOB_ID));
verify(mockJobScheduler, times(1)).cancel(eq(MEASUREMENT_DELETE_EXPIRED_JOB_ID));
+ verify(mockJobScheduler, times(1)).cancel(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
verify(mockJobScheduler, times(1)).cancel(eq(MEASUREMENT_ATTRIBUTION_JOB_ID));
verify(mockJobScheduler, times(1)).cancel(eq(MEASUREMENT_EVENT_FALLBACK_REPORTING_JOB_ID));
verify(mockJobScheduler, times(1)).cancel(eq(MEASUREMENT_AGGREGATE_MAIN_REPORTING_JOB_ID));
@@ -164,6 +217,7 @@ public class BackgroundJobsManagerTest {
verify(mockJobScheduler, times(1)).cancel(eq(MDD_CHARGING_PERIODIC_TASK_JOB_ID));
verify(mockJobScheduler, times(1)).cancel(eq(MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID));
verify(mockJobScheduler, times(1)).cancel(eq(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID));
+ verify(mockJobScheduler, times(1)).cancel(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
}
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
@@ -177,9 +231,11 @@ public class BackgroundJobsManagerTest {
.spyStatic(EventReportingJobService.class)
.spyStatic(EventFallbackReportingJobService.class)
.spyStatic(DeleteExpiredJobService.class)
+ .spyStatic(DeleteUninstalledJobService.class)
.spyStatic(FlagsFactory.class)
.spyStatic(MaintenanceJobService.class)
.spyStatic(MddJobService.class)
+ .spyStatic(AsyncRegistrationQueueJobService.class)
.strictness(Strictness.LENIENT)
.startMocking();
@@ -205,10 +261,17 @@ public class BackgroundJobsManagerTest {
any(), anyBoolean()));
ExtendedMockito.doNothing()
.when(() -> DeleteExpiredJobService.scheduleIfNeeded(any(), anyBoolean()));
+ ExtendedMockito.doNothing()
+ .when(() -> DeleteUninstalledJobService.scheduleIfNeeded(any(), anyBoolean()));
ExtendedMockito.doReturn(true)
.when(() -> MaintenanceJobService.scheduleIfNeeded(any(), anyBoolean()));
ExtendedMockito.doReturn(true)
.when(() -> MddJobService.scheduleIfNeeded(any(), anyBoolean()));
+ ExtendedMockito.doNothing()
+ .when(
+ () ->
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ any(), anyBoolean()));
// Execute
execute.run();
@@ -236,16 +299,25 @@ public class BackgroundJobsManagerTest {
ExtendedMockito.verify(
() -> DeleteExpiredJobService.scheduleIfNeeded(any(), eq(false)),
times(numberOfTimes));
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.scheduleIfNeeded(any(), eq(false)),
+ times(numberOfTimes));
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.scheduleIfNeeded(any(), eq(false)),
+ times(numberOfTimes));
}
- private void assertTopicsJobsScheduled(int numberOfTimes) {
- ExtendedMockito.verify(
- () -> EpochJobService.scheduleIfNeeded(any(), eq(false)), times(numberOfTimes));
+ private void assertMaintenanceJobScheduled(int numberOfTimes) {
ExtendedMockito.verify(
() -> MaintenanceJobService.scheduleIfNeeded(any(), eq(false)),
times(numberOfTimes));
}
+ private void assertTopicsJobsScheduled(int numberOfTimes) {
+ ExtendedMockito.verify(
+ () -> EpochJobService.scheduleIfNeeded(any(), eq(false)), times(numberOfTimes));
+ }
+
private void assertMddJobsScheduled(int numberOfTimes) {
ExtendedMockito.verify(
() -> MddJobService.scheduleIfNeeded(any(), eq(false)), times(numberOfTimes));
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ConsentNotificationJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ConsentNotificationJobServiceTest.java
index b3b981a81..405dfc139 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ConsentNotificationJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ConsentNotificationJobServiceTest.java
@@ -102,10 +102,8 @@ public class ConsentNotificationJobServiceTest {
CountDownLatch jobFinishedCountDown = new CountDownLatch(1);
doReturn(mPackageManager).when(mConsentNotificationJobService).getPackageManager();
- doReturn(Boolean.FALSE)
- .when(consentManager)
- .wasNotificationDisplayed(any(PackageManager.class));
- doNothing().when(consentManager).recordNotificationDisplayed(any());
+ doReturn(Boolean.FALSE).when(consentManager).wasNotificationDisplayed();
+ doNothing().when(consentManager).recordNotificationDisplayed();
mConsentNotificationJobService.setConsentManager(consentManager);
doReturn(consentManager).when(() -> ConsentManager.getInstance(any(Context.class)));
doReturn(true).when(() -> ConsentNotificationJobService.isEuDevice(any(Context.class)));
@@ -127,7 +125,7 @@ public class ConsentNotificationJobServiceTest {
mConsentNotificationJobService.onStartJob(mMockJobParameters);
jobFinishedCountDown.await();
- verify(consentManager).wasNotificationDisplayed(any());
+ verify(consentManager).wasNotificationDisplayed();
verify(mAdservicesSyncUtil).execute(any(Context.class), any(Boolean.class));
verify(mConsentNotificationJobService).jobFinished(mMockJobParameters, false);
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAllowListsFilterTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAllowListsFilterTest.java
index 3b07784a3..ba423d6bc 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAllowListsFilterTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAllowListsFilterTest.java
@@ -16,7 +16,10 @@
package com.android.adservices.service.common;
-import static com.android.adservices.stats.FledgeApiCallStatsMatcher.aCallStatForFledgeApiWithStatus;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
@@ -35,12 +38,12 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoSession;
-import org.mockito.Spy;
public class FledgeAllowListsFilterTest {
private static final int API_NAME_LOGGING_ID = 1;
- @Spy private final AdServicesLogger mAdServicesLoggerSpy = AdServicesLoggerImpl.getInstance();
+ private final AdServicesLogger mAdServicesLoggerMock =
+ ExtendedMockito.mock(AdServicesLoggerImpl.class);
private FledgeAllowListsFilter mFledgeAllowListsFilter;
@@ -54,7 +57,7 @@ public class FledgeAllowListsFilterTest {
.initMocks(this)
.startMocking();
mFledgeAllowListsFilter =
- new FledgeAllowListsFilter(CommonFixture.FLAGS_FOR_TEST, mAdServicesLoggerSpy);
+ new FledgeAllowListsFilter(CommonFixture.FLAGS_FOR_TEST, mAdServicesLoggerMock);
}
@After
@@ -72,7 +75,7 @@ public class FledgeAllowListsFilterTest {
mFledgeAllowListsFilter.assertAppCanUsePpapi(
CommonFixture.TEST_PACKAGE_NAME, API_NAME_LOGGING_ID);
- verifyZeroInteractions(mAdServicesLoggerSpy);
+ verifyZeroInteractions(mAdServicesLoggerMock);
}
@Test
@@ -92,16 +95,11 @@ public class FledgeAllowListsFilterTest {
assertEquals(
AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE,
exception.getMessage());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- API_NAME_LOGGING_ID, AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- API_NAME_LOGGING_ID,
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED));
-
- verifyNoMoreInteractions(mAdServicesLoggerSpy);
+ eq(API_NAME_LOGGING_ID), eq(STATUS_CALLER_NOT_ALLOWED), anyInt());
+
+ verifyNoMoreInteractions(mAdServicesLoggerMock);
}
@Test
@@ -110,6 +108,6 @@ public class FledgeAllowListsFilterTest {
NullPointerException.class,
() -> mFledgeAllowListsFilter.assertAppCanUsePpapi(null, API_NAME_LOGGING_ID));
- verifyZeroInteractions(mAdServicesLoggerSpy);
+ verifyZeroInteractions(mAdServicesLoggerMock);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAuthorizationFilterTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAuthorizationFilterTest.java
index 9f62f24c6..dd91a311e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAuthorizationFilterTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeAuthorizationFilterTest.java
@@ -16,7 +16,12 @@
package com.android.adservices.service.common;
-import static com.android.adservices.stats.FledgeApiCallStatsMatcher.aCallStatForFledgeApiWithStatus;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_PERMISSION_NOT_REQUESTED;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
@@ -33,6 +38,7 @@ import android.content.pm.PackageManager;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.service.PhFlags;
import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
@@ -44,7 +50,6 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoSession;
-import org.mockito.Spy;
public class FledgeAuthorizationFilterTest {
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
@@ -59,7 +64,9 @@ public class FledgeAuthorizationFilterTest {
@Mock private PackageManager mPackageManager;
@Mock private EnrollmentDao mEnrollmentDao;
- @Spy private final AdServicesLogger mAdServicesLoggerSpy = AdServicesLoggerImpl.getInstance();
+ @Mock private PhFlags mPhFlags;
+ private final AdServicesLogger mAdServicesLoggerMock =
+ ExtendedMockito.mock(AdServicesLoggerImpl.class);
public MockitoSession mMockitoSession;
@@ -71,11 +78,12 @@ public class FledgeAuthorizationFilterTest {
ExtendedMockito.mockitoSession()
.mockStatic(PermissionHelper.class)
.mockStatic(AppManifestConfigHelper.class)
+ .mockStatic(PhFlags.class)
.initMocks(this)
.startMocking();
mChecker =
new FledgeAuthorizationFilter(
- mPackageManager, mEnrollmentDao, mAdServicesLoggerSpy);
+ mPackageManager, mEnrollmentDao, mAdServicesLoggerMock);
}
@After
@@ -92,7 +100,7 @@ public class FledgeAuthorizationFilterTest {
verify(mPackageManager).getPackagesForUid(UID);
verifyNoMoreInteractions(mPackageManager);
- verifyZeroInteractions(mAdServicesLoggerSpy, mEnrollmentDao);
+ verifyZeroInteractions(mAdServicesLoggerMock, mEnrollmentDao);
}
@Test
@@ -101,7 +109,7 @@ public class FledgeAuthorizationFilterTest {
NullPointerException.class,
() -> mChecker.assertCallingPackageName(null, UID, API_NAME_LOGGING_ID));
- verifyZeroInteractions(mPackageManager, mAdServicesLoggerSpy, mEnrollmentDao);
+ verifyZeroInteractions(mPackageManager, mAdServicesLoggerMock, mEnrollmentDao);
}
@Test
@@ -119,14 +127,9 @@ public class FledgeAuthorizationFilterTest {
AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE,
exception.getMessage());
verify(mPackageManager).getPackagesForUid(UID);
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(
- API_NAME_LOGGING_ID, AdServicesStatusUtils.STATUS_UNAUTHORIZED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- API_NAME_LOGGING_ID, AdServicesStatusUtils.STATUS_UNAUTHORIZED));
- verifyNoMoreInteractions(mPackageManager, mAdServicesLoggerSpy, mEnrollmentDao);
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(eq(API_NAME_LOGGING_ID), eq(STATUS_UNAUTHORIZED), anyInt());
+ verifyNoMoreInteractions(mPackageManager, mAdServicesLoggerMock, mEnrollmentDao);
}
@Test
@@ -144,14 +147,9 @@ public class FledgeAuthorizationFilterTest {
AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE,
exception.getMessage());
verify(mPackageManager).getPackagesForUid(UID);
- verify(mAdServicesLoggerSpy)
- .logFledgeApiCallStats(
- API_NAME_LOGGING_ID, AdServicesStatusUtils.STATUS_UNAUTHORIZED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- API_NAME_LOGGING_ID, AdServicesStatusUtils.STATUS_UNAUTHORIZED));
- verifyNoMoreInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerSpy);
+ verify(mAdServicesLoggerMock)
+ .logFledgeApiCallStats(eq(API_NAME_LOGGING_ID), eq(STATUS_UNAUTHORIZED), anyInt());
+ verifyNoMoreInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerMock);
}
@Test
@@ -160,7 +158,7 @@ public class FledgeAuthorizationFilterTest {
mChecker.assertAppDeclaredPermission(CONTEXT, API_NAME_LOGGING_ID);
- verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerSpy);
+ verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerMock);
}
@Test
@@ -175,15 +173,10 @@ public class FledgeAuthorizationFilterTest {
assertEquals(
AdServicesStatusUtils.SECURITY_EXCEPTION_PERMISSION_NOT_REQUESTED_ERROR_MESSAGE,
exception.getMessage());
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- API_NAME_LOGGING_ID, AdServicesStatusUtils.STATUS_PERMISSION_NOT_REQUESTED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- API_NAME_LOGGING_ID,
- AdServicesStatusUtils.STATUS_PERMISSION_NOT_REQUESTED));
- verifyNoMoreInteractions(mAdServicesLoggerSpy);
+ eq(API_NAME_LOGGING_ID), eq(STATUS_PERMISSION_NOT_REQUESTED), anyInt());
+ verifyNoMoreInteractions(mAdServicesLoggerMock);
verifyZeroInteractions(mPackageManager, mEnrollmentDao);
}
@@ -193,7 +186,7 @@ public class FledgeAuthorizationFilterTest {
NullPointerException.class,
() -> mChecker.assertAppDeclaredPermission(null, API_NAME_LOGGING_ID));
- verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerSpy);
+ verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerMock);
}
@Test
@@ -204,13 +197,15 @@ public class FledgeAuthorizationFilterTest {
when(AppManifestConfigHelper.isAllowedCustomAudiencesAccess(
CONTEXT, PACKAGE_NAME, ENROLLMENT_ID))
.thenReturn(true);
+ when(PhFlags.getInstance()).thenReturn(mPhFlags);
+ when(mPhFlags.isEnrollmentBlocklisted(ENROLLMENT_ID)).thenReturn(false);
mChecker.assertAdTechAllowed(
CONTEXT, PACKAGE_NAME, CommonFixture.VALID_BUYER_1, API_NAME_LOGGING_ID);
verify(mEnrollmentDao)
.getEnrollmentDataForFledgeByAdTechIdentifier(CommonFixture.VALID_BUYER_1);
verifyNoMoreInteractions(mEnrollmentDao);
- verifyZeroInteractions(mPackageManager, mAdServicesLoggerSpy);
+ verifyZeroInteractions(mPackageManager, mAdServicesLoggerMock);
}
@Test
@@ -234,15 +229,10 @@ public class FledgeAuthorizationFilterTest {
exception.getMessage());
verify(mEnrollmentDao)
.getEnrollmentDataForFledgeByAdTechIdentifier(CommonFixture.VALID_BUYER_1);
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- API_NAME_LOGGING_ID, AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- API_NAME_LOGGING_ID,
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED));
- verifyNoMoreInteractions(mEnrollmentDao, mAdServicesLoggerSpy);
+ eq(API_NAME_LOGGING_ID), eq(STATUS_CALLER_NOT_ALLOWED), anyInt());
+ verifyNoMoreInteractions(mEnrollmentDao, mAdServicesLoggerMock);
verifyZeroInteractions(mPackageManager);
}
@@ -270,19 +260,36 @@ public class FledgeAuthorizationFilterTest {
exception.getMessage());
verify(mEnrollmentDao)
.getEnrollmentDataForFledgeByAdTechIdentifier(CommonFixture.VALID_BUYER_1);
- verify(mAdServicesLoggerSpy)
+ verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
- API_NAME_LOGGING_ID, AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(
- aCallStatForFledgeApiWithStatus(
- API_NAME_LOGGING_ID,
- AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED));
- verifyNoMoreInteractions(mEnrollmentDao, mAdServicesLoggerSpy);
+ eq(API_NAME_LOGGING_ID), eq(STATUS_CALLER_NOT_ALLOWED), anyInt());
+ verifyNoMoreInteractions(mEnrollmentDao, mAdServicesLoggerMock);
verifyZeroInteractions(mPackageManager);
}
@Test
+ public void testAdTechInBlocklist_throwSecurityException() {
+ when(mEnrollmentDao.getEnrollmentDataForFledgeByAdTechIdentifier(
+ CommonFixture.VALID_BUYER_1))
+ .thenReturn(ENROLLMENT_DATA);
+ when(AppManifestConfigHelper.isAllowedCustomAudiencesAccess(
+ CONTEXT, PACKAGE_NAME, ENROLLMENT_ID))
+ .thenReturn(true);
+ // Add ENROLLMENT_ID to blocklist.
+ when(PhFlags.getInstance()).thenReturn(mPhFlags);
+ when(mPhFlags.isEnrollmentBlocklisted(ENROLLMENT_ID)).thenReturn(true);
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mChecker.assertAdTechAllowed(
+ CONTEXT,
+ PACKAGE_NAME,
+ CommonFixture.VALID_BUYER_1,
+ API_NAME_LOGGING_ID));
+ }
+
+ @Test
public void testAssertAdTechHasPermission_nullContext_throwNpe() {
assertThrows(
NullPointerException.class,
@@ -293,7 +300,7 @@ public class FledgeAuthorizationFilterTest {
CommonFixture.VALID_BUYER_1,
API_NAME_LOGGING_ID));
- verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerSpy);
+ verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerMock);
}
@Test
@@ -304,7 +311,7 @@ public class FledgeAuthorizationFilterTest {
mChecker.assertAdTechAllowed(
CONTEXT, null, CommonFixture.VALID_BUYER_1, API_NAME_LOGGING_ID));
- verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerSpy);
+ verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerMock);
}
@Test
@@ -315,6 +322,6 @@ public class FledgeAuthorizationFilterTest {
mChecker.assertAdTechAllowed(
CONTEXT, PACKAGE_NAME, null, API_NAME_LOGGING_ID));
- verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerSpy);
+ verifyZeroInteractions(mPackageManager, mEnrollmentDao, mAdServicesLoggerMock);
}
}
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 74d554ba3..88a1ab355 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
@@ -50,6 +50,7 @@ import android.adservices.common.AdData;
import android.adservices.common.AdSelectionSignals;
import android.adservices.common.AdServicesStatusUtils;
import android.adservices.common.AdTechIdentifier;
+import android.adservices.common.CallerMetadata;
import android.adservices.common.CallingAppUidSupplierProcessImpl;
import android.adservices.common.CommonFixture;
import android.adservices.common.FledgeErrorResponse;
@@ -112,6 +113,7 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.function.Supplier;
public class FledgeE2ETest {
@@ -147,6 +149,7 @@ public class FledgeE2ETest {
+ "\t\"render_uri_1\": \"signals_for_1\",\n"
+ "\t\"render_uri_2\": \"signals_for_2\"\n"
+ "}");
+ private static final long BINDER_ELAPSED_TIMESTAMP = 100L;
private static final List<Double> BIDS_FOR_BUYER_1 = ImmutableList.of(1.1, 2.2);
private static final List<Double> BIDS_FOR_BUYER_2 = ImmutableList.of(4.5, 6.7, 10.0);
private static final List<Double> INVALID_BIDS = ImmutableList.of(0.0, -1.0, -2.0);
@@ -164,6 +167,7 @@ public class FledgeE2ETest {
private AdSelectionEntryDao mAdSelectionEntryDao;
private ExecutorService mLightweightExecutorService;
private ExecutorService mBackgroundExecutorService;
+ private ScheduledThreadPoolExecutor mScheduledExecutor;
private CustomAudienceServiceImpl mCustomAudienceService;
private AdSelectionServiceImpl mAdSelectionService;
@@ -201,6 +205,7 @@ public class FledgeE2ETest {
mLightweightExecutorService = AdServicesExecutors.getLightWeightExecutor();
mBackgroundExecutorService = AdServicesExecutors.getBackgroundExecutor();
+ mScheduledExecutor = AdServicesExecutors.getScheduler();
mAdServicesHttpsClient =
new AdServicesHttpsClient(AdServicesExecutors.getBlockingExecutor());
@@ -239,6 +244,7 @@ public class FledgeE2ETest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
mAdServicesLogger,
@@ -256,6 +262,8 @@ public class FledgeE2ETest {
.thenReturn(true);
when(mMockThrottler.tryAcquire(eq(FLEDGE_API_LEAVE_CUSTOM_AUDIENCE), anyString()))
.thenReturn(true);
+
+ mLocalhostBuyerDomain = Uri.parse(mMockWebServerRule.getServerBaseAddress());
}
@After
@@ -267,18 +275,21 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowSuccessWithDevOverrides() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
+
+ Uri sellerReportingUri = mMockWebServerRule.uriForPath(SELLER_REPORTING_PATH);
+ Uri buyerReportingUri = mMockWebServerRule.uriForPath(BUYER_REPORTING_PATH);
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(
ImmutableList.of(
- AdTechIdentifier.fromString(BUYER_DOMAIN_1.getHost()),
- AdTechIdentifier.fromString(BUYER_DOMAIN_2.getHost())))
+ AdTechIdentifier.fromString(
+ mLocalhostBuyerDomain.getHost())))
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
@@ -288,6 +299,11 @@ public class FledgeE2ETest {
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
+ .setPerBuyerSignals(
+ ImmutableMap.of(
+ AdTechIdentifier.fromString(
+ mLocalhostBuyerDomain.getHost()),
+ AdSelectionSignals.fromString("{\"buyer_signals\":0}")))
.build();
String decisionLogicJs =
@@ -300,19 +316,19 @@ public class FledgeE2ETest {
+ " contextual_signals) { \n"
+ " return {'status': 0, 'results': {'signals_for_buyer':"
+ " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
- + SELLER_REPORTING_PATH
+ + sellerReportingUri
+ "' } };\n"
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
+ "function reportWin(ad_selection_signals, per_buyer_signals,"
+ " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ " return {'status': 0, 'results': {'reporting_uri': '"
- + BUYER_REPORTING_PATH
+ + buyerReportingUri
+ "' } };\n"
+ "}";
@@ -330,9 +346,13 @@ public class FledgeE2ETest {
doNothing()
.when(() -> BackgroundFetchJobService.scheduleIfNeeded(any(), any(), anyBoolean()));
- CustomAudience customAudience1 = createCustomAudience(BUYER_DOMAIN_1, BIDS_FOR_BUYER_1);
+ CustomAudience customAudience1 =
+ createCustomAudience(
+ mLocalhostBuyerDomain, CUSTOM_AUDIENCE_SEQ_1, BIDS_FOR_BUYER_1);
- CustomAudience customAudience2 = createCustomAudience(BUYER_DOMAIN_2, BIDS_FOR_BUYER_2);
+ CustomAudience customAudience2 =
+ createCustomAudience(
+ mLocalhostBuyerDomain, CUSTOM_AUDIENCE_SEQ_2, BIDS_FOR_BUYER_2);
// Join first custom audience
ResultCapturingCallback joinCallback1 = new ResultCapturingCallback();
@@ -390,7 +410,9 @@ public class FledgeE2ETest {
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
- CommonFixture.getUri(BUYER_DOMAIN_2.getHost(), AD_URI_PREFIX + "/ad3"),
+ CommonFixture.getUri(
+ mLocalhostBuyerDomain.getAuthority(),
+ AD_URI_PREFIX + CUSTOM_AUDIENCE_SEQ_2 + "/ad3"),
resultsCallback.mAdSelectionResponse.getRenderUri());
// Run Report Impression
@@ -412,21 +434,24 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowSuccessWithDevOverridesWithRevokedUserConsentForApp()
throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Allow the first calls to succeed so that we can verify the rest of the flow works
- when(mConsentManagerMock.isFledgeConsentRevokedForApp(any(), any()))
+ when(mConsentManagerMock.isFledgeConsentRevokedForApp(any()))
.thenReturn(false)
.thenReturn(true);
- when(mConsentManagerMock.isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any()))
+ when(mConsentManagerMock.isFledgeConsentRevokedForAppAfterSettingFledgeUse(any()))
.thenReturn(false)
.thenReturn(true);
+ Uri sellerReportingUri = mMockWebServerRule.uriForPath(SELLER_REPORTING_PATH);
+ Uri buyerReportingUri = mMockWebServerRule.uriForPath(BUYER_REPORTING_PATH);
+
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(
ImmutableList.of(
- AdTechIdentifier.fromString(BUYER_DOMAIN_1.getHost()),
- AdTechIdentifier.fromString(BUYER_DOMAIN_2.getHost())))
+ AdTechIdentifier.fromString(
+ mLocalhostBuyerDomain.getHost())))
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
@@ -436,6 +461,11 @@ public class FledgeE2ETest {
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
+ .setPerBuyerSignals(
+ ImmutableMap.of(
+ AdTechIdentifier.fromString(
+ mLocalhostBuyerDomain.getHost()),
+ AdSelectionSignals.fromString("{\"buyer_signals\":0}")))
.build();
String decisionLogicJs =
@@ -448,19 +478,19 @@ public class FledgeE2ETest {
+ " contextual_signals) { \n"
+ " return {'status': 0, 'results': {'signals_for_buyer':"
+ " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
- + SELLER_REPORTING_PATH
+ + sellerReportingUri
+ "' } };\n"
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
+ "function reportWin(ad_selection_signals, per_buyer_signals,"
+ " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ " return {'status': 0, 'results': {'reporting_uri': '"
- + BUYER_REPORTING_PATH
+ + buyerReportingUri
+ "' } };\n"
+ "}";
@@ -475,9 +505,13 @@ public class FledgeE2ETest {
.setCallingAppPackageName(CommonFixture.TEST_PACKAGE_NAME)
.build());
- CustomAudience customAudience1 = createCustomAudience(BUYER_DOMAIN_1, BIDS_FOR_BUYER_1);
+ CustomAudience customAudience1 =
+ createCustomAudience(
+ mLocalhostBuyerDomain, CUSTOM_AUDIENCE_SEQ_1, BIDS_FOR_BUYER_1);
- CustomAudience customAudience2 = createCustomAudience(BUYER_DOMAIN_2, BIDS_FOR_BUYER_2);
+ CustomAudience customAudience2 =
+ createCustomAudience(
+ mLocalhostBuyerDomain, CUSTOM_AUDIENCE_SEQ_2, BIDS_FOR_BUYER_2);
// Join first custom audience
ResultCapturingCallback joinCallback1 = new ResultCapturingCallback();
@@ -534,7 +568,9 @@ public class FledgeE2ETest {
long resultSelectionId = resultsCallback.mAdSelectionResponse.getAdSelectionId();
assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
assertEquals(
- CommonFixture.getUri(BUYER_DOMAIN_1.getHost(), AD_URI_PREFIX + "/ad2"),
+ CommonFixture.getUri(
+ mLocalhostBuyerDomain.getAuthority(),
+ AD_URI_PREFIX + CUSTOM_AUDIENCE_SEQ_1 + "/ad2"),
resultsCallback.mAdSelectionResponse.getRenderUri());
// Run Report Impression
@@ -555,11 +591,11 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowFailsWithMismatchedPackageNames() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
String otherPackageName = CommonFixture.TEST_PACKAGE_NAME + "different_package";
@@ -581,6 +617,7 @@ public class FledgeE2ETest {
mAppImportanceFilter,
mLightweightExecutorService,
mBackgroundExecutorService,
+ mScheduledExecutor,
CONTEXT,
mConsentManagerMock,
mAdServicesLogger,
@@ -644,7 +681,7 @@ public class FledgeE2ETest {
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
@@ -752,18 +789,21 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowSuccessWithOneCAWithNegativeBidsWithDevOverrides() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
+
+ Uri sellerReportingUri = mMockWebServerRule.uriForPath(SELLER_REPORTING_PATH);
+ Uri buyerReportingUri = mMockWebServerRule.uriForPath(BUYER_REPORTING_PATH);
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
.setCustomAudienceBuyers(
ImmutableList.of(
- AdTechIdentifier.fromString(BUYER_DOMAIN_1.getHost()),
- AdTechIdentifier.fromString(BUYER_DOMAIN_2.getHost())))
+ AdTechIdentifier.fromString(
+ mLocalhostBuyerDomain.getHost())))
.setSeller(
AdTechIdentifier.fromString(
mMockWebServerRule
@@ -773,6 +813,11 @@ public class FledgeE2ETest {
mMockWebServerRule.uriForPath(SELLER_DECISION_LOGIC_URI_PATH))
.setTrustedScoringSignalsUri(
mMockWebServerRule.uriForPath(SELLER_TRUSTED_SIGNAL_URI_PATH))
+ .setPerBuyerSignals(
+ ImmutableMap.of(
+ AdTechIdentifier.fromString(
+ mLocalhostBuyerDomain.getHost()),
+ AdSelectionSignals.fromString("{\"buyer_signals\":0}")))
.build();
String decisionLogicJs =
@@ -785,19 +830,19 @@ public class FledgeE2ETest {
+ " contextual_signals) { \n"
+ " return {'status': 0, 'results': {'signals_for_buyer':"
+ " '{\"signals_for_buyer\":1}', 'reporting_uri': '"
- + SELLER_REPORTING_PATH
+ + sellerReportingUri
+ "' } };\n"
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
+ "function reportWin(ad_selection_signals, per_buyer_signals,"
+ " signals_for_buyer, contextual_signals, custom_audience_signals) { \n"
+ " return {'status': 0, 'results': {'reporting_uri': '"
- + BUYER_REPORTING_PATH
+ + buyerReportingUri
+ "' } };\n"
+ "}";
@@ -818,10 +863,12 @@ public class FledgeE2ETest {
doNothing()
.when(() -> BackgroundFetchJobService.scheduleIfNeeded(any(), any(), anyBoolean()));
- CustomAudience customAudience1 = createCustomAudience(BUYER_DOMAIN_1, BIDS_FOR_BUYER_1);
-
- CustomAudience customAudience2 = createCustomAudience(BUYER_DOMAIN_2, INVALID_BIDS);
+ CustomAudience customAudience1 =
+ createCustomAudience(
+ mLocalhostBuyerDomain, CUSTOM_AUDIENCE_SEQ_1, BIDS_FOR_BUYER_1);
+ CustomAudience customAudience2 =
+ createCustomAudience(mLocalhostBuyerDomain, CUSTOM_AUDIENCE_SEQ_2, INVALID_BIDS);
// Join first custom audience
ResultCapturingCallback joinCallback1 = new ResultCapturingCallback();
mCustomAudienceService.joinCustomAudience(
@@ -883,7 +930,9 @@ public class FledgeE2ETest {
assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(resultSelectionId));
// Expect that ad from buyer 1 won since buyer 2 had negative bids
assertEquals(
- CommonFixture.getUri(BUYER_DOMAIN_1.getAuthority(), AD_URI_PREFIX + "/ad2"),
+ CommonFixture.getUri(
+ mLocalhostBuyerDomain.getAuthority(),
+ AD_URI_PREFIX + CUSTOM_AUDIENCE_SEQ_1 + "/ad2"),
resultsCallback.mAdSelectionResponse.getRenderUri());
// Run Report Impression
@@ -904,11 +953,11 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowFailsWithBothCANegativeBidsWithDevOverrides() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
@@ -942,7 +991,7 @@ public class FledgeE2ETest {
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
@@ -1043,14 +1092,13 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowSuccessWithMockServer() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
Uri sellerReportingUri = mMockWebServerRule.uriForPath(SELLER_REPORTING_PATH);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(BUYER_REPORTING_PATH);
- mLocalhostBuyerDomain = Uri.parse(mMockWebServerRule.getServerBaseAddress());
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
@@ -1089,7 +1137,7 @@ public class FledgeE2ETest {
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
@@ -1199,15 +1247,14 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowSuccessWithRevokedUserConsentForApp() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
// Allow the first join call to succeed so that we can verify the rest of the flow works
- when(mConsentManagerMock.isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any()))
+ when(mConsentManagerMock.isFledgeConsentRevokedForAppAfterSettingFledgeUse(any()))
.thenReturn(false)
.thenReturn(true);
Uri sellerReportingUri = mMockWebServerRule.uriForPath(SELLER_REPORTING_PATH);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(BUYER_REPORTING_PATH);
- mLocalhostBuyerDomain = Uri.parse(mMockWebServerRule.getServerBaseAddress());
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
@@ -1246,7 +1293,7 @@ public class FledgeE2ETest {
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
@@ -1353,12 +1400,10 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowSuccessWithRevokedUserConsentForFledge() throws Exception {
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent();
doReturn(true)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
-
- mLocalhostBuyerDomain = Uri.parse(mMockWebServerRule.getServerBaseAddress());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
@@ -1437,14 +1482,13 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowSuccessWithOneCAWithNegativeBidsWithMockServer() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
Uri sellerReportingUri = mMockWebServerRule.uriForPath(SELLER_REPORTING_PATH);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(BUYER_REPORTING_PATH);
- mLocalhostBuyerDomain = Uri.parse(mMockWebServerRule.getServerBaseAddress());
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
@@ -1483,7 +1527,7 @@ public class FledgeE2ETest {
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
@@ -1593,14 +1637,13 @@ public class FledgeE2ETest {
@Test
public void testFledgeFlowFailsWithOnlyCANegativeBidsWithMockServer() throws Exception {
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
Uri sellerReportingUri = mMockWebServerRule.uriForPath(SELLER_REPORTING_PATH);
Uri buyerReportingUri = mMockWebServerRule.uriForPath(BUYER_REPORTING_PATH);
- mLocalhostBuyerDomain = Uri.parse(mMockWebServerRule.getServerBaseAddress());
mAdSelectionConfig =
AdSelectionConfigFixture.anAdSelectionConfigBuilder()
@@ -1639,7 +1682,7 @@ public class FledgeE2ETest {
+ "}";
String biddingLogicJs =
"function generateBid(ad, auction_signals, per_buyer_signals,"
- + " trusted_bidding_signals, contextual_signals, user_signals,"
+ + " trusted_bidding_signals, contextual_signals,"
+ " custom_audience_signals) { \n"
+ " return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };\n"
+ "}\n"
@@ -1727,8 +1770,11 @@ public class FledgeE2ETest {
.setAdSelectionConfig(adSelectionConfig)
.setCallerPackageName(callerPackageName)
.build();
-
- adSelectionService.runAdSelection(input, adSelectionTestCallback);
+ CallerMetadata callerMetadata =
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(BINDER_ELAPSED_TIMESTAMP)
+ .build();
+ adSelectionService.runAdSelection(input, callerMetadata, adSelectionTestCallback);
adSelectionTestCallback.mCountDownLatch.await();
return adSelectionTestCallback;
}
@@ -1829,7 +1875,7 @@ public class FledgeE2ETest {
buyerDomain.getAuthority(),
BUYER_TRUSTED_SIGNAL_URI_PATH))
.setTrustedBiddingKeys(
- TrustedBiddingDataFixture.VALID_TRUSTED_BIDDING_KEYS)
+ TrustedBiddingDataFixture.getValidTrustedBiddingKeys())
.build())
.setBiddingLogicUri(
CommonFixture.getUri(
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeMaintenanceTasksWorkerTests.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeMaintenanceTasksWorkerTests.java
new file mode 100644
index 000000000..e853f4cf5
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeMaintenanceTasksWorkerTests.java
@@ -0,0 +1,172 @@
+/*
+ * 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.adservices.adselection.CustomAudienceSignalsFixture;
+import android.net.Uri;
+
+import androidx.room.Room;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.data.adselection.AdSelectionDatabase;
+import com.android.adservices.data.adselection.AdSelectionEntryDao;
+import com.android.adservices.data.adselection.DBAdSelection;
+import com.android.adservices.data.adselection.DBBuyerDecisionLogic;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoSession;
+
+import java.time.Clock;
+
+public class FledgeMaintenanceTasksWorkerTests {
+ private static final Flags TEST_FLAGS = FlagsFactory.getFlagsForTest();
+ private static final Uri BIDDING_LOGIC_URI = Uri.parse("https://biddinglogic.com");
+ private static final Uri EXPIRED_BIDDING_LOGIC_URI =
+ Uri.parse("https://expiredbiddinglogic.com");
+
+ private static final long AD_SELECTION_ID_1 = 12345L;
+ private static final long AD_SELECTION_ID_2 = 23456L;
+
+ private static final DBAdSelection DB_AD_SELECTION =
+ new DBAdSelection.Builder()
+ .setAdSelectionId(AD_SELECTION_ID_1)
+ .setCustomAudienceSignals(CustomAudienceSignalsFixture.aCustomAudienceSignals())
+ .setContextualSignals("contextualSignals")
+ .setBiddingLogicUri(BIDDING_LOGIC_URI)
+ .setWinningAdRenderUri(Uri.parse("https://winningAd.com"))
+ .setWinningAdBid(5)
+ .setCreationTimestamp(Clock.systemUTC().instant())
+ .setCallerPackageName("testPackageName")
+ .build();
+
+ private static final DBBuyerDecisionLogic DB_BUYER_DECISION_LOGIC =
+ new DBBuyerDecisionLogic.Builder()
+ .setBuyerDecisionLogicJs("buyerDecisionLogicJS")
+ .setBiddingLogicUri(BIDDING_LOGIC_URI)
+ .build();
+
+ private static final DBAdSelection EXPIRED_DB_AD_SELECTION =
+ new DBAdSelection.Builder()
+ .setAdSelectionId(AD_SELECTION_ID_2)
+ .setCustomAudienceSignals(CustomAudienceSignalsFixture.aCustomAudienceSignals())
+ .setContextualSignals("contextualSignals")
+ .setBiddingLogicUri(EXPIRED_BIDDING_LOGIC_URI)
+ .setWinningAdRenderUri(Uri.parse("https://winningAd.com"))
+ .setWinningAdBid(5)
+ .setCreationTimestamp(
+ Clock.systemUTC()
+ .instant()
+ .minusSeconds(2 * TEST_FLAGS.getAdSelectionExpirationWindowS()))
+ .setCallerPackageName("testPackageName")
+ .build();
+
+ private static final DBBuyerDecisionLogic EXPIRED_DB_BUYER_DECISION_LOGIC =
+ new DBBuyerDecisionLogic.Builder()
+ .setBuyerDecisionLogicJs("buyerDecisionLogicJS")
+ .setBiddingLogicUri(EXPIRED_BIDDING_LOGIC_URI)
+ .build();
+
+ private AdSelectionEntryDao mAdSelectionEntryDao;
+ private FledgeMaintenanceTasksWorker mFledgeMaintenanceTasksWorker;
+
+ @Before
+ public void setup() {
+ mAdSelectionEntryDao =
+ Room.inMemoryDatabaseBuilder(
+ ApplicationProvider.getApplicationContext(),
+ AdSelectionDatabase.class)
+ .build()
+ .adSelectionEntryDao();
+
+ mFledgeMaintenanceTasksWorker = new FledgeMaintenanceTasksWorker(mAdSelectionEntryDao);
+ }
+
+ @Test
+ public void testFledgeMaintenanceWorkerDoesNotRemoveValidData() throws Exception {
+ // Add valid and expired ad selections
+ mAdSelectionEntryDao.persistAdSelection(DB_AD_SELECTION);
+
+ assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(DB_AD_SELECTION.getAdSelectionId()));
+
+ // Add valid and expired buyer decision logics
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(DB_BUYER_DECISION_LOGIC);
+
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC.getBiddingLogicUri()));
+
+ // Assert that valid data was not cleared
+ assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(DB_AD_SELECTION.getAdSelectionId()));
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC.getBiddingLogicUri()));
+ }
+
+ @Test
+ public void testFledgeMaintenanceWorkerRemovesExpiredData() throws Exception {
+ MockitoSession mockitoSession =
+ ExtendedMockito.mockitoSession().spyStatic(FlagsFactory.class).startMocking();
+
+ // Mock static method FlagsFactory.getFlags() to return Mock Flags.
+ ExtendedMockito.doReturn(TEST_FLAGS).when(FlagsFactory::getFlags);
+
+ // Add valid and expired ad selections
+ mAdSelectionEntryDao.persistAdSelection(DB_AD_SELECTION);
+ mAdSelectionEntryDao.persistAdSelection(EXPIRED_DB_AD_SELECTION);
+
+ assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(DB_AD_SELECTION.getAdSelectionId()));
+ assertTrue(
+ mAdSelectionEntryDao.doesAdSelectionIdExist(
+ EXPIRED_DB_AD_SELECTION.getAdSelectionId()));
+
+ // Add valid and expired buyer decision logics
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(DB_BUYER_DECISION_LOGIC);
+ mAdSelectionEntryDao.persistBuyerDecisionLogic(EXPIRED_DB_BUYER_DECISION_LOGIC);
+
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC.getBiddingLogicUri()));
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ EXPIRED_DB_BUYER_DECISION_LOGIC.getBiddingLogicUri()));
+
+ mFledgeMaintenanceTasksWorker.clearExpiredAdSelectionData();
+
+ // Assert expired data was removed
+ assertFalse(
+ mAdSelectionEntryDao.doesAdSelectionIdExist(
+ EXPIRED_DB_AD_SELECTION.getAdSelectionId()));
+ assertFalse(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ EXPIRED_DB_BUYER_DECISION_LOGIC.getBiddingLogicUri()));
+
+ // Assert that valid data was not cleared
+ assertTrue(mAdSelectionEntryDao.doesAdSelectionIdExist(DB_AD_SELECTION.getAdSelectionId()));
+ assertTrue(
+ mAdSelectionEntryDao.doesBuyerDecisionLogicExist(
+ DB_BUYER_DECISION_LOGIC.getBiddingLogicUri()));
+
+ mockitoSession.finishMocking();
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/PackageChangedReceiverTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/PackageChangedReceiverTest.java
index 01cf5a59b..cff5002ee 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/PackageChangedReceiverTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/PackageChangedReceiverTest.java
@@ -174,14 +174,20 @@ public class PackageChangedReceiverTest {
.strictness(Strictness.LENIENT)
.startMocking();
try {
+ long epochId = 1;
+
// Kill switch is off.
doReturn(false).when(mMockFlags).getTopicsKillSwitch();
// Mock static method FlagsFactory.getFlags() to return Mock Flags.
when(FlagsFactory.getFlags()).thenReturn(mMockFlags);
+ // Enable TopicContributors feature
+ when(mMockEpochManager.supportsTopicContributorFeature()).thenReturn(true);
+
// Stubbing TopicsWorker.getInstance() to return mocked TopicsWorker instance
doReturn(mSpyTopicsWorker).when(() -> TopicsWorker.getInstance(any()));
+ doReturn(epochId).when(mMockEpochManager).getCurrentEpochId();
// Initialize package receiver meant for Topics
PackageChangedReceiver spyReceiver = createSpyPackageReceiverForTopics();
@@ -192,7 +198,9 @@ public class PackageChangedReceiverTest {
// Verify method in AppUpdateManager is invoked
// Note that only package name is passed into following methods.
- verify(mMockAppUpdateManager).deleteAppDataByUri(eq(Uri.parse(SAMPLE_PACKAGE)));
+ verify(mMockEpochManager).getCurrentEpochId();
+ verify(mMockAppUpdateManager)
+ .handleAppUninstallationInRealTime(Uri.parse(SAMPLE_PACKAGE), epochId);
} finally {
session.finishMocking();
}
@@ -223,7 +231,7 @@ public class PackageChangedReceiverTest {
Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS);
// When the kill switch is on, there is no Topics related work.
- verify(mSpyTopicsWorker, never()).deletePackageData(any());
+ verify(mSpyTopicsWorker, never()).handleAppUninstallation(any());
} finally {
session.finishMocking();
}
@@ -456,7 +464,7 @@ public class PackageChangedReceiverTest {
// Note that only package name is passed into following methods.
verify(mMockEpochManager).getCurrentEpochId();
verify(mMockAppUpdateManager)
- .assignTopicsToNewlyInstalledApps(eq(Uri.parse(SAMPLE_PACKAGE)), eq(epochId));
+ .handleAppInstallationInRealTime(Uri.parse(SAMPLE_PACKAGE), epochId);
} finally {
session.finishMocking();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java
index aa7f229ec..7433da0ee 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java
@@ -18,6 +18,7 @@ package com.android.adservices.service.consent;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__OPT_IN_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
import static com.google.common.truth.Truth.assertThat;
@@ -35,19 +36,25 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
import android.app.job.JobScheduler;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
+import androidx.test.core.content.pm.ApplicationInfoBuilder;
import androidx.test.filters.SmallTest;
+import com.android.adservices.data.DbHelper;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.common.BooleanFileDatastore;
import com.android.adservices.data.consent.AppConsentDao;
import com.android.adservices.data.consent.AppConsentDaoFixture;
import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.topics.Topic;
import com.android.adservices.data.topics.TopicsTables;
import com.android.adservices.download.MddJobService;
@@ -56,7 +63,9 @@ import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.MaintenanceJobService;
import com.android.adservices.service.common.BackgroundJobsManager;
+import com.android.adservices.service.measurement.AsyncRegistrationQueueJobService;
import com.android.adservices.service.measurement.DeleteExpiredJobService;
+import com.android.adservices.service.measurement.DeleteUninstalledJobService;
import com.android.adservices.service.measurement.MeasurementImpl;
import com.android.adservices.service.measurement.attribution.AttributionJobService;
import com.android.adservices.service.measurement.reporting.AggregateFallbackReportingJobService;
@@ -85,6 +94,8 @@ import org.mockito.Spy;
import org.mockito.quality.Strictness;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -95,6 +106,8 @@ public class ConsentManagerTest {
private BooleanFileDatastore mDatastore;
private ConsentManager mConsentManager;
private AppConsentDao mAppConsentDao;
+ private EnrollmentDao mEnrollmentDao;
+ private DbHelper mDbHelper;
@Mock private PackageManager mPackageManagerMock;
@Mock private TopicsWorker mTopicsWorker;
@@ -124,9 +137,12 @@ public class ConsentManagerTest {
.spyStatic(EventReportingJobService.class)
.spyStatic(EventFallbackReportingJobService.class)
.spyStatic(DeleteExpiredJobService.class)
+ .spyStatic(DeleteUninstalledJobService.class)
.spyStatic(FlagsFactory.class)
.spyStatic(MaintenanceJobService.class)
.spyStatic(MddJobService.class)
+ .spyStatic(DeviceRegionProvider.class)
+ .spyStatic(AsyncRegistrationQueueJobService.class)
.strictness(Strictness.WARN)
.initMocks(this)
.startMocking();
@@ -134,12 +150,15 @@ public class ConsentManagerTest {
mDatastore =
new BooleanFileDatastore(mContextSpy, AppConsentDaoFixture.TEST_DATASTORE_NAME, 1);
mAppConsentDao = spy(new AppConsentDao(mDatastore, mPackageManagerMock));
+ mDbHelper = DbTestUtil.getDbHelperForTest();
+ mEnrollmentDao = spy(new EnrollmentDao(mContextSpy, mDbHelper));
mConsentManager =
new ConsentManager(
mContextSpy,
mTopicsWorker,
mAppConsentDao,
+ mEnrollmentDao,
mMeasurementImpl,
mAdServicesLoggerImpl,
mCustomAudienceDaoMock,
@@ -171,8 +190,12 @@ public class ConsentManagerTest {
.when(() -> EventFallbackReportingJobService.scheduleIfNeeded(any(), anyBoolean()));
ExtendedMockito.doNothing()
.when(() -> DeleteExpiredJobService.scheduleIfNeeded(any(), anyBoolean()));
+ ExtendedMockito.doNothing()
+ .when(() -> DeleteUninstalledJobService.scheduleIfNeeded(any(), anyBoolean()));
ExtendedMockito.doReturn(true)
.when(() -> MaintenanceJobService.scheduleIfNeeded(any(), anyBoolean()));
+ ExtendedMockito.doNothing()
+ .when(() -> AsyncRegistrationQueueJobService.scheduleIfNeeded(any(), anyBoolean()));
}
@After
@@ -187,7 +210,7 @@ public class ConsentManagerTest {
public void testConsentIsGivenAfterEnabling() {
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
}
@Test
@@ -195,13 +218,14 @@ public class ConsentManagerTest {
doReturn(mPackageManagerMock).when(mContextSpy).getPackageManager();
mConsentManager.disable(mContextSpy);
- assertFalse(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertFalse(mConsentManager.getConsent().isGiven());
}
@Test
public void testJobsAreScheduledAfterEnablingKillSwitchOff() {
doReturn(mPackageManagerMock).when(mContextSpy).getPackageManager();
doReturn(false).when(mMockFlags).getTopicsKillSwitch();
+ doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch();
doReturn(false).when(mMockFlags).getMeasurementKillSwitch();
doReturn(false).when(mMockFlags).getMddBackgroundTaskKillSwitch();
@@ -230,12 +254,19 @@ public class ConsentManagerTest {
any(Context.class), eq(false)));
ExtendedMockito.verify(
() -> DeleteExpiredJobService.scheduleIfNeeded(any(Context.class), eq(false)));
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.scheduleIfNeeded(any(Context.class), eq(false)));
+ ExtendedMockito.verify(
+ () ->
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ any(Context.class), eq(false)));
}
@Test
public void testJobsAreNotScheduledAfterEnablingKillSwitchOn() {
doReturn(mPackageManagerMock).when(mContextSpy).getPackageManager();
doReturn(true).when(mMockFlags).getTopicsKillSwitch();
+ doReturn(true).when(mMockFlags).getFledgeSelectAdsKillSwitch();
doReturn(true).when(mMockFlags).getMeasurementKillSwitch();
doReturn(true).when(mMockFlags).getMddBackgroundTaskKillSwitch();
@@ -274,6 +305,14 @@ public class ConsentManagerTest {
ExtendedMockito.verify(
() -> DeleteExpiredJobService.scheduleIfNeeded(any(Context.class), eq(false)),
ExtendedMockito.never());
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.scheduleIfNeeded(any(Context.class), eq(false)),
+ ExtendedMockito.never());
+ ExtendedMockito.verify(
+ () ->
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ any(Context.class), eq(false)),
+ ExtendedMockito.never());
}
@Test
@@ -286,6 +325,7 @@ public class ConsentManagerTest {
verify(mJobSchedulerMock).cancel(AdServicesConfig.TOPICS_EPOCH_JOB_ID);
verify(mJobSchedulerMock).cancel(AdServicesConfig.MEASUREMENT_EVENT_MAIN_REPORTING_JOB_ID);
verify(mJobSchedulerMock).cancel(AdServicesConfig.MEASUREMENT_DELETE_EXPIRED_JOB_ID);
+ verify(mJobSchedulerMock).cancel(AdServicesConfig.MEASUREMENT_DELETE_UNINSTALLED_JOB_ID);
verify(mJobSchedulerMock).cancel(AdServicesConfig.MEASUREMENT_ATTRIBUTION_JOB_ID);
verify(mJobSchedulerMock)
.cancel(AdServicesConfig.MEASUREMENT_EVENT_FALLBACK_REPORTING_JOB_ID);
@@ -293,6 +333,7 @@ public class ConsentManagerTest {
.cancel(AdServicesConfig.MEASUREMENT_AGGREGATE_MAIN_REPORTING_JOB_ID);
verify(mJobSchedulerMock)
.cancel(AdServicesConfig.MEASUREMENT_AGGREGATE_FALLBACK_REPORTING_JOB_ID);
+ verify(mJobSchedulerMock).cancel(AdServicesConfig.ASYNC_REGISTRATION_QUEUE_JOB_ID);
verify(mJobSchedulerMock).cancel(AdServicesConfig.FLEDGE_BACKGROUND_FETCH_JOB_ID);
verify(mJobSchedulerMock).cancel(AdServicesConfig.CONSENT_NOTIFICATION_JOB_ID);
verify(mJobSchedulerMock).cancel(AdServicesConfig.MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID);
@@ -313,6 +354,7 @@ public class ConsentManagerTest {
verify(mTopicsWorker, times(1)).clearAllTopicsData(any());
// TODO(b/240988406): change to test for correct method call
verify(mAppConsentDao, times(1)).clearAllConsentData();
+ verify(mEnrollmentDao, times(1)).deleteAll();
verify(mMeasurementImpl, times(1)).deleteAllMeasurementData(any());
verify(mCustomAudienceDaoMock).deleteAllCustomAudienceData();
}
@@ -321,7 +363,7 @@ public class ConsentManagerTest {
public void testIsFledgeConsentRevokedForAppWithFullApiConsent()
throws IOException, PackageManager.NameNotFoundException {
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
@@ -338,13 +380,13 @@ public class ConsentManagerTest {
assertFalse(
mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManagerMock, AppConsentDaoFixture.APP10_PACKAGE_NAME));
+ AppConsentDaoFixture.APP10_PACKAGE_NAME));
assertTrue(
mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManagerMock, AppConsentDaoFixture.APP20_PACKAGE_NAME));
+ AppConsentDaoFixture.APP20_PACKAGE_NAME));
assertFalse(
mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManagerMock, AppConsentDaoFixture.APP30_PACKAGE_NAME));
+ AppConsentDaoFixture.APP30_PACKAGE_NAME));
}
@Test
@@ -352,7 +394,7 @@ public class ConsentManagerTest {
throws PackageManager.NameNotFoundException {
doReturn(mPackageManagerMock).when(mContextSpy).getPackageManager();
mConsentManager.disable(mContextSpy);
- assertFalse(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertFalse(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
@@ -363,17 +405,17 @@ public class ConsentManagerTest {
assertTrue(
mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManagerMock, AppConsentDaoFixture.APP10_PACKAGE_NAME));
+ AppConsentDaoFixture.APP10_PACKAGE_NAME));
assertTrue(
mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManagerMock, AppConsentDaoFixture.APP20_PACKAGE_NAME));
+ AppConsentDaoFixture.APP20_PACKAGE_NAME));
}
@Test
public void testIsFledgeConsentRevokedForNotFoundAppThrows()
throws PackageManager.NameNotFoundException {
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doThrow(PackageManager.NameNotFoundException.class)
.when(mPackageManagerMock)
@@ -383,7 +425,6 @@ public class ConsentManagerTest {
IllegalArgumentException.class,
() ->
mConsentManager.isFledgeConsentRevokedForApp(
- mPackageManagerMock,
AppConsentDaoFixture.APP_NOT_FOUND_PACKAGE_NAME));
}
@@ -391,7 +432,7 @@ public class ConsentManagerTest {
public void testIsFledgeConsentRevokedForAppAfterSettingFledgeUseWithFullApiConsent()
throws IOException, PackageManager.NameNotFoundException {
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
@@ -408,13 +449,13 @@ public class ConsentManagerTest {
assertFalse(
mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- mPackageManagerMock, AppConsentDaoFixture.APP10_PACKAGE_NAME));
+ AppConsentDaoFixture.APP10_PACKAGE_NAME));
assertTrue(
mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- mPackageManagerMock, AppConsentDaoFixture.APP20_PACKAGE_NAME));
+ AppConsentDaoFixture.APP20_PACKAGE_NAME));
assertFalse(
mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- mPackageManagerMock, AppConsentDaoFixture.APP30_PACKAGE_NAME));
+ AppConsentDaoFixture.APP30_PACKAGE_NAME));
}
@Test
@@ -422,7 +463,7 @@ public class ConsentManagerTest {
throws PackageManager.NameNotFoundException {
doReturn(mPackageManagerMock).when(mContextSpy).getPackageManager();
mConsentManager.disable(mContextSpy);
- assertFalse(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertFalse(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
@@ -433,17 +474,17 @@ public class ConsentManagerTest {
assertTrue(
mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- mPackageManagerMock, AppConsentDaoFixture.APP10_PACKAGE_NAME));
+ AppConsentDaoFixture.APP10_PACKAGE_NAME));
assertTrue(
mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- mPackageManagerMock, AppConsentDaoFixture.APP20_PACKAGE_NAME));
+ AppConsentDaoFixture.APP20_PACKAGE_NAME));
}
@Test
public void testIsFledgeConsentRevokedForAppAfterSettingFledgeUseThrows()
throws PackageManager.NameNotFoundException {
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doThrow(PackageManager.NameNotFoundException.class)
.when(mPackageManagerMock)
@@ -453,7 +494,6 @@ public class ConsentManagerTest {
IllegalArgumentException.class,
() ->
mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- mPackageManagerMock,
AppConsentDaoFixture.APP_NOT_FOUND_PACKAGE_NAME));
}
@@ -461,7 +501,7 @@ public class ConsentManagerTest {
public void testGetKnownAppsWithConsent()
throws IOException, PackageManager.NameNotFoundException {
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
.getPackageUid(eq(AppConsentDaoFixture.APP10_PACKAGE_NAME), any());
@@ -474,6 +514,20 @@ public class ConsentManagerTest {
mDatastore.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, false);
mDatastore.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, false);
mDatastore.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, false);
+ List<ApplicationInfo> applicationsInstalled =
+ Arrays.asList(
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP10_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP20_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP30_PACKAGE_NAME)
+ .build());
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
ImmutableList<App> knownAppsWithConsent = mConsentManager.getKnownAppsWithConsent();
ImmutableList<App> appsWithRevokedConsent = mConsentManager.getAppsWithRevokedConsent();
@@ -489,7 +543,7 @@ public class ConsentManagerTest {
doNothing().when(mCustomAudienceDaoMock).deleteCustomAudienceDataByOwner(any());
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
.getPackageUid(eq(AppConsentDaoFixture.APP10_PACKAGE_NAME), any());
@@ -502,6 +556,20 @@ public class ConsentManagerTest {
mDatastore.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, false);
mDatastore.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, false);
mDatastore.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, false);
+ List<ApplicationInfo> applicationsInstalled =
+ Arrays.asList(
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP10_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP20_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP30_PACKAGE_NAME)
+ .build());
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
App app = App.create(AppConsentDaoFixture.APP10_PACKAGE_NAME);
// revoke consent for first app
@@ -525,7 +593,7 @@ public class ConsentManagerTest {
doNothing().when(mCustomAudienceDaoMock).deleteCustomAudienceDataByOwner(any());
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
.getPackageUid(eq(AppConsentDaoFixture.APP10_PACKAGE_NAME), any());
@@ -539,6 +607,20 @@ public class ConsentManagerTest {
mDatastore.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, false);
mDatastore.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, false);
App app = App.create(AppConsentDaoFixture.APP10_PACKAGE_NAME);
+ List<ApplicationInfo> applicationsInstalled =
+ Arrays.asList(
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP10_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP20_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP30_PACKAGE_NAME)
+ .build());
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
// revoke consent for first app
mConsentManager.revokeConsentForApp(app);
@@ -604,7 +686,7 @@ public class ConsentManagerTest {
// Prepopulate with consent data for some apps
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
.getPackageUid(eq(AppConsentDaoFixture.APP10_PACKAGE_NAME), any());
@@ -617,6 +699,20 @@ public class ConsentManagerTest {
mDatastore.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, false);
mDatastore.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, true);
mDatastore.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, false);
+ List<ApplicationInfo> applicationsInstalled =
+ Arrays.asList(
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP10_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP20_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP30_PACKAGE_NAME)
+ .build());
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
// Verify population was successful
ImmutableList<App> knownAppsWithConsent = mConsentManager.getKnownAppsWithConsent();
@@ -643,7 +739,7 @@ public class ConsentManagerTest {
// Prepopulate with consent data for some apps
mConsentManager.enable(mContextSpy);
- assertTrue(mConsentManager.getConsent(mPackageManagerMock).isGiven());
+ assertTrue(mConsentManager.getConsent().isGiven());
doReturn(AppConsentDaoFixture.APP10_UID)
.when(mPackageManagerMock)
.getPackageUid(eq(AppConsentDaoFixture.APP10_PACKAGE_NAME), any());
@@ -656,6 +752,20 @@ public class ConsentManagerTest {
mDatastore.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, false);
mDatastore.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, true);
mDatastore.put(AppConsentDaoFixture.APP30_DATASTORE_KEY, false);
+ List<ApplicationInfo> applicationsInstalled =
+ Arrays.asList(
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP10_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP20_PACKAGE_NAME)
+ .build(),
+ ApplicationInfoBuilder.newBuilder()
+ .setPackageName(AppConsentDaoFixture.APP30_PACKAGE_NAME)
+ .build());
+ doReturn(applicationsInstalled)
+ .when(mPackageManagerMock)
+ .getInstalledApplications(any(PackageManager.ApplicationInfoFlags.class));
// Verify population was successful
ImmutableList<App> knownAppsWithConsentBeforeReset =
@@ -688,13 +798,12 @@ public class ConsentManagerTest {
@Test
public void testNotificationDisplayedRecorded() {
- Boolean wasNotificationDisplayed =
- mConsentManager.wasNotificationDisplayed(mPackageManagerMock);
+ Boolean wasNotificationDisplayed = mConsentManager.wasNotificationDisplayed();
assertThat(wasNotificationDisplayed).isFalse();
- mConsentManager.recordNotificationDisplayed(mPackageManagerMock);
- wasNotificationDisplayed = mConsentManager.wasNotificationDisplayed(mPackageManagerMock);
+ mConsentManager.recordNotificationDisplayed();
+ wasNotificationDisplayed = mConsentManager.wasNotificationDisplayed();
assertThat(wasNotificationDisplayed).isTrue();
}
@@ -702,24 +811,34 @@ public class ConsentManagerTest {
@Test
public void testTopicsProxyCalls() {
Topic topic = Topic.create(1, 1, 1);
- List<String> tablesToBlock = List.of(TopicsTables.BlockedTopicsContract.TABLE);
- ConsentManager consentManager =
- new ConsentManager(
- mContextSpy,
+ ArrayList<String> tablesToBlock = new ArrayList<>();
+ tablesToBlock.add(TopicsTables.BlockedTopicsContract.TABLE);
+
+ TopicsWorker topicsWorker =
+ spy(
new TopicsWorker(
mMockEpochManager,
mCacheManager,
mBlockedTopicsManager,
mAppUpdateManager,
- mMockFlags),
+ mMockFlags));
+ // Enable TopicContributors feature
+ when(mMockEpochManager.supportsTopicContributorFeature()).thenReturn(true);
+
+ ConsentManager consentManager =
+ new ConsentManager(
+ mContextSpy,
+ topicsWorker,
mAppConsentDao,
+ mEnrollmentDao,
mMeasurementImpl,
mAdServicesLoggerImpl,
mCustomAudienceDaoMock,
mMockFlags);
doNothing().when(mBlockedTopicsManager).blockTopic(any());
doNothing().when(mBlockedTopicsManager).unblockTopic(any());
- doNothing().when(mCacheManager).clearAllTopicsData(any());
+ // The actual usage is to invoke clearAllTopicsData() from TopicsWorker
+ doNothing().when(topicsWorker).clearAllTopicsData(any());
consentManager.revokeConsentForTopic(topic);
consentManager.restoreConsentForTopic(topic);
@@ -727,13 +846,25 @@ public class ConsentManagerTest {
verify(mBlockedTopicsManager).blockTopic(topic);
verify(mBlockedTopicsManager).unblockTopic(topic);
- verify(mCacheManager).clearAllTopicsData(tablesToBlock);
+ verify(topicsWorker).clearAllTopicsData(tablesToBlock);
}
@Test
- public void testLoggingSettingsUsageReportedOptInSelected() throws IOException {
- mConsentManager.init(mPackageManagerMock);
- mConsentManager.enable(mContextSpy);
+ public void testLoggingSettingsUsageReportedOptInSelectedRow() {
+ ExtendedMockito.doReturn(false)
+ .when(() -> DeviceRegionProvider.isEuDevice(any(Context.class)));
+ ConsentManager temporalConsentManager =
+ new ConsentManager(
+ mContextSpy,
+ mTopicsWorker,
+ mAppConsentDao,
+ mEnrollmentDao,
+ mMeasurementImpl,
+ mAdServicesLoggerImpl,
+ mCustomAudienceDaoMock,
+ mMockFlags);
+
+ temporalConsentManager.enable(mContextSpy);
UIStats expectedUIStats =
new UIStats.Builder()
@@ -745,4 +876,32 @@ public class ConsentManagerTest {
verify(mAdServicesLoggerImpl, times(1)).logUIStats(any());
verify(mAdServicesLoggerImpl, times(1)).logUIStats(expectedUIStats);
}
+
+ @Test
+ public void testLoggingSettingsUsageReportedOptInSelectedEu() {
+ ExtendedMockito.doReturn(true)
+ .when(() -> DeviceRegionProvider.isEuDevice(any(Context.class)));
+ ConsentManager temporalConsentManager =
+ new ConsentManager(
+ mContextSpy,
+ mTopicsWorker,
+ mAppConsentDao,
+ mEnrollmentDao,
+ mMeasurementImpl,
+ mAdServicesLoggerImpl,
+ mCustomAudienceDaoMock,
+ mMockFlags);
+
+ temporalConsentManager.enable(mContextSpy);
+
+ UIStats expectedUIStats =
+ new UIStats.Builder()
+ .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
+ .setRegion(AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU)
+ .setAction(AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__OPT_IN_SELECTED)
+ .build();
+
+ verify(mAdServicesLoggerImpl, times(1)).logUIStats(any());
+ verify(mAdServicesLoggerImpl, times(1)).logUIStats(expectedUIStats);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchJobServiceTest.java
index da8eef4f5..de8015009 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchJobServiceTest.java
@@ -139,8 +139,7 @@ public class BackgroundFetchJobServiceTest {
throws ExecutionException, InterruptedException, TimeoutException {
doReturn(mFlagsWithEnabledBgF).when(FlagsFactory::getFlags);
doReturn(mConsentManagerMock).when(() -> ConsentManager.getInstance(any()));
- doReturn(mPackageManagerMock).when(mBgFJobServiceSpy).getPackageManager();
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManagerMock).getConsent();
doReturn(JOB_SCHEDULER).when(mBgFJobServiceSpy).getSystemService(JobScheduler.class);
doNothing().when(mBgFJobServiceSpy).jobFinished(mJobParametersMock, false);
@@ -192,8 +191,7 @@ public class BackgroundFetchJobServiceTest {
throws ExecutionException, InterruptedException, TimeoutException {
doReturn(mFlagsWithCustomAudienceServiceKillSwitchOff).when(FlagsFactory::getFlags);
doReturn(mConsentManagerMock).when(() -> ConsentManager.getInstance(any()));
- doReturn(mPackageManagerMock).when(mBgFJobServiceSpy).getPackageManager();
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doReturn(mBgFWorkerMock).when(() -> BackgroundFetchWorker.getInstance(any()));
doNothing().when(mBgFWorkerMock).runBackgroundFetch(any());
CountDownLatch jobFinishedCountDown = new CountDownLatch(1);
@@ -222,8 +220,7 @@ public class BackgroundFetchJobServiceTest {
doReturn(mFlagsWithEnabledBgF).when(FlagsFactory::getFlags);
doReturn(mConsentManagerMock).when(() -> ConsentManager.getInstance(any()));
- doReturn(mPackageManagerMock).when(mBgFJobServiceSpy).getPackageManager();
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doReturn(mBgFWorkerMock).when(() -> BackgroundFetchWorker.getInstance(any()));
doNothing().when(mBgFWorkerMock).runBackgroundFetch(any());
doAnswer(
@@ -250,8 +247,7 @@ public class BackgroundFetchJobServiceTest {
doReturn(mFlagsWithEnabledBgF).when(FlagsFactory::getFlags);
doReturn(mConsentManagerMock).when(() -> ConsentManager.getInstance(any()));
- doReturn(mPackageManagerMock).when(mBgFJobServiceSpy).getPackageManager();
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doReturn(mBgFWorkerMock).when(() -> BackgroundFetchWorker.getInstance(any()));
doThrow(TimeoutException.class).when(mBgFWorkerMock).runBackgroundFetch(any());
doAnswer(
@@ -278,8 +274,7 @@ public class BackgroundFetchJobServiceTest {
doReturn(mFlagsWithEnabledBgF).when(FlagsFactory::getFlags);
doReturn(mConsentManagerMock).when(() -> ConsentManager.getInstance(any()));
- doReturn(mPackageManagerMock).when(mBgFJobServiceSpy).getPackageManager();
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doReturn(mBgFWorkerMock).when(() -> BackgroundFetchWorker.getInstance(any()));
doThrow(InterruptedException.class).when(mBgFWorkerMock).runBackgroundFetch(any());
doAnswer(
@@ -306,8 +301,7 @@ public class BackgroundFetchJobServiceTest {
doReturn(mFlagsWithEnabledBgF).when(FlagsFactory::getFlags);
doReturn(mConsentManagerMock).when(() -> ConsentManager.getInstance(any()));
- doReturn(mPackageManagerMock).when(mBgFJobServiceSpy).getPackageManager();
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent(any());
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManagerMock).getConsent();
doReturn(mBgFWorkerMock).when(() -> BackgroundFetchWorker.getInstance(any()));
doThrow(ExecutionException.class).when(mBgFWorkerMock).runBackgroundFetch(any());
doAnswer(
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchRunnerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchRunnerTest.java
index 82db43e52..4024e8905 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchRunnerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchRunnerTest.java
@@ -20,7 +20,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
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.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
@@ -29,13 +28,16 @@ import static org.junit.Assert.assertTrue;
import android.adservices.common.CommonFixture;
import android.adservices.http.MockWebServerRule;
+import android.content.pm.PackageManager;
import android.net.Uri;
import com.android.adservices.LogUtil;
import com.android.adservices.MockWebServerRuleFactory;
import com.android.adservices.customaudience.DBCustomAudienceBackgroundFetchDataFixture;
import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.customaudience.CustomAudienceStats;
import com.android.adservices.data.customaudience.DBCustomAudienceBackgroundFetchData;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -65,6 +67,8 @@ public class BackgroundFetchRunnerTest {
private MockitoSession mStaticMockSession = null;
@Mock private CustomAudienceDao mCustomAudienceDaoMock;
+ @Mock private PackageManager mPackageManagerMock;
+ @Mock private EnrollmentDao mEnrollmentDaoMock;
private BackgroundFetchRunner mBackgroundFetchRunnerSpy;
@Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
@@ -81,8 +85,13 @@ public class BackgroundFetchRunnerTest {
.initMocks(this)
.startMocking();
- mBackgroundFetchRunnerSpy = new BackgroundFetchRunner(mCustomAudienceDaoMock, mFlags);
- spyOn(mBackgroundFetchRunnerSpy);
+ mBackgroundFetchRunnerSpy =
+ ExtendedMockito.spy(
+ new BackgroundFetchRunner(
+ mCustomAudienceDaoMock,
+ mPackageManagerMock,
+ mEnrollmentDaoMock,
+ mFlags));
mFetchUri = mMockWebServerRule.uriForPath(mFetchPath);
}
@@ -95,16 +104,74 @@ public class BackgroundFetchRunnerTest {
}
@Test
+ public void testDeleteExpiredCustomAudiences() {
+ mBackgroundFetchRunnerSpy.deleteExpiredCustomAudiences(CommonFixture.FIXED_NOW);
+
+ verify(mCustomAudienceDaoMock).deleteAllExpiredCustomAudienceData(CommonFixture.FIXED_NOW);
+ }
+
+ @Test
+ public void testDeleteDisallowedOwnerCustomAudiences() {
+ doReturn(
+ CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(10)
+ .setTotalOwnerCount(2)
+ .build())
+ .when(mCustomAudienceDaoMock)
+ .deleteAllDisallowedOwnerCustomAudienceData(any(), any());
+
+ mBackgroundFetchRunnerSpy.deleteDisallowedOwnerCustomAudiences();
+
+ verify(mCustomAudienceDaoMock)
+ .deleteAllDisallowedOwnerCustomAudienceData(mPackageManagerMock, mFlags);
+ }
+
+ @Test
+ public void testDeleteDisallowedBuyerCustomAudiences() {
+ doReturn(
+ CustomAudienceStats.builder()
+ .setTotalCustomAudienceCount(12)
+ .setTotalBuyerCount(3)
+ .build())
+ .when(mCustomAudienceDaoMock)
+ .deleteAllDisallowedBuyerCustomAudienceData(any(), any());
+
+ mBackgroundFetchRunnerSpy.deleteDisallowedBuyerCustomAudiences();
+
+ verify(mCustomAudienceDaoMock)
+ .deleteAllDisallowedBuyerCustomAudienceData(mEnrollmentDaoMock, mFlags);
+ }
+
+ @Test
public void testBackgroundFetchRunnerNullInputsCauseFailure() {
- assertThrows(NullPointerException.class, () -> new BackgroundFetchRunner(null, mFlags));
assertThrows(
NullPointerException.class,
- () -> new BackgroundFetchRunner(mCustomAudienceDaoMock, null));
+ () ->
+ new BackgroundFetchRunner(
+ null, mPackageManagerMock, mEnrollmentDaoMock, mFlags));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ new BackgroundFetchRunner(
+ mCustomAudienceDaoMock, null, mEnrollmentDaoMock, mFlags));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ new BackgroundFetchRunner(
+ mCustomAudienceDaoMock, mPackageManagerMock, null, mFlags));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ new BackgroundFetchRunner(
+ mCustomAudienceDaoMock,
+ mPackageManagerMock,
+ mEnrollmentDaoMock,
+ null));
}
@Test
public void testUpdateCustomAudienceWithEmptyUpdate() {
- doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+ doReturn(mFlags).when(FlagsFactory::getFlags);
CustomAudienceUpdatableData updatableData =
CustomAudienceUpdatableDataFixture.getValidBuilderEmptySuccessfulResponse().build();
@@ -226,7 +293,11 @@ public class BackgroundFetchRunnerTest {
}
BackgroundFetchRunner runnerWithSmallLimits =
- new BackgroundFetchRunner(mCustomAudienceDaoMock, new FlagsWithSmallLimits());
+ new BackgroundFetchRunner(
+ mCustomAudienceDaoMock,
+ mPackageManagerMock,
+ mEnrollmentDaoMock,
+ new FlagsWithSmallLimits());
CountDownLatch responseLatch = new CountDownLatch(1);
MockWebServer mockWebServer =
@@ -301,7 +372,11 @@ public class BackgroundFetchRunnerTest {
}
BackgroundFetchRunner runnerWithSmallLimits =
- new BackgroundFetchRunner(mCustomAudienceDaoMock, new FlagsWithSmallLimits());
+ new BackgroundFetchRunner(
+ mCustomAudienceDaoMock,
+ mPackageManagerMock,
+ mEnrollmentDaoMock,
+ new FlagsWithSmallLimits());
MockWebServer mockWebServer =
mMockWebServerRule.startMockWebServer(
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchWorkerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchWorkerTest.java
index 99d87cd12..62dae9b7f 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchWorkerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/BackgroundFetchWorkerTest.java
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.verify;
import android.adservices.common.CommonFixture;
import android.annotation.NonNull;
import android.content.Context;
+import android.content.pm.PackageManager;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
@@ -39,13 +40,15 @@ import com.android.adservices.customaudience.DBCustomAudienceBackgroundFetchData
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.CustomAudienceDatabase;
import com.android.adservices.data.customaudience.DBCustomAudienceBackgroundFetchData;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.Spy;
+import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -74,20 +77,29 @@ public class BackgroundFetchWorkerTest {
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Spy
- private CustomAudienceDao mCustomAudienceDaoSpy =
- Room.inMemoryDatabaseBuilder(CONTEXT, CustomAudienceDatabase.class)
- .build()
- .customAudienceDao();
-
- @Spy
- private BackgroundFetchRunner mBackgroundFetchRunnerSpy =
- new BackgroundFetchRunner(mCustomAudienceDaoSpy, mFlags);
+ @Mock private PackageManager mPackageManagerMock;
+ @Mock private EnrollmentDao mEnrollmentDaoMock;
+ private CustomAudienceDao mCustomAudienceDaoSpy;
+ private BackgroundFetchRunner mBackgroundFetchRunnerSpy;
private BackgroundFetchWorker mBackgroundFetchWorker;
@Before
public void setup() {
+ mCustomAudienceDaoSpy =
+ Mockito.spy(
+ Room.inMemoryDatabaseBuilder(CONTEXT, CustomAudienceDatabase.class)
+ .build()
+ .customAudienceDao());
+
+ mBackgroundFetchRunnerSpy =
+ Mockito.spy(
+ new BackgroundFetchRunner(
+ mCustomAudienceDaoSpy,
+ mPackageManagerMock,
+ mEnrollmentDaoMock,
+ mFlags));
+
mBackgroundFetchWorker =
new BackgroundFetchWorker(mCustomAudienceDaoSpy, mFlags, mBackgroundFetchRunnerSpy);
}
@@ -96,24 +108,21 @@ public class BackgroundFetchWorkerTest {
public void testBackgroundFetchWorkerNullInputsCauseFailure() {
assertThrows(
NullPointerException.class,
- () -> {
- new BackgroundFetchWorker(
- null, FlagsFactory.getFlagsForTest(), mBackgroundFetchRunnerSpy);
- });
+ () ->
+ new BackgroundFetchWorker(
+ null, FlagsFactory.getFlagsForTest(), mBackgroundFetchRunnerSpy));
assertThrows(
NullPointerException.class,
- () -> {
- new BackgroundFetchWorker(
- mCustomAudienceDaoSpy, null, mBackgroundFetchRunnerSpy);
- });
+ () ->
+ new BackgroundFetchWorker(
+ mCustomAudienceDaoSpy, null, mBackgroundFetchRunnerSpy));
assertThrows(
NullPointerException.class,
- () -> {
- new BackgroundFetchWorker(
- mCustomAudienceDaoSpy, FlagsFactory.getFlagsForTest(), null);
- });
+ () ->
+ new BackgroundFetchWorker(
+ mCustomAudienceDaoSpy, FlagsFactory.getFlagsForTest(), null));
}
@Test
@@ -145,7 +154,7 @@ public class BackgroundFetchWorkerTest {
class BackgroundFetchRunnerWithSleep extends BackgroundFetchRunner {
BackgroundFetchRunnerWithSleep(
@NonNull CustomAudienceDao customAudienceDao, @NonNull Flags flags) {
- super(customAudienceDao, flags);
+ super(customAudienceDao, mPackageManagerMock, mEnrollmentDaoMock, flags);
}
@Override
@@ -201,6 +210,12 @@ public class BackgroundFetchWorkerTest {
mBackgroundFetchWorker.runBackgroundFetch(CommonFixture.FIXED_NOW);
+ verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
+ verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
+ verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
+ verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
+ verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
+ verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, never()).updateCustomAudience(any(), any());
}
@@ -220,6 +235,12 @@ public class BackgroundFetchWorkerTest {
mBackgroundFetchWorker.runBackgroundFetch(CommonFixture.FIXED_NOW);
+ verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
+ verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
+ verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
+ verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
+ verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
+ verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy).updateCustomAudience(any(), any());
}
@@ -245,6 +266,12 @@ public class BackgroundFetchWorkerTest {
mBackgroundFetchWorker.runBackgroundFetch(CommonFixture.FIXED_NOW);
+ verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
+ verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
+ verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
+ verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
+ verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
+ verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, times(numEligibleCustomAudiences))
.updateCustomAudience(any(), any());
}
@@ -268,7 +295,6 @@ public class BackgroundFetchWorkerTest {
doReturn(fetchDataList)
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
- doNothing().when(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
doAnswer(
unusedInvocation -> {
Thread.sleep(100);
@@ -298,6 +324,11 @@ public class BackgroundFetchWorkerTest {
bgfWorkStoppedLatch.await();
verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
+ verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
+ verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
+ verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
+ verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
+ verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, times(numEligibleCustomAudiences))
.updateCustomAudience(any(), any());
}
@@ -326,7 +357,6 @@ public class BackgroundFetchWorkerTest {
doReturn(fetchDataList)
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
- doNothing().when(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
doAnswer(
unusedInvocation -> {
Thread.sleep(100);
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
index 203435068..e7742a4b7 100644
--- 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
@@ -23,6 +23,7 @@ import android.adservices.common.CommonFixture;
import android.adservices.customaudience.CustomAudienceFixture;
import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.data.customaudience.CustomAudienceStats;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
@@ -72,11 +73,13 @@ public class CustomAudienceQuantityCheckerTest {
public void testExistOwnerAndOwnerReachMax_success() {
when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
.thenReturn(
- new CustomAudienceDao.CustomAudienceStats(
- CustomAudienceFixture.VALID_OWNER,
- 20L,
- 1L,
- FLAGS.getFledgeCustomAudienceMaxOwnerCount()));
+ CustomAudienceStats.builder()
+ .setOwner(CustomAudienceFixture.VALID_OWNER)
+ .setTotalCustomAudienceCount(20L)
+ .setPerOwnerCustomAudienceCount(1L)
+ .setTotalOwnerCount(FLAGS.getFledgeCustomAudienceMaxOwnerCount())
+ .build());
+
mChecker.check(
CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER_1).build(),
CustomAudienceFixture.VALID_OWNER);
@@ -88,11 +91,12 @@ public class CustomAudienceQuantityCheckerTest {
public void testOwnerExceedMax() {
when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
.thenReturn(
- new CustomAudienceDao.CustomAudienceStats(
- CustomAudienceFixture.VALID_OWNER,
- 20L,
- 0L,
- FLAGS.getFledgeCustomAudienceMaxOwnerCount()));
+ CustomAudienceStats.builder()
+ .setOwner(CustomAudienceFixture.VALID_OWNER)
+ .setTotalCustomAudienceCount(20L)
+ .setPerOwnerCustomAudienceCount(0L)
+ .setTotalOwnerCount(FLAGS.getFledgeCustomAudienceMaxOwnerCount())
+ .build());
assertViolations(
assertThrows(
@@ -114,11 +118,14 @@ public class CustomAudienceQuantityCheckerTest {
public void testTotalCountExceedMax() {
when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
.thenReturn(
- new CustomAudienceDao.CustomAudienceStats(
- CustomAudienceFixture.VALID_OWNER,
- FLAGS.getFledgeCustomAudienceMaxCount(),
- 0L,
- 1L));
+ CustomAudienceStats.builder()
+ .setOwner(CustomAudienceFixture.VALID_OWNER)
+ .setTotalCustomAudienceCount(
+ FLAGS.getFledgeCustomAudienceMaxCount())
+ .setPerOwnerCustomAudienceCount(0L)
+ .setTotalOwnerCount(1L)
+ .build());
+
assertViolations(
assertThrows(
IllegalArgumentException.class,
@@ -138,11 +145,14 @@ public class CustomAudienceQuantityCheckerTest {
public void testPerOwnerCountExceedMax() {
when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
.thenReturn(
- new CustomAudienceDao.CustomAudienceStats(
- CustomAudienceFixture.VALID_OWNER,
- 20L,
- FLAGS.getFledgeCustomAudiencePerAppMaxCount(),
- 1L));
+ CustomAudienceStats.builder()
+ .setOwner(CustomAudienceFixture.VALID_OWNER)
+ .setTotalCustomAudienceCount(20L)
+ .setPerOwnerCustomAudienceCount(
+ FLAGS.getFledgeCustomAudiencePerAppMaxCount())
+ .setTotalOwnerCount(1L)
+ .build());
+
assertViolations(
assertThrows(
IllegalArgumentException.class,
@@ -163,8 +173,12 @@ public class CustomAudienceQuantityCheckerTest {
public void testAllGood() {
when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
.thenReturn(
- new CustomAudienceDao.CustomAudienceStats(
- CustomAudienceFixture.VALID_OWNER, 0L, 0L, 0L));
+ CustomAudienceStats.builder()
+ .setOwner(CustomAudienceFixture.VALID_OWNER)
+ .setTotalCustomAudienceCount(0L)
+ .setPerOwnerCustomAudienceCount(0L)
+ .setTotalOwnerCount(0L)
+ .build());
mChecker.check(
CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER_1).build(),
CustomAudienceFixture.VALID_OWNER);
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 04547cb85..75e0e8b2f 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
@@ -79,6 +79,7 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.concurrent.CountDownLatch;
import java.util.function.Supplier;
@@ -141,9 +142,12 @@ public class CustomAudienceServiceEndToEndTest {
ExtendedMockito.mockitoSession()
.spyStatic(FlagsFactory.class)
.mockStatic(BackgroundFetchJobService.class)
+ .strictness(Strictness.WARN)
.initMocks(this)
.startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+
mCustomAudienceDao =
Room.inMemoryDatabaseBuilder(CONTEXT, CustomAudienceDatabase.class)
.build()
@@ -266,7 +270,7 @@ public class CustomAudienceServiceEndToEndTest {
.when(() -> BackgroundFetchJobService.scheduleIfNeeded(any(), any(), anyBoolean()));
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
ResultCapturingCallback callback = new ResultCapturingCallback();
mService.joinCustomAudience(
@@ -297,7 +301,7 @@ public class CustomAudienceServiceEndToEndTest {
public void testJoinCustomAudienceWithRevokedUserConsentForAppSuccess() {
doReturn(true)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
ResultCapturingCallback callback = new ResultCapturingCallback();
mService.joinCustomAudience(
@@ -314,7 +318,7 @@ public class CustomAudienceServiceEndToEndTest {
public void testJoinCustomAudience_beyondMaxExpirationTime_fail() {
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
ResultCapturingCallback callback = new ResultCapturingCallback();
mService.joinCustomAudience(
@@ -394,10 +398,10 @@ public class CustomAudienceServiceEndToEndTest {
doReturn(CommonFixture.FLAGS_FOR_TEST).when(FlagsFactory::getFlags);
doNothing()
.when(() -> BackgroundFetchJobService.scheduleIfNeeded(any(), any(), anyBoolean()));
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
ResultCapturingCallback callback = new ResultCapturingCallback();
mService.joinCustomAudience(
@@ -429,10 +433,10 @@ public class CustomAudienceServiceEndToEndTest {
@Test
public void testLeaveCustomAudienceWithRevokedUserConsentForAppSuccess() {
doReturn(CommonFixture.FLAGS_FOR_TEST).when(FlagsFactory::getFlags);
- doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
ResultCapturingCallback callback = new ResultCapturingCallback();
mService.joinCustomAudience(
@@ -462,7 +466,7 @@ public class CustomAudienceServiceEndToEndTest {
@Test
public void testLeaveCustomAudience_leaveNotJoinedCustomAudience_doesNotFail() {
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
ResultCapturingCallback callback = new ResultCapturingCallback();
mService.leaveCustomAudience(
@@ -486,7 +490,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
CustomAudienceOverrideTestCallback callback =
callAddOverride(
@@ -512,7 +516,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
- doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
CustomAudienceOverrideTestCallback callback =
callAddOverride(
@@ -538,7 +542,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
String otherOwner = "otherOwner";
@@ -585,7 +589,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
DBCustomAudienceOverride dbCustomAudienceOverride =
DBCustomAudienceOverride.builder()
@@ -620,7 +624,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
- doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
DBCustomAudienceOverride dbCustomAudienceOverride =
DBCustomAudienceOverride.builder()
@@ -657,7 +661,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(incorrectPackageName)
.build());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
DBCustomAudienceOverride dbCustomAudienceOverride =
DBCustomAudienceOverride.builder()
@@ -721,7 +725,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
DBCustomAudienceOverride dbCustomAudienceOverride1 =
DBCustomAudienceOverride.builder()
@@ -773,7 +777,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(MY_APP_PACKAGE_NAME)
.build());
- doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
DBCustomAudienceOverride dbCustomAudienceOverride1 =
DBCustomAudienceOverride.builder()
@@ -827,7 +831,7 @@ public class CustomAudienceServiceEndToEndTest {
.setDevOptionsEnabled(true)
.setCallingAppPackageName(incorrectPackageName)
.build());
- doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(false).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
DBCustomAudienceOverride dbCustomAudienceOverride1 =
DBCustomAudienceOverride.builder()
@@ -922,7 +926,7 @@ public class CustomAudienceServiceEndToEndTest {
.when(() -> BackgroundFetchJobService.scheduleIfNeeded(any(), any(), anyBoolean()));
doReturn(false)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
CustomAudienceQuantityChecker customAudienceQuantityChecker =
new CustomAudienceQuantityChecker(mCustomAudienceDao, CommonFixture.FLAGS_FOR_TEST);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceImplTest.java
index 32651128f..4dcbef72b 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceImplTest.java
@@ -36,7 +36,6 @@ import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICE
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__OVERRIDE_CUSTOM_AUDIENCE_REMOTE_INFO;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__REMOVE_CUSTOM_AUDIENCE_REMOTE_INFO_OVERRIDE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__RESET_ALL_CUSTOM_AUDIENCE_OVERRIDES;
-import static com.android.adservices.stats.FledgeApiCallStatsMatcher.aCallStatForFledgeApiWithStatus;
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;
@@ -82,6 +81,7 @@ import com.android.adservices.service.devapi.DevContext;
import com.android.adservices.service.devapi.DevContextFilter;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.google.common.util.concurrent.MoreExecutors;
@@ -92,7 +92,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
-import org.mockito.Spy;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
@@ -113,7 +112,8 @@ public class CustomAudienceServiceImplTest {
@Mock private AppImportanceFilter mAppImportanceFilter;
@Mock private CustomAudienceDao mCustomAudienceDao;
@Mock DevContextFilter mDevContextFilter;
- @Spy private final AdServicesLogger mAdServicesLoggerSpy = AdServicesLoggerImpl.getInstance();
+ private final AdServicesLogger mAdServicesLoggerMock =
+ ExtendedMockito.mock(AdServicesLoggerImpl.class);
@Mock private Throttler mMockThrottler;
private Supplier<Throttler> mThrottlerSupplier = () -> mMockThrottler;
@@ -144,7 +144,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithAllCheckEnabled,
mThrottlerSupplier,
@@ -171,7 +171,7 @@ public class CustomAudienceServiceImplTest {
mDevContextFilter,
mAppImportanceFilter,
mConsentManagerMock,
- mAdServicesLoggerSpy);
+ mAdServicesLoggerMock);
mStaticMockitoSession.finishMocking();
}
@@ -206,20 +206,20 @@ public class CustomAudienceServiceImplTest {
CustomAudienceFixture.VALID_OWNER,
CommonFixture.VALID_BUYER_1,
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock).isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ verify(mConsentManagerMock).isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
verify(mFledgeAllowListsFilter)
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verifyLoggerSpy(AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_SUCCESS);
+ verifyLoggerMock(AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_SUCCESS);
}
@Test
public void testJoinCustomAudienceWithRevokedUserConsentSuccess() throws RemoteException {
doReturn(true)
.when(mConsentManagerMock)
- .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
mService.joinCustomAudience(
VALID_CUSTOM_AUDIENCE, CustomAudienceFixture.VALID_OWNER, mICustomAudienceCallback);
@@ -238,7 +238,7 @@ public class CustomAudienceServiceImplTest {
CustomAudienceFixture.VALID_OWNER,
CommonFixture.VALID_BUYER_1,
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock).isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ verify(mConsentManagerMock).isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
verify(mFledgeAllowListsFilter)
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
@@ -249,7 +249,7 @@ public class CustomAudienceServiceImplTest {
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE,
null);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE,
STATUS_USER_CONSENT_REVOKED);
}
@@ -265,7 +265,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithAllCheckEnabled,
mThrottlerSupplier,
@@ -282,7 +282,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
CONTEXT, AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_INTERNAL_ERROR);
}
@@ -321,7 +321,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
CONTEXT, AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_INVALID_ARGUMENT);
}
@@ -336,7 +336,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
CONTEXT, AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_INVALID_ARGUMENT);
}
@@ -351,7 +351,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
CONTEXT, AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_INVALID_ARGUMENT);
}
@@ -371,7 +371,7 @@ public class CustomAudienceServiceImplTest {
verify(mCustomAudienceImpl)
.joinCustomAudience(VALID_CUSTOM_AUDIENCE, CustomAudienceFixture.VALID_OWNER);
verifyErrorResponseICustomAudienceCallback(STATUS_INTERNAL_ERROR, errorMessage);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_INTERNAL_ERROR);
verify(mFledgeAuthorizationFilter)
.assertCallingPackageName(
@@ -391,13 +391,13 @@ public class CustomAudienceServiceImplTest {
null);
verify(mConsentManagerMock)
.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ CustomAudienceFixture.VALID_OWNER);
verify(mFledgeAllowListsFilter)
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_INTERNAL_ERROR);
}
@@ -437,8 +437,8 @@ public class CustomAudienceServiceImplTest {
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
verify(mConsentManagerMock)
.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
- verifyLoggerSpy(
+ CustomAudienceFixture.VALID_OWNER);
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_INTERNAL_ERROR);
}
@@ -479,16 +479,14 @@ public class CustomAudienceServiceImplTest {
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
- verifyLoggerSpy(AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_SUCCESS);
+ verifyLoggerMock(AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_SUCCESS);
}
@Test
public void testLeaveCustomAudienceWithRevokedUserConsent() throws RemoteException {
- doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any(), any());
+ doReturn(true).when(mConsentManagerMock).isFledgeConsentRevokedForApp(any());
mService.leaveCustomAudience(
CustomAudienceFixture.VALID_OWNER,
@@ -515,16 +513,14 @@ public class CustomAudienceServiceImplTest {
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
verify(mAppImportanceFilter)
.assertCallerIsInForeground(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE,
null);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE,
STATUS_USER_CONSENT_REVOKED);
}
@@ -540,7 +536,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithAllCheckEnabled,
mThrottlerSupplier,
@@ -558,7 +554,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
any(), eq(AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE));
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_INTERNAL_ERROR);
}
@@ -604,7 +600,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
CONTEXT, AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_INVALID_ARGUMENT);
}
@@ -623,7 +619,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
CONTEXT, AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_INVALID_ARGUMENT);
}
@@ -642,7 +638,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
CONTEXT, AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_INVALID_ARGUMENT);
}
@@ -661,7 +657,7 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter)
.assertAppDeclaredPermission(
CONTEXT, AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_INVALID_ARGUMENT);
}
@@ -709,11 +705,9 @@ public class CustomAudienceServiceImplTest {
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_INTERNAL_ERROR);
}
@@ -756,10 +750,8 @@ public class CustomAudienceServiceImplTest {
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
- verifyLoggerSpy(
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_INTERNAL_ERROR);
}
@@ -805,7 +797,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithForegroundCheckDisabled,
mThrottlerSupplier,
@@ -833,12 +825,12 @@ public class CustomAudienceServiceImplTest {
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
verify(mConsentManagerMock)
.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ CustomAudienceFixture.VALID_OWNER);
verify(mCustomAudienceImpl)
.joinCustomAudience(VALID_CUSTOM_AUDIENCE, CustomAudienceFixture.VALID_OWNER);
verify(() -> BackgroundFetchJobService.scheduleIfNeeded(any(), any(), eq(false)));
verify(mICustomAudienceCallback).onSuccess();
- verifyLoggerSpy(AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_SUCCESS);
+ verifyLoggerMock(AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_SUCCESS);
}
@Test
@@ -887,7 +879,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithForegroundCheckDisabled,
mThrottlerSupplier,
@@ -917,16 +909,14 @@ public class CustomAudienceServiceImplTest {
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
verify(mCustomAudienceImpl)
.leaveCustomAudience(
CustomAudienceFixture.VALID_OWNER,
CommonFixture.VALID_BUYER_1,
CustomAudienceFixture.VALID_NAME);
verify(mICustomAudienceCallback).onSuccess();
- verifyLoggerSpy(AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_SUCCESS);
+ verifyLoggerMock(AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_SUCCESS);
}
@Test
@@ -968,7 +958,7 @@ public class CustomAudienceServiceImplTest {
verifyErrorResponseCustomAudienceOverrideCallback(
AdServicesStatusUtils.STATUS_BACKGROUND_CALLER,
ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE);
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__OVERRIDE_CUSTOM_AUDIENCE_REMOTE_INFO,
STATUS_BACKGROUND_CALLER);
}
@@ -992,7 +982,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithForegroundCheckDisabled,
mThrottlerSupplier,
@@ -1012,9 +1002,7 @@ public class CustomAudienceServiceImplTest {
AD_SERVICES_API_CALLED__API_NAME__OVERRIDE_CUSTOM_AUDIENCE_REMOTE_INFO);
verify(mDevContextFilter).createDevContext();
verify(mCustomAudienceImpl).getCustomAudienceDao();
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
verify(mCustomAudienceDao)
.persistCustomAudienceOverride(
DBCustomAudienceOverride.builder()
@@ -1026,7 +1014,7 @@ public class CustomAudienceServiceImplTest {
.setAppPackageName(CustomAudienceFixture.VALID_OWNER)
.build());
verify(mCustomAudienceOverrideCallback).onSuccess();
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__OVERRIDE_CUSTOM_AUDIENCE_REMOTE_INFO,
STATUS_SUCCESS);
}
@@ -1059,7 +1047,7 @@ public class CustomAudienceServiceImplTest {
.assertCallerIsInForeground(CustomAudienceFixture.VALID_OWNER, apiName, null);
verifyErrorResponseCustomAudienceOverrideCallback(
STATUS_BACKGROUND_CALLER, ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE);
- verifyLoggerSpy(apiName, STATUS_BACKGROUND_CALLER);
+ verifyLoggerMock(apiName, STATUS_BACKGROUND_CALLER);
}
@Test
@@ -1082,7 +1070,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithForegroundCheckDisabled,
mThrottlerSupplier,
@@ -1097,15 +1085,13 @@ public class CustomAudienceServiceImplTest {
verify(mFledgeAuthorizationFilter).assertAppDeclaredPermission(CONTEXT, apiName);
verify(mDevContextFilter).createDevContext();
verify(mCustomAudienceImpl).getCustomAudienceDao();
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
verify(mCustomAudienceDao)
.removeCustomAudienceOverrideByPrimaryKeyAndPackageName(
CustomAudienceFixture.VALID_OWNER, CommonFixture.VALID_BUYER_1,
CustomAudienceFixture.VALID_NAME, CustomAudienceFixture.VALID_OWNER);
verify(mCustomAudienceOverrideCallback).onSuccess();
- verifyLoggerSpy(apiName, STATUS_SUCCESS);
+ verifyLoggerMock(apiName, STATUS_SUCCESS);
}
@Test
@@ -1128,7 +1114,7 @@ public class CustomAudienceServiceImplTest {
verify(mDevContextFilter).createDevContext();
verify(mCustomAudienceImpl).getCustomAudienceDao();
verify(mAppImportanceFilter).assertCallerIsInForeground(Process.myUid(), apiName, null);
- verifyLoggerSpy(apiName, STATUS_BACKGROUND_CALLER);
+ verifyLoggerMock(apiName, STATUS_BACKGROUND_CALLER);
verifyErrorResponseCustomAudienceOverrideCallback(
STATUS_BACKGROUND_CALLER, ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE);
}
@@ -1152,7 +1138,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithForegroundCheckDisabled,
mThrottlerSupplier,
@@ -1166,13 +1152,11 @@ public class CustomAudienceServiceImplTest {
AD_SERVICES_API_CALLED__API_NAME__RESET_ALL_CUSTOM_AUDIENCE_OVERRIDES);
verify(mDevContextFilter).createDevContext();
verify(mCustomAudienceImpl).getCustomAudienceDao();
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
verify(mCustomAudienceDao)
.removeCustomAudienceOverridesByPackageName(CustomAudienceFixture.VALID_OWNER);
verify(mCustomAudienceOverrideCallback).onSuccess();
- verifyLoggerSpy(
+ verifyLoggerMock(
AD_SERVICES_API_CALLED__API_NAME__RESET_ALL_CUSTOM_AUDIENCE_OVERRIDES,
STATUS_SUCCESS);
}
@@ -1311,7 +1295,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithEnrollmentCheckDisabled,
mThrottlerSupplier,
@@ -1340,9 +1324,9 @@ public class CustomAudienceServiceImplTest {
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock).isFledgeConsentRevokedForAppAfterSettingFledgeUse(any(), any());
+ verify(mConsentManagerMock).isFledgeConsentRevokedForAppAfterSettingFledgeUse(any());
- verifyLoggerSpy(AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_SUCCESS);
+ verifyLoggerMock(AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, STATUS_SUCCESS);
}
@Test
@@ -1398,7 +1382,7 @@ public class CustomAudienceServiceImplTest {
mConsentManagerMock,
mDevContextFilter,
DIRECT_EXECUTOR,
- mAdServicesLoggerSpy,
+ mAdServicesLoggerMock,
mAppImportanceFilter,
mFlagsWithEnrollmentCheckDisabled,
mThrottlerSupplier,
@@ -1433,11 +1417,9 @@ public class CustomAudienceServiceImplTest {
.assertAppCanUsePpapi(
CustomAudienceFixture.VALID_OWNER,
AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE);
- verify(mConsentManagerMock)
- .isFledgeConsentRevokedForApp(
- CONTEXT.getPackageManager(), CustomAudienceFixture.VALID_OWNER);
+ verify(mConsentManagerMock).isFledgeConsentRevokedForApp(CustomAudienceFixture.VALID_OWNER);
- verifyLoggerSpy(AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_SUCCESS);
+ verifyLoggerMock(AD_SERVICES_API_CALLED__API_NAME__LEAVE_CUSTOM_AUDIENCE, STATUS_SUCCESS);
}
@Test
@@ -1590,10 +1572,8 @@ public class CustomAudienceServiceImplTest {
assertEquals(errorMessage, errorCaptor.getValue().getErrorMessage());
}
- private void verifyLoggerSpy(int apiName, int statusCode) {
- verify(mAdServicesLoggerSpy).logFledgeApiCallStats(apiName, statusCode);
- verify(mAdServicesLoggerSpy)
- .logApiCallStats(aCallStatForFledgeApiWithStatus(apiName, statusCode));
+ private void verifyLoggerMock(int apiName, int statusCode) {
+ verify(mAdServicesLoggerMock).logFledgeApiCallStats(eq(apiName), eq(statusCode), anyInt());
}
private static class FlagsWithCheckEnabledSwitch implements Flags {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/enrollment/EnrollmentDataTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/enrollment/EnrollmentDataTest.java
index 624c4fba3..e5cbbe96f 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/enrollment/EnrollmentDataTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/enrollment/EnrollmentDataTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.enrollment;
+import static com.android.adservices.service.enrollment.EnrollmentData.SEPARATOR;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -24,48 +26,114 @@ import static org.junit.Assert.assertNull;
import androidx.test.filters.SmallTest;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Test;
import java.util.Arrays;
+import java.util.List;
/** Unit tests for {@link EnrollmentData} */
@SmallTest
public final class EnrollmentDataTest {
+ private static final String ENROLLMENT_ID = "1";
+ private static final String COMPANY_ID = "100";
+ private static final ImmutableList<String> SDK_NAMES = ImmutableList.of("Admob");
+ private static final ImmutableList<String> ATTRIBUTION_SOURCE_REGISTRATION_URLS =
+ ImmutableList.of("source1.example.com", "source2.example.com");
+ private static final ImmutableList<String> ATTRIBUTION_TRIGGER_REGISTRATION_URLS =
+ ImmutableList.of("trigger1.example.com", "trigger2.example.com");
+ private static final ImmutableList<String> ATTRIBUTION_REPORTING_REGISTRATION_URLS =
+ ImmutableList.of("reporting1.example.com", "reporting2.example.com");
+ private static final ImmutableList<String> REMARKETING_RESPONSE_BASED_REGISTRATION_URLS =
+ ImmutableList.of("remarketing1.example.com", "remarketing2.example.com");
+ private static final ImmutableList<String> ENCRYPTION_KEY_URLS =
+ ImmutableList.of("encryption1.example.com", "encryption2.example.com");
private EnrollmentData createEnrollmentData() {
return new EnrollmentData.Builder()
- .setEnrollmentId("1")
- .setCompanyId("100")
- .setSdkNames(Arrays.asList("Admob"))
- .setAttributionSourceRegistrationUrl(
- Arrays.asList("source1.example.com", "source2.example.com"))
- .setAttributionTriggerRegistrationUrl(
- Arrays.asList("trigger1.example.com", "trigger2.example.com"))
- .setAttributionReportingUrl(
- Arrays.asList("reporting1.example.com", "reporting2.example.com"))
+ .setEnrollmentId(ENROLLMENT_ID)
+ .setCompanyId(COMPANY_ID)
+ .setSdkNames(SDK_NAMES)
+ .setAttributionSourceRegistrationUrl(ATTRIBUTION_SOURCE_REGISTRATION_URLS)
+ .setAttributionTriggerRegistrationUrl(ATTRIBUTION_TRIGGER_REGISTRATION_URLS)
+ .setAttributionReportingUrl(ATTRIBUTION_REPORTING_REGISTRATION_URLS)
.setRemarketingResponseBasedRegistrationUrl(
- Arrays.asList("remarketing1.example.com", "remarketing2.example.com"))
- .setEncryptionKeyUrl(
- Arrays.asList("encryption1.example.com", "encryption2.example.com"))
+ REMARKETING_RESPONSE_BASED_REGISTRATION_URLS)
+ .setEncryptionKeyUrl(ENCRYPTION_KEY_URLS)
.build();
}
@Test
public void testCreation() throws Exception {
EnrollmentData enrollmentData = createEnrollmentData();
- assertEquals("1", enrollmentData.getEnrollmentId());
- assertEquals("100", enrollmentData.getCompanyId());
- assertThat(enrollmentData.getSdkNames()).containsExactly("Admob");
+
+ assertEquals(ENROLLMENT_ID, enrollmentData.getEnrollmentId());
+ assertEquals(COMPANY_ID, enrollmentData.getCompanyId());
+ assertThat(enrollmentData.getSdkNames()).containsExactlyElementsIn(SDK_NAMES);
+ assertThat(enrollmentData.getAttributionSourceRegistrationUrl())
+ .containsExactlyElementsIn(ATTRIBUTION_SOURCE_REGISTRATION_URLS);
+ assertThat(enrollmentData.getAttributionTriggerRegistrationUrl())
+ .containsExactlyElementsIn(ATTRIBUTION_TRIGGER_REGISTRATION_URLS);
+ assertThat(enrollmentData.getAttributionReportingUrl())
+ .containsExactlyElementsIn(ATTRIBUTION_REPORTING_REGISTRATION_URLS);
+ assertThat(enrollmentData.getRemarketingResponseBasedRegistrationUrl())
+ .containsExactlyElementsIn(REMARKETING_RESPONSE_BASED_REGISTRATION_URLS);
+ assertThat(enrollmentData.getEncryptionKeyUrl())
+ .containsExactlyElementsIn(ENCRYPTION_KEY_URLS);
+ }
+
+ @Test
+ public void testCreationFromStrings() {
+ EnrollmentData enrollmentData =
+ new EnrollmentData.Builder()
+ .setEnrollmentId(ENROLLMENT_ID)
+ .setCompanyId(COMPANY_ID)
+ .setSdkNames(String.join(SEPARATOR, SDK_NAMES))
+ .setAttributionSourceRegistrationUrl(
+ String.join(SEPARATOR, ATTRIBUTION_SOURCE_REGISTRATION_URLS))
+ .setAttributionTriggerRegistrationUrl(
+ String.join(SEPARATOR, ATTRIBUTION_TRIGGER_REGISTRATION_URLS))
+ .setAttributionReportingUrl(
+ String.join(SEPARATOR, ATTRIBUTION_REPORTING_REGISTRATION_URLS))
+ .setRemarketingResponseBasedRegistrationUrl(
+ String.join(
+ SEPARATOR, REMARKETING_RESPONSE_BASED_REGISTRATION_URLS))
+ .setEncryptionKeyUrl(String.join(SEPARATOR, ENCRYPTION_KEY_URLS))
+ .build();
+
+ assertEquals(ENROLLMENT_ID, enrollmentData.getEnrollmentId());
+ assertEquals(COMPANY_ID, enrollmentData.getCompanyId());
+ assertThat(enrollmentData.getSdkNames()).containsExactlyElementsIn(SDK_NAMES);
assertThat(enrollmentData.getAttributionSourceRegistrationUrl())
- .containsExactly("source1.example.com", "source2.example.com");
+ .containsExactlyElementsIn(ATTRIBUTION_SOURCE_REGISTRATION_URLS);
assertThat(enrollmentData.getAttributionTriggerRegistrationUrl())
- .containsExactly("trigger1.example.com", "trigger2.example.com");
+ .containsExactlyElementsIn(ATTRIBUTION_TRIGGER_REGISTRATION_URLS);
assertThat(enrollmentData.getAttributionReportingUrl())
- .containsExactly("reporting1.example.com", "reporting2.example.com");
+ .containsExactlyElementsIn(ATTRIBUTION_REPORTING_REGISTRATION_URLS);
assertThat(enrollmentData.getRemarketingResponseBasedRegistrationUrl())
- .containsExactly("remarketing1.example.com", "remarketing2.example.com");
+ .containsExactlyElementsIn(REMARKETING_RESPONSE_BASED_REGISTRATION_URLS);
assertThat(enrollmentData.getEncryptionKeyUrl())
- .containsExactly("encryption1.example.com", "encryption2.example.com");
+ .containsExactlyElementsIn(ENCRYPTION_KEY_URLS);
+ }
+
+ @Test
+ public void testSplitEnrollmentInputToList_emptyString() {
+ assertThat(EnrollmentData.splitEnrollmentInputToList("")).isEmpty();
+ }
+
+ @Test
+ public void testSplitEnrollmentInputToList_singleItem() {
+ String item = "one.item";
+ assertThat(EnrollmentData.splitEnrollmentInputToList(item)).containsExactly(item);
+ }
+
+ @Test
+ public void testSplitEnrollmentInputToList_multipleItems() {
+ List<String> items = Arrays.asList("first.item", "second.item");
+ String itemListString = String.join(SEPARATOR, items);
+ assertThat(EnrollmentData.splitEnrollmentInputToList(itemListString))
+ .containsExactlyElementsIn(items);
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/js/JSScriptEngineTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/js/JSScriptEngineTest.java
index 85d855a64..d4b8955da 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/js/JSScriptEngineTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/js/JSScriptEngineTest.java
@@ -20,7 +20,7 @@ import static com.android.adservices.service.js.JSScriptArgument.arrayArg;
import static com.android.adservices.service.js.JSScriptArgument.numericArg;
import static com.android.adservices.service.js.JSScriptArgument.recordArg;
import static com.android.adservices.service.js.JSScriptArgument.stringArg;
-import static com.android.adservices.service.js.JSScriptEngine.JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_ERROR_MSG;
+import static com.android.adservices.service.js.JSScriptEngine.JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +39,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.util.Log;
+import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.javascriptengine.IsolateStartupParameters;
@@ -52,6 +53,7 @@ import com.android.adservices.service.exception.JSExecutionException;
import com.android.adservices.service.profiling.JSScriptEngineLogConstants;
import com.android.adservices.service.profiling.Profiler;
import com.android.adservices.service.profiling.StopWatch;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FluentFuture;
@@ -64,9 +66,12 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.io.IOException;
import java.util.Arrays;
@@ -135,6 +140,32 @@ public class JSScriptEngineTest {
}
@Test
+ public void testProviderFailsIfJSSandboxNotAvailableInWebViewVersion() {
+ MockitoSession staticMockSessionLocal = null;
+
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(WebView.class)
+ .strictness(Strictness.LENIENT)
+ .initMocks(this)
+ .startMocking();
+ ExtendedMockito.doReturn(null).when(WebView::getCurrentWebViewPackage);
+
+ ThrowingRunnable getFutureInstance =
+ () ->
+ new JSScriptEngine.JavaScriptSandboxProvider(sMockProfiler)
+ .getFutureInstance(sContext);
+
+ assertThrows(JSSandboxIsNotAvailableException.class, getFutureInstance);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void testCanRunSimpleScriptWithNoArgs() throws Exception {
assertThat(
callJSEngine(
@@ -346,7 +377,7 @@ public class JSScriptEngineTest {
.isInstanceOf(JSScriptEngineConnectionException.class);
assertThat(executionException)
.hasMessageThat()
- .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_ERROR_MSG);
+ .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG);
verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME);
}
@@ -380,7 +411,7 @@ public class JSScriptEngineTest {
.isInstanceOf(JSScriptEngineConnectionException.class);
assertThat(executionException)
.hasMessageThat()
- .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_ERROR_MSG);
+ .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG);
verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME);
verify(mMockedSandbox)
.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE);
@@ -410,7 +441,7 @@ public class JSScriptEngineTest {
.isInstanceOf(JSScriptEngineConnectionException.class);
assertThat(executionException)
.hasMessageThat()
- .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_ERROR_MSG);
+ .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG);
verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME);
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueJobServiceTest.java
new file mode 100644
index 000000000..36249ea32
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueJobServiceTest.java
@@ -0,0 +1,283 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.AdServicesConfig.ASYNC_REGISTRATION_QUEUE_JOB_ID;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.Context;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.stats.AdServicesLoggerImpl;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+public class AsyncRegistrationQueueJobServiceTest {
+
+ private static final long WAIT_IN_MILLIS = 50L;
+ private JobScheduler mMockJobScheduler;
+ private AsyncRegistrationQueueJobService mSpyService;
+ private DatastoreManager mMockDatastoreManager;
+
+ @Before
+ public void setUp() {
+ mSpyService = spy(new AsyncRegistrationQueueJobService());
+ mMockJobScheduler = mock(JobScheduler.class);
+ }
+
+ @Test
+ public void onStartJob_killSwitchOn() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ // Execute
+ boolean result = mSpyService.onStartJob(mock(JobParameters.class));
+
+ // Validate
+ assertFalse(result);
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mSpyService, times(1)).jobFinished(any(), eq(false));
+ verify(mMockJobScheduler, times(1)).cancel(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void onStartJob_killSwitchOff() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+ ExtendedMockito.doNothing()
+ .when(
+ () ->
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ any(), anyBoolean()));
+
+ // Execute
+ boolean result = mSpyService.onStartJob(mock(JobParameters.class));
+
+ // Validate
+ assertTrue(result);
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(mSpyService, times(1)).jobFinished(any(), anyBoolean());
+ verify(mMockJobScheduler, never()).cancel(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+
+ // Execute
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, never())
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+
+ // Execute
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+
+ // Execute
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ true);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.schedule(any(), any()),
+ times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ doReturn(/* noJobInfo = */ null)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+
+ // Execute
+ AsyncRegistrationQueueJobService.scheduleIfNeeded(mockContext, false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> AsyncRegistrationQueueJobService.schedule(any(), any()),
+ times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(ASYNC_REGISTRATION_QUEUE_JOB_ID));
+ });
+ }
+
+ private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(AsyncRegistrationQueueJobService.class)
+ .spyStatic(AdServicesLoggerImpl.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ try {
+ // Setup mock everything in job
+ mMockDatastoreManager = mock(DatastoreManager.class);
+ doReturn(Optional.empty())
+ .when(mMockDatastoreManager)
+ .runInTransactionWithResult(any());
+ doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
+ doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
+ doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
+ ExtendedMockito.doReturn(mock(AdServicesLoggerImpl.class))
+ .when(AdServicesLoggerImpl::getInstance);
+ ExtendedMockito.doNothing()
+ .when(() -> AsyncRegistrationQueueJobService.schedule(any(), any()));
+ ExtendedMockito.doReturn(mMockDatastoreManager)
+ .when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
+
+ // Execute
+ execute.run();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ private void enableKillSwitch() {
+ toggleKillSwitch(true);
+ }
+
+ private void disableKillSwitch() {
+ toggleKillSwitch(false);
+ }
+
+ private void toggleKillSwitch(boolean value) {
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getRegistrationJobQueueKillSwitch();
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueRunnerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueRunnerTest.java
new file mode 100644
index 000000000..5912fb341
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AsyncRegistrationQueueRunnerTest.java
@@ -0,0 +1,1787 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.measurement.attribution.TriggerContentProvider.TRIGGER_URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyShort;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.data.DbHelper;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.IMeasurementDao;
+import com.android.adservices.data.measurement.ITransaction;
+import com.android.adservices.data.measurement.MeasurementTables;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.registration.AsyncSourceFetcher;
+import com.android.adservices.service.measurement.registration.AsyncTriggerFetcher;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/** Unit tests for {@link AsyncRegistrationQueueRunnerTest} */
+public class AsyncRegistrationQueueRunnerTest {
+ private static final Context sDefaultContext = ApplicationProvider.getApplicationContext();
+ private static final String DEFAULT_ENROLLMENT_ID = "enrollment_id";
+ private static final Uri DEFAULT_REGISTRANT = Uri.parse("android-app://com.registrant");
+ private static final Uri DEFAULT_VERIFIED_DESTINATION = Uri.parse("android-app://com.example");
+ private static final Uri APP_TOP_ORIGIN =
+ Uri.parse("android-app://" + sDefaultContext.getPackageName());
+ private static final Uri WEB_TOP_ORIGIN = Uri.parse("https://example.com");
+ private static final Uri REGISTRATION_URI = Uri.parse("https://foo.com/bar?ad=134");
+ private static final String LIST_TYPE_REDIRECT_URI_1 = "https://foo.com";
+ private static final String LIST_TYPE_REDIRECT_URI_2 = "https://bar.com";
+ private static final String LOCATION_TYPE_REDIRECT_URI = "https://baz.com";
+ private static final Uri WEB_DESTINATION = Uri.parse("https://web-destination.com");
+ private static final Uri APP_DESTINATION = Uri.parse("android-app://com.app_destination");
+ private static final Source SOURCE_1 =
+ SourceFixture.getValidSourceBuilder()
+ .setEventId(new UnsignedLong(1L))
+ .setPublisher(APP_TOP_ORIGIN)
+ .setAppDestination(Uri.parse("android-app://com.destination1"))
+ .setWebDestination(Uri.parse("https://web-destination1.com"))
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrant(Uri.parse("android-app://com.example"))
+ .setEventTime(new Random().nextLong())
+ .setExpiryTime(8640000010L)
+ .setPriority(100L)
+ .setSourceType(Source.SourceType.EVENT)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setDebugKey(new UnsignedLong(47823478789L))
+ .build();
+
+ private AsyncSourceFetcher mAsyncSourceFetcher;
+ private AsyncTriggerFetcher mAsyncTriggerFetcher;
+
+ @Mock private IMeasurementDao mMeasurementDao;
+ @Mock private Source mMockedSource;
+ @Mock private Trigger mMockedTrigger;
+ @Mock private ITransaction mTransaction;
+ @Mock private EnrollmentDao mEnrollmentDao;
+ @Mock private ContentResolver mContentResolver;
+ @Mock private ContentProviderClient mMockContentProviderClient;
+
+ private MockitoSession mStaticMockSession;
+
+ private static EnrollmentData getEnrollment(String enrollmentId) {
+ return new EnrollmentData.Builder().setEnrollmentId(enrollmentId).build();
+ }
+
+ class FakeDatastoreManager extends DatastoreManager {
+
+ @Override
+ public ITransaction createNewTransaction() {
+ return mTransaction;
+ }
+
+ @Override
+ public IMeasurementDao getMeasurementDao() {
+ return mMeasurementDao;
+ }
+ }
+
+ @After
+ public void cleanup() {
+ SQLiteDatabase db = DbHelper.getInstance(sDefaultContext).safeGetWritableDatabase();
+ for (String table : MeasurementTables.ALL_MSMT_TABLES) {
+ db.delete(table, null, null);
+ }
+ mStaticMockSession.finishMocking();
+ }
+
+ @Before
+ public void before() throws RemoteException {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+
+ mAsyncSourceFetcher = spy(new AsyncSourceFetcher(sDefaultContext));
+ mAsyncTriggerFetcher = spy(new AsyncTriggerFetcher(sDefaultContext));
+ MockitoAnnotations.initMocks(this);
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any()))
+ .thenReturn(getEnrollment(DEFAULT_ENROLLMENT_ID));
+ when(mContentResolver.acquireContentProviderClient(TRIGGER_URI))
+ .thenReturn(mMockContentProviderClient);
+ when(mMockContentProviderClient.insert(any(), any())).thenReturn(TRIGGER_URI);
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_success() throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, times(2)).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ // Tests for redirect types
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appSource_defaultRegistration_redirectTypeList()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+ Answer<Optional<Source>> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LIST_TYPE_REDIRECT_URI_1),
+ Uri.parse(LIST_TYPE_REDIRECT_URI_2)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.NONE);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ List<Source.FakeReport> eventReportList = Collections.singletonList(
+ new Source.FakeReport(new UnsignedLong(1L), 1L, APP_DESTINATION));
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, times(2)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'none' type redirect and values
+ Assert.assertEquals(2, asyncRegistrationArgumentCaptor.getAllValues().size());
+
+ AsyncRegistration asyncReg1 = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LIST_TYPE_REDIRECT_URI_1), asyncReg1.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.NONE, asyncReg1.getRedirectType());
+ Assert.assertEquals(1, asyncReg1.getRedirectCount());
+
+ AsyncRegistration asyncReg2 = asyncRegistrationArgumentCaptor.getAllValues().get(1);
+ Assert.assertEquals(Uri.parse(LIST_TYPE_REDIRECT_URI_2), asyncReg2.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.NONE, asyncReg2.getRedirectType());
+ Assert.assertEquals(1, asyncReg2.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appSource_defaultRegistration_redirectTypeLocation()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+ Answer<Optional<Source>> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LOCATION_TYPE_REDIRECT_URI)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.DAISY_CHAIN);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ List<Source.FakeReport> eventReportList = Collections.singletonList(
+ new Source.FakeReport(new UnsignedLong(1L), 1L, APP_DESTINATION));
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, times(1)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'location' type redirect and value
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ AsyncRegistration asyncReg = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LOCATION_TYPE_REDIRECT_URI), asyncReg.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncReg.getRedirectType());
+ Assert.assertEquals(1, asyncReg.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appSource_middleRegistration_redirectTypeLocation()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource(
+ AsyncRegistration.RedirectType.DAISY_CHAIN, 3);
+ Answer<Optional<Source>> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LOCATION_TYPE_REDIRECT_URI)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.DAISY_CHAIN);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ List<Source.FakeReport> eventReportList = Collections.singletonList(
+ new Source.FakeReport(new UnsignedLong(1L), 1L, APP_DESTINATION));
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, times(1)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'location' type redirect and value
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ AsyncRegistration asyncReg = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LOCATION_TYPE_REDIRECT_URI), asyncReg.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncReg.getRedirectType());
+ Assert.assertEquals(4, asyncReg.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appTrigger_defaultRegistration_redirectTypeList()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<Optional<Trigger>> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LIST_TYPE_REDIRECT_URI_1),
+ Uri.parse(LIST_TYPE_REDIRECT_URI_2)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.NONE);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, times(2)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'none' type redirect and values
+ Assert.assertEquals(2, asyncRegistrationArgumentCaptor.getAllValues().size());
+
+ AsyncRegistration asyncReg1 = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LIST_TYPE_REDIRECT_URI_1), asyncReg1.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.NONE, asyncReg1.getRedirectType());
+ Assert.assertEquals(1, asyncReg1.getRedirectCount());
+
+ AsyncRegistration asyncReg2 = asyncRegistrationArgumentCaptor.getAllValues().get(1);
+ Assert.assertEquals(Uri.parse(LIST_TYPE_REDIRECT_URI_2), asyncReg2.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.NONE, asyncReg2.getRedirectType());
+ Assert.assertEquals(1, asyncReg2.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appTrigger_defaultReg_redirectTypeLocation()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<Optional<Trigger>> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LOCATION_TYPE_REDIRECT_URI)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.DAISY_CHAIN);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, times(1)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'location' type redirect and values
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+
+ AsyncRegistration asyncReg = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LOCATION_TYPE_REDIRECT_URI), asyncReg.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncReg.getRedirectType());
+ Assert.assertEquals(1, asyncReg.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void runAsyncRegistrationQueueWorker_appTrigger_middleRegistration_redirectTypeLocation()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger(
+ AsyncRegistration.RedirectType.DAISY_CHAIN, 4);
+
+ Answer<Optional<Trigger>> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse(LOCATION_TYPE_REDIRECT_URI)));
+ asyncRedirect.setRedirectType(AsyncRegistration.RedirectType.DAISY_CHAIN);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, times(1)).insertAsyncRegistration(
+ asyncRegistrationArgumentCaptor.capture());
+
+ // Assert 'location' type redirect and values
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+
+ AsyncRegistration asyncReg = asyncRegistrationArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(Uri.parse(LOCATION_TYPE_REDIRECT_URI), asyncReg.getRegistrationUri());
+ Assert.assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncReg.getRedirectType());
+ Assert.assertEquals(5, asyncReg.getRedirectCount());
+
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ // End tests for redirect types
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_noRedirects_success()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_adTechUnavailable()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_NetworkError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appSource_parsingError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, never()).updateRetryCount(any());
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_success()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, times(2)).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_noRedirects_success()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_adTechUnavailable()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_networkError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_appTrigger_parsingError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForAppTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
+ AsyncRedirect asyncRedirect = invocation.getArgument(2);
+ asyncRedirect.addToRedirects(List.of(
+ Uri.parse("https://example.com/sF1"),
+ Uri.parse("https://example.com/sF2")));
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, never()).updateRetryCount(any());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webSource_success() throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ return Optional.of(mMockedSource);
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, times(1)).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webSource_adTechUnavailable()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webSource_NetworkError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webSource_parsingError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebSource();
+
+ Answer<?> answerAsyncSourceFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncSourceFetcher)
+ .when(mAsyncSourceFetcher)
+ .fetchSource(any(), any(), any());
+
+ Source.FakeReport sf =
+ new Source.FakeReport(
+ new UnsignedLong(1L), 1L, Uri.parse("https://example.com/sF"));
+ List<Source.FakeReport> eventReportList = Collections.singletonList(sf);
+ when(mMockedSource.assignAttributionModeAndGenerateFakeReports())
+ .thenReturn(eventReportList);
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncSourceFetcher, times(1))
+ .fetchSource(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, never()).updateRetryCount(any());
+ verify(mMeasurementDao, never()).insertEventReport(any(EventReport.class));
+ verify(mMeasurementDao, never()).insertSource(any(Source.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webTrigger_success()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
+ return Optional.of(mMockedTrigger);
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, times(1)).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webTrigger_adTechUnavailable()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webTrigger_networkError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ ArgumentCaptor<AsyncRegistration> asyncRegistrationArgumentCaptor =
+ ArgumentCaptor.forClass(AsyncRegistration.class);
+ verify(mMeasurementDao, times(1))
+ .updateRetryCount(asyncRegistrationArgumentCaptor.capture());
+ Assert.assertEquals(1, asyncRegistrationArgumentCaptor.getAllValues().size());
+ Assert.assertEquals(
+ 1, asyncRegistrationArgumentCaptor.getAllValues().get(0).getRetryCount());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).deleteAsyncRegistration(any(String.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ }
+
+ @Test
+ public void test_runAsyncRegistrationQueueWorker_webTrigger_parsingError()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ AsyncRegistration validAsyncRegistration = createAsyncRegistrationForWebTrigger();
+
+ Answer<?> answerAsyncTriggerFetcher =
+ invocation -> {
+ AsyncFetchStatus asyncFetchStatus = invocation.getArgument(1);
+ asyncFetchStatus.setStatus(AsyncFetchStatus.ResponseStatus.PARSING_ERROR);
+ return Optional.empty();
+ };
+ doAnswer(answerAsyncTriggerFetcher)
+ .when(mAsyncTriggerFetcher)
+ .fetchTrigger(any(), any(), any());
+
+ when(mMeasurementDao.fetchNextQueuedAsyncRegistration(anyShort(), any()))
+ .thenReturn(validAsyncRegistration);
+
+ // Execution
+ asyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(1L, (short) 5);
+
+ // Assertions
+ verify(mAsyncTriggerFetcher, times(1))
+ .fetchTrigger(any(AsyncRegistration.class), any(AsyncFetchStatus.class), any());
+ verify(mMeasurementDao, never()).updateRetryCount(any());
+ verify(mMeasurementDao, never()).insertTrigger(any(Trigger.class));
+ verify(mMeasurementDao, never()).insertAsyncRegistration(any(AsyncRegistration.class));
+ verify(mMeasurementDao, times(1)).deleteAsyncRegistration(any(String.class));
+ }
+
+ @Test
+ public void insertSource_withFakeReportsFalseAppAttribution_accountsForFakeReportAttribution()
+ throws DatastoreException {
+ // Setup
+ int fakeReportsCount = 2;
+ Source source =
+ spy(
+ SourceFixture.getValidSourceBuilder()
+ .setAppDestination(
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
+ .setWebDestination(null)
+ .build());
+ List<Source.FakeReport> fakeReports =
+ createFakeReports(
+ source,
+ fakeReportsCount,
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION);
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ Answer<?> falseAttributionAnswer =
+ (arg) -> {
+ source.setAttributionMode(Source.AttributionMode.FALSELY);
+ return fakeReports;
+ };
+ doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
+ ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
+ ArgumentCaptor.forClass(Attribution.class);
+
+ // Execution
+ asyncRegistrationQueueRunner.insertSourcesFromTransaction(source, mMeasurementDao);
+
+ // Assertion
+ verify(mMeasurementDao).insertSource(source);
+ verify(mMeasurementDao, times(2)).insertEventReport(any());
+ verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
+
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getAppDestination().toString())
+ .setDestinationSite(source.getAppDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getValue());
+ }
+
+ @Test
+ public void insertSource_withFakeReportsFalseWebAttribution_accountsForFakeReportAttribution()
+ throws DatastoreException {
+ // Setup
+ int fakeReportsCount = 2;
+ Source source =
+ spy(
+ SourceFixture.getValidSourceBuilder()
+ .setAppDestination(null)
+ .setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
+ .build());
+ List<Source.FakeReport> fakeReports =
+ createFakeReports(
+ source, fakeReportsCount, SourceFixture.ValidSourceParams.WEB_DESTINATION);
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ Answer<?> falseAttributionAnswer =
+ (arg) -> {
+ source.setAttributionMode(Source.AttributionMode.FALSELY);
+ return fakeReports;
+ };
+ doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
+ ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
+ ArgumentCaptor.forClass(Attribution.class);
+
+ // Execution
+ asyncRegistrationQueueRunner.insertSourcesFromTransaction(source, mMeasurementDao);
+
+ // Assertion
+ verify(mMeasurementDao).insertSource(source);
+ verify(mMeasurementDao, times(2)).insertEventReport(any());
+ verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
+
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getWebDestination().toString())
+ .setDestinationSite(source.getWebDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getValue());
+ }
+
+ @Test
+ public void insertSource_withFalseAppAndWebAttribution_accountsForFakeReportAttribution()
+ throws DatastoreException {
+ // Setup
+ int fakeReportsCount = 2;
+ Source source =
+ spy(
+ SourceFixture.getValidSourceBuilder()
+ .setAppDestination(
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
+ .setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
+ .build());
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+
+ List<Source.FakeReport> fakeReports =
+ createFakeReports(
+ source,
+ fakeReportsCount,
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION);
+
+ Answer<?> falseAttributionAnswer =
+ (arg) -> {
+ source.setAttributionMode(Source.AttributionMode.FALSELY);
+ return fakeReports;
+ };
+ ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
+ ArgumentCaptor.forClass(Attribution.class);
+ doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
+
+ // Execution
+ asyncRegistrationQueueRunner.insertSourcesFromTransaction(source, mMeasurementDao);
+
+ // Assertion
+ verify(mMeasurementDao).insertSource(source);
+ verify(mMeasurementDao, times(2)).insertEventReport(any());
+ verify(mMeasurementDao, times(2))
+ .insertAttribution(attributionRateLimitArgCaptor.capture());
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getAppDestination().toString())
+ .setDestinationSite(source.getAppDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getAllValues().get(0));
+
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getWebDestination().toString())
+ .setDestinationSite(source.getWebDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getAllValues().get(1));
+ }
+
+ @Test
+ public void insertSource_withFakeReportsNeverAppAttribution_accountsForFakeReportAttribution()
+ throws DatastoreException {
+ // Setup
+ Source source =
+ spy(
+ SourceFixture.getValidSourceBuilder()
+ .setAppDestination(
+ SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
+ .setWebDestination(null)
+ .build());
+ List<Source.FakeReport> fakeReports = Collections.emptyList();
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ getSpyAsyncRegistrationQueueRunner();
+ Answer<?> neverAttributionAnswer =
+ (arg) -> {
+ source.setAttributionMode(Source.AttributionMode.NEVER);
+ return fakeReports;
+ };
+ doAnswer(neverAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
+ ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
+ ArgumentCaptor.forClass(Attribution.class);
+
+ // Execution
+ asyncRegistrationQueueRunner.insertSourcesFromTransaction(source, mMeasurementDao);
+
+ // Assertion
+ verify(mMeasurementDao).insertSource(source);
+ verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
+
+ assertEquals(
+ new Attribution.Builder()
+ .setDestinationOrigin(source.getAppDestination().toString())
+ .setDestinationSite(source.getAppDestination().toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setSourceSite(source.getPublisher().toString())
+ .setRegistrant(source.getRegistrant().toString())
+ .setTriggerTime(source.getEventTime())
+ .build(),
+ attributionRateLimitArgCaptor.getValue());
+ }
+
+ @Test
+ public void testRegister_registrationTypeSource_sourceFetchSuccess() throws DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assertions
+ verify(mMeasurementDao, times(2))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, times(2))
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegister_registrationTypeSource_exceedsPrivacyParam_destination()
+ throws DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(100));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMeasurementDao, times(1))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, never())
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegister_registrationTypeSource_exceedsMaxSourcesLimit()
+ throws DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ doReturn(SystemHealthParams.MAX_SOURCES_PER_PUBLISHER)
+ .when(mMeasurementDao)
+ .getNumSourcesPerPublisher(any(), anyInt());
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMeasurementDao, times(1)).getNumSourcesPerPublisher(any(), anyInt());
+ }
+
+ @Test
+ public void testRegister_registrationTypeSource_exceedsPrivacyParam_adTech()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(100));
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMeasurementDao, times(1))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, times(1))
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegisterWebSource_exceedsPrivacyParam_destination()
+ throws RemoteException, DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(100));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMockContentProviderClient, never()).insert(any(), any());
+ verify(mMeasurementDao, times(1))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, never())
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegisterWebSource_exceedsPrivacyParam_adTech() throws DatastoreException {
+ // setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ // Execution
+ when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(0));
+ when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong()))
+ .thenReturn(Integer.valueOf(100));
+
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assert
+ assertFalse(status);
+ verify(mMeasurementDao, times(1))
+ .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
+ verify(mMeasurementDao, times(1))
+ .countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ any(), anyInt(), any(), any(), anyLong(), anyLong());
+ }
+
+ @Test
+ public void testRegisterWebSource_exceedsMaxSourcesLimit() throws DatastoreException {
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+ doReturn(SystemHealthParams.MAX_SOURCES_PER_PUBLISHER)
+ .when(mMeasurementDao)
+ .getNumSourcesPerPublisher(any(), anyInt());
+
+ // Execution
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assertions
+ assertFalse(status);
+ }
+
+ @Test
+ public void testRegisterWebSource_LimitsMaxSources_ForWebPublisher_WitheTLDMatch()
+ throws DatastoreException {
+ // Setup
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
+ spy(
+ new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+
+ doReturn(SystemHealthParams.MAX_SOURCES_PER_PUBLISHER)
+ .when(mMeasurementDao)
+ .getNumSourcesPerPublisher(any(), anyInt());
+
+ // Execution
+ boolean status =
+ asyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ SOURCE_1, SOURCE_1.getPublisher(), EventSurfaceType.APP, mMeasurementDao);
+
+ // Assertions
+ assertFalse(status);
+ }
+
+ private List<Source.FakeReport> createFakeReports(Source source, int count, Uri destination) {
+ return IntStream.range(0, count)
+ .mapToObj(
+ x ->
+ new Source.FakeReport(
+ new UnsignedLong(0L),
+ source.getReportingTimeForNoising(0),
+ destination))
+ .collect(Collectors.toList());
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForAppSource() {
+ return createAsyncRegistrationForAppSource(AsyncRegistration.RedirectType.ANY, 0);
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForAppSource(
+ @AsyncRegistration.RedirectType int redirectType, int redirectCount) {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrationUri(REGISTRATION_URI)
+ // null .setWebDestination(webDestination)
+ // null .setOsDestination(osDestination)
+ .setRegistrant(DEFAULT_REGISTRANT)
+ // null .setVerifiedDestination(null)
+ .setTopOrigin(APP_TOP_ORIGIN)
+ .setType(AsyncRegistration.RegistrationType.APP_SOURCE.ordinal())
+ .setSourceType(Source.SourceType.EVENT)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(0)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(true)
+ .build();
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForAppTrigger() {
+ return createAsyncRegistrationForAppTrigger(AsyncRegistration.RedirectType.ANY, 0);
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForAppTrigger(
+ @AsyncRegistration.RedirectType int redirectType, int redirectCount) {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrationUri(REGISTRATION_URI)
+ // null .setWebDestination(webDestination)
+ // null .setOsDestination(osDestination)
+ .setRegistrant(DEFAULT_REGISTRANT)
+ // null .setVerifiedDestination(null)
+ .setTopOrigin(APP_TOP_ORIGIN)
+ .setType(AsyncRegistration.RegistrationType.APP_TRIGGER.ordinal())
+ // null .setSourceType(null)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(0)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(true)
+ .build();
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForWebSource() {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrationUri(REGISTRATION_URI)
+ .setWebDestination(WEB_DESTINATION)
+ .setOsDestination(APP_DESTINATION)
+ .setRegistrant(DEFAULT_REGISTRANT)
+ .setVerifiedDestination(DEFAULT_VERIFIED_DESTINATION)
+ .setTopOrigin(WEB_TOP_ORIGIN)
+ .setType(AsyncRegistration.RegistrationType.WEB_SOURCE.ordinal())
+ .setSourceType(Source.SourceType.EVENT)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(0)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setRedirectType(AsyncRegistration.RedirectType.NONE)
+ .setDebugKeyAllowed(true)
+ .build();
+ }
+
+ private static AsyncRegistration createAsyncRegistrationForWebTrigger() {
+ return new AsyncRegistration.Builder()
+ .setId(UUID.randomUUID().toString())
+ .setEnrollmentId(DEFAULT_ENROLLMENT_ID)
+ .setRegistrationUri(REGISTRATION_URI)
+ // null .setWebDestination(webDestination)
+ // null .setOsDestination(osDestination)
+ .setRegistrant(DEFAULT_REGISTRANT)
+ // null .setVerifiedDestination(null)
+ .setTopOrigin(WEB_TOP_ORIGIN)
+ .setType(AsyncRegistration.RegistrationType.WEB_TRIGGER.ordinal())
+ // null .setSourceType(null)
+ .setRequestTime(System.currentTimeMillis())
+ .setRetryCount(0)
+ .setLastProcessingTime(System.currentTimeMillis())
+ .setRedirectType(AsyncRegistration.RedirectType.NONE)
+ .setDebugKeyAllowed(true)
+ .build();
+ }
+
+ private AsyncRegistrationQueueRunner getSpyAsyncRegistrationQueueRunner() {
+ return spy(new AsyncRegistrationQueueRunner(
+ mContentResolver,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ mEnrollmentDao,
+ new FakeDatastoreManager()));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java
index 56893ca64..8067da5e7 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributionTest.java
@@ -22,6 +22,8 @@ import static org.junit.Assert.assertThrows;
import org.junit.Test;
+import java.util.UUID;
+
public class AttributionTest {
private static final String ID = "AR1";
private static final String DESTINATION_ORIGIN = "https://destination.com/origin";
@@ -33,6 +35,8 @@ public class AttributionTest {
private static final long TRIGGER_TIME = 10000L;
private static final String SOME_OTHER_STRING = "some_other";
private static final long SOME_OTHER_LONG = 1L;
+ private static final String SOURCE_ID = UUID.randomUUID().toString();
+ private static final String TRIGGER_ID = UUID.randomUUID().toString();
@Test
public void equals_pass() {
@@ -75,6 +79,12 @@ public class AttributionTest {
assertNotEquals(
createExampleAttributionBuilder().setTriggerTime(SOME_OTHER_LONG).build(),
createExampleAttributionBuilder().build());
+ assertNotEquals(
+ createExampleAttributionBuilder().setSourceId(SOME_OTHER_STRING).build(),
+ createExampleAttributionBuilder().build());
+ assertNotEquals(
+ createExampleAttributionBuilder().setTriggerId(SOME_OTHER_STRING).build(),
+ createExampleAttributionBuilder().build());
}
@Test
@@ -128,6 +138,15 @@ public class AttributionTest {
.build()
.hashCode(),
createExampleAttributionBuilder().build().hashCode());
+ assertNotEquals(
+ createExampleAttributionBuilder().setSourceId(SOME_OTHER_STRING).build().hashCode(),
+ createExampleAttributionBuilder().build().hashCode());
+ assertNotEquals(
+ createExampleAttributionBuilder()
+ .setTriggerId(SOME_OTHER_STRING)
+ .build()
+ .hashCode(),
+ createExampleAttributionBuilder().build().hashCode());
}
@Test
@@ -150,6 +169,8 @@ public class AttributionTest {
assertEquals(
ENROLLMENT_ID,
createExampleAttributionBuilder().build().getEnrollmentId());
+ assertEquals(SOURCE_ID, createExampleAttributionBuilder().build().getSourceId());
+ assertEquals(TRIGGER_ID, createExampleAttributionBuilder().build().getTriggerId());
}
@Test
@@ -186,6 +207,8 @@ public class AttributionTest {
.setDestinationOrigin(DESTINATION_ORIGIN)
.setDestinationSite(DESTINATION_SITE)
.setSourceOrigin(PUBLISHER_ORIGIN)
- .setSourceSite(PUBLISHER_SITE);
+ .setSourceSite(PUBLISHER_SITE)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java
index 4d3a471fc..a515361a6 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteExpiredJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DELETE_EXPIRED_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,30 +36,28 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import java.util.concurrent.TimeUnit;
+
/**
* Unit test for {@link DeleteExpiredJobService
*/
public class DeleteExpiredJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
private static final long WAIT_IN_MILLIS = 50L;
private DatastoreManager mMockDatastoreManager;
@@ -75,10 +74,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -95,10 +95,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ disableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -115,11 +116,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -146,11 +147,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -177,11 +178,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -208,11 +209,11 @@ public class DeleteExpiredJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -234,11 +235,28 @@ public class DeleteExpiredJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ DeleteExpiredJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
+ .spyStatic(AdServicesConfig.class)
.spyStatic(DatastoreManagerFactory.class)
.spyStatic(DeleteExpiredJobService.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -247,6 +265,8 @@ public class DeleteExpiredJobServiceTest {
doReturn(true).when(mMockDatastoreManager).runInTransaction(any());
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(24))
+ .when(AdServicesConfig::getMeasurementDeleteExpiredJobPeriodMs);
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing().when(() -> DeleteExpiredJobService.schedule(any(), any()));
@@ -267,10 +287,8 @@ public class DeleteExpiredJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_delete_expired_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getMeasurementJobDeleteExpiredKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteUninstalledJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteUninstalledJobServiceTest.java
new file mode 100644
index 000000000..e0efb7529
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/DeleteUninstalledJobServiceTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DELETE_UNINSTALLED_JOB_ID;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.Context;
+
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.Spy;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.TimeUnit;
+
+public class DeleteUninstalledJobServiceTest {
+ private static final long WAIT_IN_MILLIS = 50L;
+
+ @Mock private JobScheduler mMockJobScheduler;
+ @Mock private MeasurementImpl mMockMeasurementImpl;
+
+ @Spy private DeleteUninstalledJobService mSpyService;
+
+ @Test
+ public void onStartJob_killSwitchOn() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ // Execute
+ boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
+
+ // Validate
+ assertFalse(result);
+
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mMockMeasurementImpl, never()).deleteAllUninstalledMeasurementData();
+ verify(mSpyService, times(1)).jobFinished(any(), eq(false));
+ verify(mMockJobScheduler, times(1))
+ .cancel(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void onStartJob_killSwitchOff() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ // Execute
+ boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
+
+ // Validate
+ assertTrue(result);
+
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mMockMeasurementImpl, times(1)).deleteAllUninstalledMeasurementData();
+ verify(mSpyService, times(1)).jobFinished(any(), anyBoolean());
+ verify(mMockJobScheduler, never())
+ .cancel(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+
+ // Execute
+ DeleteUninstalledJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, never())
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+
+ // Execute
+ DeleteUninstalledJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+
+ // Execute
+ DeleteUninstalledJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ true);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.schedule(any(), any()), times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ doReturn(/* noJobInfo = */ null)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+
+ // Execute
+ DeleteUninstalledJobService.scheduleIfNeeded(mockContext, false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DeleteUninstalledJobService.schedule(any(), any()), times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DELETE_UNINSTALLED_JOB_ID));
+ });
+ }
+
+ @Test
+ public void testSchedule_jobInfoIsPersisted() {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ DeleteUninstalledJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ }
+
+ private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .spyStatic(AdServicesConfig.class)
+ .spyStatic(MeasurementImpl.class)
+ .spyStatic(DeleteUninstalledJobService.class)
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ try {
+ // Setup mock everything in job
+ ExtendedMockito.doReturn(mMockMeasurementImpl)
+ .when(() -> MeasurementImpl.getInstance(any()));
+ doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
+ doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(24))
+ .when(AdServicesConfig::getMeasurementDeleteUninstalledJobPeriodMs);
+ ExtendedMockito.doNothing()
+ .when(() -> DeleteUninstalledJobService.schedule(any(), any()));
+
+ // Execute
+ execute.run();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ private void enableKillSwitch() {
+ toggleKillSwitch(true);
+ }
+
+ private void disableKillSwitch() {
+ toggleKillSwitch(false);
+ }
+
+ private void toggleKillSwitch(boolean value) {
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value)
+ .when(mockFlags)
+ .getMeasurementJobDeleteUninstalledKillSwitch();
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java
index d0871327e..8b79440f8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EDenoisedMockTest.java
@@ -35,22 +35,29 @@ import java.util.Collection;
public class E2EDenoisedMockTest extends E2EMockTest {
private static final String TEST_DIR_NAME = "msmt_e2e_tests";
- @Parameterized.Parameters(name = "{2}")
+ @Parameterized.Parameters(name = "{3}")
public static Collection<Object[]> getData() throws IOException, JSONException {
return data(TEST_DIR_NAME);
}
public E2EDenoisedMockTest(Collection<Action> actions, ReportObjects expectedOutput,
- String name) throws DatastoreException {
- super(actions, expectedOutput, name);
+ PrivacyParamsProvider privacyParamsProvider, String name) throws DatastoreException {
+ super(actions, expectedOutput, privacyParamsProvider, name);
mAttributionHelper = TestObjectProvider.getAttributionJobHandler(sDatastoreManager);
mMeasurementImpl =
TestObjectProvider.getMeasurementImpl(
- TestObjectProvider.Type.DENOISED,
sDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
mClickVerifier,
- mFlags);
+ mFlags,
+ mMeasurementDataDeleter,
+ sEnrollmentDao);
+
+ mAsyncRegistrationQueueRunner =
+ TestObjectProvider.getAsyncRegistrationQueueRunner(
+ TestObjectProvider.Type.DENOISED,
+ sDatastoreManager,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ sEnrollmentDao);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java
index c01225741..997041834 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EImpressionNoiseMockTest.java
@@ -51,23 +51,29 @@ public class E2EImpressionNoiseMockTest extends E2EMockTest {
String TRIGGER_DATA = "trigger_data";
}
- @Parameterized.Parameters(name = "{2}")
+ @Parameterized.Parameters(name = "{3}")
public static Collection<Object[]> getData() throws IOException, JSONException {
return data(TEST_DIR_NAME);
}
public E2EImpressionNoiseMockTest(Collection<Action> actions, ReportObjects expectedOutput,
- String name) throws DatastoreException {
- super(actions, expectedOutput, name);
+ PrivacyParamsProvider privacyParamsProvider, String name) throws DatastoreException {
+ super(actions, expectedOutput, privacyParamsProvider, name);
mAttributionHelper = TestObjectProvider.getAttributionJobHandler(sDatastoreManager);
mMeasurementImpl =
TestObjectProvider.getMeasurementImpl(
- TestObjectProvider.Type.NOISY,
sDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
mClickVerifier,
- mFlags);
+ mFlags,
+ mMeasurementDataDeleter,
+ sEnrollmentDao);
+ mAsyncRegistrationQueueRunner =
+ TestObjectProvider.getAsyncRegistrationQueueRunner(
+ TestObjectProvider.Type.NOISY,
+ sDatastoreManager,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ sEnrollmentDao);
getExpectedTriggerDataDistributions();
}
@@ -79,7 +85,7 @@ public class E2EImpressionNoiseMockTest extends E2EMockTest {
// is currently supporting only one reporting job, which batches multiple reports at once,
// although each is a separate network request.
for (int i = 0; i < destinations.size(); i++) {
- String uri = destinations.get(i).toString();
+ String uri = getReportUrl(ReportType.EVENT, destinations.get(i).toString());
JSONObject payload = payloads.get(i);
String eventId = payload.getString(PayloadKeys.EVENT_ID);
String triggerData = payload.getString(PayloadKeys.TRIGGER_DATA);
@@ -94,7 +100,8 @@ public class E2EImpressionNoiseMockTest extends E2EMockTest {
for (String key : mActualTriggerDataDistributions.keySet()) {
if (!mExpectedTriggerDataDistributions.containsKey(key)) {
Assert.assertTrue(getTestFailureMessage(
- "Missing key in expected trigger data distributions"), false);
+ "Missing key in expected trigger data distributions"
+ + getDatastoreState()), false);
}
}
boolean testPassed = false;
@@ -113,8 +120,10 @@ public class E2EImpressionNoiseMockTest extends E2EMockTest {
}
}
}
- Assert.assertTrue(getTestFailureMessage(
- "Trigger data distributions were the same"), testPassed);
+ Assert.assertTrue(
+ getTestFailureMessage(
+ "Trigger data distributions were the same " + getDatastoreState()),
+ testPassed);
}
private void getExpectedTriggerDataDistributions() {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EInteropMockTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EInteropMockTest.java
new file mode 100644
index 000000000..43dde1884
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EInteropMockTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.measurement;
+
+import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
+import static com.android.adservices.service.measurement.PrivacyParams.MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.service.measurement.actions.Action;
+import com.android.adservices.service.measurement.actions.RegisterSource;
+import com.android.adservices.service.measurement.actions.RegisterTrigger;
+import com.android.adservices.service.measurement.actions.ReportObjects;
+import com.android.adservices.service.measurement.util.Enrollment;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.adservices.service.measurement.util.Web;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * End-to-end test from source and trigger registration to attribution reporting, using mocked HTTP
+ * requests.
+ *
+ * Tests in assets/msmt_interop_tests/ directory were copied from
+ * https://source.chromium.org/chromium/chromium/src/+/main:content/test/data/attribution_reporting/interop/
+ * on October 15, 2022
+ */
+@RunWith(Parameterized.class)
+public class E2EInteropMockTest extends E2EMockTest {
+ private static final String LOG_TAG = "msmt_e2e_interop_mock_test";
+ private static final String TEST_DIR_NAME = "msmt_interop_tests";
+ private static final String ANDROID_APP_SCHEME = "android-app";
+
+ @Parameterized.Parameters(name = "{3}")
+ public static Collection<Object[]> getData() throws IOException, JSONException {
+ return data(TEST_DIR_NAME);
+ }
+
+ public E2EInteropMockTest(Collection<Action> actions, ReportObjects expectedOutput,
+ PrivacyParamsProvider privacyParamsProvider, String name) throws DatastoreException {
+ super(actions, expectedOutput, privacyParamsProvider, name);
+ mAttributionHelper = TestObjectProvider.getAttributionJobHandler(sDatastoreManager);
+ mMeasurementImpl =
+ TestObjectProvider.getMeasurementImpl(
+ sDatastoreManager,
+ mClickVerifier,
+ mFlags,
+ mMeasurementDataDeleter,
+ sEnrollmentDao);
+ mAsyncRegistrationQueueRunner =
+ TestObjectProvider.getAsyncRegistrationQueueRunner(
+ TestObjectProvider.Type.DENOISED,
+ sDatastoreManager,
+ mAsyncSourceFetcher,
+ mAsyncTriggerFetcher,
+ sEnrollmentDao);
+ }
+
+ @Override
+ void processAction(RegisterSource sourceRegistration) throws IOException {
+ RegistrationRequest request = sourceRegistration.mRegistrationRequest;
+ // For interop tests, we currently expect only one HTTPS response per registration with no
+ // redirects, partly due to differences in redirect handling across attribution APIs.
+ for (String uri : sourceRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
+ Source source = getSource(
+ sourceRegistration.getPublisher(),
+ sourceRegistration.mTimestamp,
+ uri,
+ request,
+ getNextResponse(sourceRegistration.mUriToResponseHeadersMap, uri));
+ Assert.assertTrue(
+ "measurementDao.insertSource failed",
+ sDatastoreManager.runInTransaction(
+ measurementDao -> {
+ if (AsyncRegistrationQueueRunner.isSourceAllowedToInsert(
+ source,
+ source.getPublisher(),
+ EventSurfaceType.WEB,
+ measurementDao)) {
+ measurementDao.insertSource(source);
+ }
+ }));
+ }
+ }
+
+ @Override
+ void processAction(RegisterTrigger triggerRegistration) throws IOException {
+ RegistrationRequest request = triggerRegistration.mRegistrationRequest;
+ // For interop tests, we currently expect only one HTTPS response per registration with no
+ // redirects, partly due to differences in redirect handling across attribution APIs.
+ for (String uri : triggerRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
+ Trigger trigger = getTrigger(
+ triggerRegistration.getDestination(),
+ triggerRegistration.mTimestamp,
+ uri,
+ request,
+ getNextResponse(triggerRegistration.mUriToResponseHeadersMap, uri));
+ Assert.assertTrue(
+ "measurementDao.insertTrigger failed",
+ sDatastoreManager.runInTransaction(
+ measurementDao ->
+ measurementDao.insertTrigger(trigger)));
+ }
+ Assert.assertTrue("AttributionJobHandler.performPendingAttributions returned false",
+ mAttributionHelper.performPendingAttributions());
+ }
+
+ private static Source getSource(String publisher, long timestamp, String uri,
+ RegistrationRequest request, Map<String, List<String>> headers) {
+ try {
+ Source.Builder sourceBuilder = new Source.Builder();
+ String enrollmentId =
+ Enrollment.maybeGetEnrollmentId(Uri.parse(uri), sEnrollmentDao).get();
+ sourceBuilder.setEnrollmentId(enrollmentId);
+ sourceBuilder.setPublisher(Uri.parse(publisher));
+ sourceBuilder.setPublisherType(EventSurfaceType.WEB);
+ sourceBuilder.setEventTime(timestamp);
+ sourceBuilder.setSourceType(getSourceType(request));
+ sourceBuilder.setAttributionMode(Source.AttributionMode.TRUTHFULLY);
+ sourceBuilder.setRegistrant(getRegistrant(request.getPackageName()));
+ List<String> field = headers.get("Attribution-Reporting-Register-Source");
+ JSONObject json = new JSONObject(field.get(0));
+ sourceBuilder.setEventId(new UnsignedLong(json.getString("source_event_id")));
+ if (!json.isNull("expiry")) {
+ long offset =
+ TimeUnit.SECONDS.toMillis(
+ extractValidNumberInRange(
+ json.getLong("expiry"),
+ MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS,
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS));
+ sourceBuilder.setExpiryTime(timestamp + offset);
+ } else {
+ sourceBuilder.setExpiryTime(
+ timestamp
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS));
+ }
+ if (!json.isNull("priority")) {
+ sourceBuilder.setPriority(json.getLong("priority"));
+ }
+ if (!json.isNull("debug_key")) {
+ sourceBuilder.setDebugKey(new UnsignedLong(json.getString("debug_key")));
+ }
+ if (!json.isNull("filter_data")) {
+ sourceBuilder.setFilterData(json.getJSONObject("filter_data").toString());
+ }
+ sourceBuilder.setWebDestination(Web.topPrivateDomainAndScheme(
+ Uri.parse(json.getString("destination"))).get());
+ if (!json.isNull("aggregation_keys")) {
+ sourceBuilder.setAggregateSource(json.getJSONObject("aggregation_keys").toString());
+ }
+ return sourceBuilder.build();
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, "Failed to parse source registration. %s", e);
+ return null;
+ }
+ }
+
+ private static Trigger getTrigger(String destination, long timestamp, String uri,
+ RegistrationRequest request, Map<String, List<String>> headers) {
+ try {
+ Trigger.Builder triggerBuilder = new Trigger.Builder();
+ String enrollmentId =
+ Enrollment.maybeGetEnrollmentId(Uri.parse(uri), sEnrollmentDao).get();
+ triggerBuilder.setEnrollmentId(enrollmentId);
+ triggerBuilder.setAttributionDestination(Uri.parse(destination));
+ triggerBuilder.setDestinationType(EventSurfaceType.WEB);
+ triggerBuilder.setTriggerTime(timestamp);
+ triggerBuilder.setRegistrant(getRegistrant(request.getPackageName()));
+ List<String> field = headers.get("Attribution-Reporting-Register-Trigger");
+ JSONObject json = new JSONObject(field.get(0));
+ if (!json.isNull("event_trigger_data")) {
+ triggerBuilder.setEventTriggers(json.getJSONArray("event_trigger_data").toString());
+ }
+ if (!json.isNull("aggregatable_trigger_data")) {
+ triggerBuilder.setAggregateTriggerData(
+ json.getJSONArray("aggregatable_trigger_data").toString());
+ }
+ if (!json.isNull("aggregatable_values")) {
+ triggerBuilder.setAggregateValues(
+ json.getJSONObject("aggregatable_values").toString());
+ }
+ if (!json.isNull("filters")) {
+ triggerBuilder.setFilters(json.getString("filters"));
+ }
+ if (!json.isNull("not_filters")) {
+ triggerBuilder.setNotFilters(json.getString("not_filters"));
+ }
+ if (!json.isNull("debug_key")) {
+ triggerBuilder.setDebugKey(new UnsignedLong(json.getString("debug_key")));
+ }
+ return triggerBuilder.build();
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, "Failed to parse trigger registration. %s", e);
+ return null;
+ }
+ }
+
+ private static Source.SourceType getSourceType(RegistrationRequest request) {
+ return request.getInputEvent() == null
+ ? Source.SourceType.EVENT
+ : Source.SourceType.NAVIGATION;
+ }
+
+ private static Uri getRegistrant(String packageName) {
+ return Uri.parse(ANDROID_APP_SCHEME + "://" + packageName);
+ }
+
+ private static long extractValidNumberInRange(long value, long lowerLimit, long upperLimit) {
+ if (value < lowerLimit) {
+ return lowerLimit;
+ } else if (value > upperLimit) {
+ return upperLimit;
+ }
+
+ return value;
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockStatic.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockStatic.java
new file mode 100644
index 000000000..372558b3d
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockStatic.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.measurement;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.modules.utils.testing.StaticMockFixture;
+import com.android.modules.utils.testing.StaticMockFixtureRule;
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import org.mockito.stubbing.Answer;
+
+/**
+ * Combines TestableDeviceConfig with other needed static mocks.
+ */
+public final class E2EMockStatic implements StaticMockFixture {
+
+ private final E2ETest.PrivacyParamsProvider mPrivacyParams;
+
+ public E2EMockStatic(E2ETest.PrivacyParamsProvider privacyParamsProvider) {
+ mPrivacyParams = privacyParamsProvider;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ sessionBuilder.spyStatic(PrivacyParams.class);
+ return sessionBuilder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUpMockBehaviors() {
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getMaxAttributionPerRateLimitWindow())
+ .when(() -> PrivacyParams.getMaxAttributionPerRateLimitWindow());
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getNavigationTriggerDataCardinality())
+ .when(() -> PrivacyParams.getNavigationTriggerDataCardinality());
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution())
+ .when(() -> PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution());
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource())
+ .when(() -> PrivacyParams
+ .getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource());
+ doAnswer((Answer<Integer>) invocation ->
+ mPrivacyParams.getMaxDistinctEnrollmentsPerPublisherXDestinationInSource())
+ .when(() -> PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInSource());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown() { }
+
+ public static class E2EMockStaticRule extends StaticMockFixtureRule {
+ public E2EMockStaticRule(E2ETest.PrivacyParamsProvider privacyParamsProvider) {
+ super(TestableDeviceConfig::new, () -> new E2EMockStatic(privacyParamsProvider));
+ }
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java
index d51526d28..07ece39b7 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2EMockTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement;
+import static com.android.adservices.ResultCode.RESULT_OK;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
@@ -23,24 +25,41 @@ import static org.mockito.Mockito.when;
import android.net.Uri;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.HpkeJni;
+import com.android.adservices.data.DbTestUtil;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
+import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.measurement.actions.Action;
+import com.android.adservices.service.measurement.actions.AggregateReportingJob;
+import com.android.adservices.service.measurement.actions.EventReportingJob;
+import com.android.adservices.service.measurement.actions.InstallApp;
import com.android.adservices.service.measurement.actions.RegisterSource;
import com.android.adservices.service.measurement.actions.RegisterTrigger;
import com.android.adservices.service.measurement.actions.RegisterWebSource;
import com.android.adservices.service.measurement.actions.RegisterWebTrigger;
import com.android.adservices.service.measurement.actions.ReportObjects;
-import com.android.adservices.service.measurement.actions.ReportingJob;
+import com.android.adservices.service.measurement.actions.UninstallApp;
+import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
+import com.android.adservices.service.measurement.attribution.AttributionJobHandlerWrapper;
import com.android.adservices.service.measurement.inputverification.ClickVerifier;
-import com.android.adservices.service.measurement.registration.SourceFetcher;
-import com.android.adservices.service.measurement.registration.TriggerFetcher;
+import com.android.adservices.service.measurement.registration.AsyncSourceFetcher;
+import com.android.adservices.service.measurement.registration.AsyncTriggerFetcher;
import com.android.adservices.service.measurement.reporting.AggregateReportingJobHandlerWrapper;
import com.android.adservices.service.measurement.reporting.EventReportingJobHandlerWrapper;
+import com.android.adservices.service.stats.AdServicesLoggerImpl;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Rule;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
@@ -52,8 +71,11 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.HttpsURLConnection;
@@ -71,73 +93,184 @@ import co.nstant.in.cbor.model.UnicodeString;
* <p>Consider @RunWith(Parameterized.class)
*/
public abstract class E2EMockTest extends E2ETest {
- SourceFetcher mSourceFetcher;
- TriggerFetcher mTriggerFetcher;
+
+ static EnrollmentDao sEnrollmentDao =
+ new EnrollmentDao(
+ ApplicationProvider.getApplicationContext(), DbTestUtil.getDbHelperForTest());
+ static DatastoreManager sDatastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+
+ // Class extensions may choose to disable or enable added noise.
+ AttributionJobHandlerWrapper mAttributionHelper;
+ MeasurementImpl mMeasurementImpl;
ClickVerifier mClickVerifier;
+ MeasurementDataDeleter mMeasurementDataDeleter;
Flags mFlags;
+ AsyncRegistrationQueueRunner mAsyncRegistrationQueueRunner;
+ AsyncSourceFetcher mAsyncSourceFetcher;
+ AsyncTriggerFetcher mAsyncTriggerFetcher;
- E2EMockTest(Collection<Action> actions, ReportObjects expectedOutput, String name) {
+ private static final long MAX_RECORDS_PROCESSED = 20L;
+ private static final short ASYNC_REG_RETRY_LIMIT = 1;
+ private final AtomicInteger mEnrollmentCount = new AtomicInteger();
+ private final Set<String> mSeenUris = new HashSet<>();
+ private final Map<String, String> mUriToEnrollmentId = new HashMap<>();
+
+ @Rule
+ public final E2EMockStatic.E2EMockStaticRule mE2EMockStaticRule;
+
+ E2EMockTest(Collection<Action> actions, ReportObjects expectedOutput,
+ PrivacyParamsProvider privacyParamsProvider, String name) {
super(actions, expectedOutput, name);
- mSourceFetcher = Mockito.spy(new SourceFetcher(sContext));
- mTriggerFetcher = Mockito.spy(new TriggerFetcher(sContext));
mClickVerifier = Mockito.mock(ClickVerifier.class);
mFlags = FlagsFactory.getFlagsForTest();
+ mE2EMockStaticRule = new E2EMockStatic.E2EMockStaticRule(privacyParamsProvider);
+ mMeasurementDataDeleter = Mockito.spy(new MeasurementDataDeleter(sDatastoreManager));
+ mAsyncSourceFetcher =
+ Mockito.spy(
+ new AsyncSourceFetcher(
+ sEnrollmentDao,
+ FlagsFactory.getFlagsForTest(),
+ AdServicesLoggerImpl.getInstance()));
+ mAsyncTriggerFetcher =
+ Mockito.spy(
+ new AsyncTriggerFetcher(
+ sEnrollmentDao,
+ FlagsFactory.getFlagsForTest(),
+ AdServicesLoggerImpl.getInstance()));
when(mClickVerifier.isInputEventVerifiable(any(), anyLong())).thenReturn(true);
}
@Override
void prepareRegistrationServer(RegisterSource sourceRegistration) throws IOException {
for (String uri : sourceRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
HttpsURLConnection urlConnection = mock(HttpsURLConnection.class);
when(urlConnection.getResponseCode()).thenReturn(200);
Answer<Map<String, List<String>>> headerFieldsMockAnswer =
invocation -> getNextResponse(sourceRegistration.mUriToResponseHeadersMap, uri);
Mockito.doAnswer(headerFieldsMockAnswer).when(urlConnection).getHeaderFields();
- Mockito.doReturn(urlConnection).when(mSourceFetcher).openUrl(new URL(uri));
+ Mockito.doReturn(urlConnection).when(mAsyncSourceFetcher).openUrl(new URL(uri));
}
}
@Override
- void prepareRegistrationServer(RegisterTrigger triggerRegistration)
- throws IOException {
+ void prepareRegistrationServer(RegisterTrigger triggerRegistration) throws IOException {
for (String uri : triggerRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
HttpsURLConnection urlConnection = mock(HttpsURLConnection.class);
when(urlConnection.getResponseCode()).thenReturn(200);
Answer<Map<String, List<String>>> headerFieldsMockAnswer =
invocation ->
getNextResponse(triggerRegistration.mUriToResponseHeadersMap, uri);
Mockito.doAnswer(headerFieldsMockAnswer).when(urlConnection).getHeaderFields();
- Mockito.doReturn(urlConnection).when(mTriggerFetcher).openUrl(new URL(uri));
+ Mockito.doReturn(urlConnection).when(mAsyncTriggerFetcher).openUrl(new URL(uri));
}
}
@Override
void prepareRegistrationServer(RegisterWebSource sourceRegistration) throws IOException {
for (String uri : sourceRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
HttpsURLConnection urlConnection = mock(HttpsURLConnection.class);
when(urlConnection.getResponseCode()).thenReturn(200);
Answer<Map<String, List<String>>> headerFieldsMockAnswer =
invocation -> getNextResponse(sourceRegistration.mUriToResponseHeadersMap, uri);
Mockito.doAnswer(headerFieldsMockAnswer).when(urlConnection).getHeaderFields();
- Mockito.doReturn(urlConnection).when(mSourceFetcher).openUrl(new URL(uri));
+ Mockito.doReturn(urlConnection).when(mAsyncSourceFetcher).openUrl(new URL(uri));
}
}
@Override
void prepareRegistrationServer(RegisterWebTrigger triggerRegistration) throws IOException {
for (String uri : triggerRegistration.mUriToResponseHeadersMap.keySet()) {
+ updateEnrollment(uri);
HttpsURLConnection urlConnection = mock(HttpsURLConnection.class);
when(urlConnection.getResponseCode()).thenReturn(200);
Answer<Map<String, List<String>>> headerFieldsMockAnswer =
invocation ->
getNextResponse(triggerRegistration.mUriToResponseHeadersMap, uri);
Mockito.doAnswer(headerFieldsMockAnswer).when(urlConnection).getHeaderFields();
- Mockito.doReturn(urlConnection).when(mTriggerFetcher).openUrl(new URL(uri));
+ Mockito.doReturn(urlConnection).when(mAsyncTriggerFetcher).openUrl(new URL(uri));
}
}
@Override
- void processAction(ReportingJob reportingJob) throws IOException, JSONException {
+ void processAction(RegisterSource sourceRegistration) throws IOException {
+ prepareRegistrationServer(sourceRegistration);
+ Assert.assertEquals(
+ "MeasurementImpl.register source failed",
+ RESULT_OK,
+ mMeasurementImpl.register(
+ sourceRegistration.mRegistrationRequest, sourceRegistration.mTimestamp));
+ mAsyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(
+ MAX_RECORDS_PROCESSED, ASYNC_REG_RETRY_LIMIT);
+ }
+
+ @Override
+ void processAction(RegisterWebSource sourceRegistration) throws IOException {
+ prepareRegistrationServer(sourceRegistration);
+ Assert.assertEquals(
+ "MeasurementImpl.registerWebSource failed",
+ RESULT_OK,
+ mMeasurementImpl.registerWebSource(
+ sourceRegistration.mRegistrationRequest, sourceRegistration.mTimestamp));
+ mAsyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(
+ MAX_RECORDS_PROCESSED, ASYNC_REG_RETRY_LIMIT);
+ }
+
+ @Override
+ void processAction(RegisterTrigger triggerRegistration) throws IOException {
+ prepareRegistrationServer(triggerRegistration);
+ Assert.assertEquals(
+ "MeasurementImpl.register trigger failed",
+ RESULT_OK,
+ mMeasurementImpl.register(
+ triggerRegistration.mRegistrationRequest, triggerRegistration.mTimestamp));
+ mAsyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(
+ MAX_RECORDS_PROCESSED, ASYNC_REG_RETRY_LIMIT);
+ Assert.assertTrue("AttributionJobHandler.performPendingAttributions returned false",
+ mAttributionHelper.performPendingAttributions());
+ }
+
+ @Override
+ void processAction(RegisterWebTrigger triggerRegistration) throws IOException {
+ prepareRegistrationServer(triggerRegistration);
+ Assert.assertEquals(
+ "MeasurementImpl.registerWebTrigger failed",
+ RESULT_OK,
+ mMeasurementImpl.registerWebTrigger(
+ triggerRegistration.mRegistrationRequest, triggerRegistration.mTimestamp));
+ mAsyncRegistrationQueueRunner.runAsyncRegistrationQueueWorker(
+ MAX_RECORDS_PROCESSED, ASYNC_REG_RETRY_LIMIT);
+ Assert.assertTrue(
+ "AttributionJobHandler.performPendingAttributions returned false",
+ mAttributionHelper.performPendingAttributions());
+ }
+
+ @Override
+ void processAction(InstallApp installApp) {
+ Assert.assertTrue(
+ "measurementDao.doInstallAttribution failed",
+ sDatastoreManager.runInTransaction(
+ measurementDao ->
+ measurementDao.doInstallAttribution(
+ installApp.mUri, installApp.mTimestamp)));
+ }
+
+ @Override
+ void processAction(UninstallApp uninstallApp) {
+ Assert.assertTrue(
+ "measurementDao.undoInstallAttribution failed",
+ sDatastoreManager.runInTransaction(
+ measurementDao -> {
+ measurementDao.deleteAppRecords(uninstallApp.mUri);
+ measurementDao.undoInstallAttribution(uninstallApp.mUri);
+ }));
+ }
+
+ @Override
+ void processAction(EventReportingJob reportingJob) throws IOException, JSONException {
Object[] eventCaptures = EventReportingJobHandlerWrapper
.spyPerformScheduledPendingReportsInWindow(
sEnrollmentDao,
@@ -146,19 +279,22 @@ public abstract class E2EMockTest extends E2ETest {
- SystemHealthParams.MAX_EVENT_REPORT_UPLOAD_RETRY_WINDOW_MS,
reportingJob.mTimestamp);
+ processEventReports(
+ (List<EventReport>) eventCaptures[0],
+ (List<Uri>) eventCaptures[1],
+ (List<JSONObject>) eventCaptures[2]);
+ }
+
+ @Override
+ void processAction(AggregateReportingJob reportingJob) throws IOException, JSONException {
Object[] aggregateCaptures = AggregateReportingJobHandlerWrapper
.spyPerformScheduledPendingReportsInWindow(
sEnrollmentDao,
sDatastoreManager,
reportingJob.mTimestamp
- - SystemHealthParams.MAX_EVENT_REPORT_UPLOAD_RETRY_WINDOW_MS,
+ - SystemHealthParams.MAX_AGGREGATE_REPORT_UPLOAD_RETRY_WINDOW_MS,
reportingJob.mTimestamp);
- processEventReports(
- (List<EventReport>) eventCaptures[0],
- (List<Uri>) eventCaptures[1],
- (List<JSONObject>) eventCaptures[2]);
-
processAggregateReports(
(List<Uri>) aggregateCaptures[0],
(List<JSONObject>) aggregateCaptures[1]);
@@ -198,8 +334,8 @@ public abstract class E2EMockTest extends E2ETest {
for (int i = 0; i < destinations.size(); i++) {
JSONObject sharedInfo = new JSONObject(payloads.get(i).getString("shared_info"));
result.add(new JSONObject()
- .put(TestFormatJsonMapping.REPORT_TIME_KEY,
- sharedInfo.getLong("scheduled_report_time") * 1000)
+ .put(TestFormatJsonMapping.REPORT_TIME_KEY, String.valueOf(
+ sharedInfo.getLong("scheduled_report_time") * 1000))
.put(TestFormatJsonMapping.REPORT_TO_KEY, destinations.get(i).toString())
.put(TestFormatJsonMapping.PAYLOAD_KEY,
getAggregatablePayloadForTest(sharedInfo, payloads.get(i))));
@@ -212,37 +348,55 @@ public abstract class E2EMockTest extends E2ETest {
String payload =
data.getJSONArray("aggregation_service_payloads")
.getJSONObject(0)
- .getString("debug_cleartext_payload");
- return new JSONObject()
- .put(
- AggregateReportPayloadKeys.ATTRIBUTION_DESTINATION,
- sharedInfo.getString("attribution_destination"))
- .put(AggregateReportPayloadKeys.HISTOGRAMS, getAggregateHistograms(payload));
+ .getString("payload");
+
+ final byte[] decryptedPayload =
+ HpkeJni.decrypt(
+ decode(AggregateCryptoFixture.getPrivateKeyBase64()),
+ decode(payload),
+ (AggregateCryptoFixture.getSharedInfoPrefix() + sharedInfo.toString())
+ .getBytes());
+
+ String sourceDebugKey = data.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY);
+ String triggerDebugKey = data.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY);
+ JSONObject aggregateJson =
+ new JSONObject()
+ .put(
+ AggregateReportPayloadKeys.ATTRIBUTION_DESTINATION,
+ sharedInfo.getString("attribution_destination"))
+ .put(
+ AggregateReportPayloadKeys.HISTOGRAMS,
+ getAggregateHistograms(decryptedPayload));
+ if (!sourceDebugKey.isEmpty()) {
+ aggregateJson.put(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, sourceDebugKey);
+ }
+ if (!triggerDebugKey.isEmpty()) {
+ aggregateJson.put(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, triggerDebugKey);
+ }
+ return aggregateJson;
}
- private static JSONArray getAggregateHistograms(String payloadJsonBase64) throws JSONException {
+ private static JSONArray getAggregateHistograms(byte[] encodedCborPayload)
+ throws JSONException {
List<JSONObject> result = new ArrayList<>();
try {
- final byte[] payloadJson = Base64.getDecoder().decode(payloadJsonBase64);
final List<DataItem> dataItems =
- new CborDecoder(new ByteArrayInputStream(payloadJson)).decode();
+ new CborDecoder(new ByteArrayInputStream(encodedCborPayload)).decode();
final co.nstant.in.cbor.model.Map payload =
(co.nstant.in.cbor.model.Map) dataItems.get(0);
final Array payloadArray = (Array) payload.get(new UnicodeString("data"));
for (DataItem i : payloadArray.getDataItems()) {
co.nstant.in.cbor.model.Map m = (co.nstant.in.cbor.model.Map) i;
+ Object value =
+ "0x"
+ + new BigInteger(
+ ((ByteString) m.get(new UnicodeString("bucket")))
+ .getBytes())
+ .toString(16);
result.add(
new JSONObject()
- .put(
- AggregateHistogramKeys.BUCKET,
- new BigInteger(
- ((ByteString)
- m.get(
- new UnicodeString(
- "bucket")))
- .getBytes())
- .toString())
+ .put(AggregateHistogramKeys.BUCKET, value)
.put(
AggregateHistogramKeys.VALUE,
new BigInteger(
@@ -251,7 +405,7 @@ public abstract class E2EMockTest extends E2ETest {
new UnicodeString(
"value")))
.getBytes())
- .toString()));
+ .intValue()));
}
} catch (CborException e) {
throw new JSONException(e);
@@ -260,9 +414,53 @@ public abstract class E2EMockTest extends E2ETest {
return new JSONArray(result);
}
- private static Map<String, List<String>> getNextResponse(
+ protected static Map<String, List<String>> getNextResponse(
Map<String, List<Map<String, List<String>>>> uriToResponseHeadersMap, String uri) {
List<Map<String, List<String>>> responseList = uriToResponseHeadersMap.get(uri);
return responseList.remove(0);
}
+
+ void updateEnrollment(String uri) {
+ if (mSeenUris.contains(uri)) {
+ return;
+ }
+ mSeenUris.add(uri);
+ String enrollmentId = getEnrollmentId(uri);
+ Set<String> attributionRegistrationUrls;
+ EnrollmentData enrollmentData = sEnrollmentDao.getEnrollmentData(enrollmentId);
+ if (enrollmentData != null) {
+ sEnrollmentDao.delete(enrollmentId);
+ attributionRegistrationUrls = new HashSet<>(
+ enrollmentData.getAttributionSourceRegistrationUrl());
+ attributionRegistrationUrls.addAll(
+ enrollmentData.getAttributionTriggerRegistrationUrl());
+ attributionRegistrationUrls.add(uri);
+ } else {
+ attributionRegistrationUrls = Set.of(uri);
+ }
+ Uri registrationUri = Uri.parse(uri);
+ String reportingUrl = registrationUri.getScheme() + "://" + registrationUri.getAuthority();
+ insertEnrollment(enrollmentId, reportingUrl, new ArrayList<>(attributionRegistrationUrls));
+ }
+
+ private void insertEnrollment(String enrollmentId, String reportingUrl,
+ List<String> attributionRegistrationUrls) {
+ EnrollmentData enrollmentData = new EnrollmentData.Builder()
+ .setEnrollmentId(enrollmentId)
+ .setAttributionSourceRegistrationUrl(attributionRegistrationUrls)
+ .setAttributionTriggerRegistrationUrl(attributionRegistrationUrls)
+ .setAttributionReportingUrl(List.of(reportingUrl))
+ .build();
+ Assert.assertTrue(sEnrollmentDao.insert(enrollmentData));
+ }
+
+ private String getEnrollmentId(String uri) {
+ String authority = Uri.parse(uri).getAuthority();
+ return mUriToEnrollmentId.computeIfAbsent(authority, k ->
+ "enrollment-id-" + mEnrollmentCount.incrementAndGet());
+ }
+
+ private static byte[] decode(String value) {
+ return Base64.getDecoder().decode(value.getBytes());
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
index be05aa927..ca3568075 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
@@ -19,11 +19,14 @@ package com.android.adservices.service.measurement;
import static android.view.MotionEvent.ACTION_BUTTON_PRESS;
import static android.view.MotionEvent.obtain;
-import static com.android.adservices.ResultCode.RESULT_OK;
+import static com.android.adservices.service.measurement.reporting.AggregateReportSender.AGGREGATE_ATTRIBUTION_REPORT_URI_PATH;
+import static com.android.adservices.service.measurement.reporting.EventReportSender.EVENT_ATTRIBUTION_REPORT_URI_PATH;
import android.content.AttributionSource;
import android.content.Context;
import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -33,22 +36,17 @@ import android.view.MotionEvent.PointerProperties;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
-import com.android.adservices.data.DbHelper;
-import com.android.adservices.data.enrollment.EnrollmentDao;
-import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
-import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.service.measurement.actions.Action;
+import com.android.adservices.service.measurement.actions.AggregateReportingJob;
+import com.android.adservices.service.measurement.actions.EventReportingJob;
import com.android.adservices.service.measurement.actions.InstallApp;
import com.android.adservices.service.measurement.actions.RegisterSource;
import com.android.adservices.service.measurement.actions.RegisterTrigger;
import com.android.adservices.service.measurement.actions.RegisterWebSource;
import com.android.adservices.service.measurement.actions.RegisterWebTrigger;
import com.android.adservices.service.measurement.actions.ReportObjects;
-import com.android.adservices.service.measurement.actions.ReportingJob;
import com.android.adservices.service.measurement.actions.UninstallApp;
-import com.android.adservices.service.measurement.attribution.AttributionJobHandlerWrapper;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import com.google.common.collect.ImmutableList;
@@ -56,7 +54,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Assert;
-import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
@@ -73,7 +70,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
@@ -84,61 +80,24 @@ import java.util.concurrent.TimeUnit;
* Consider @RunWith(Parameterized.class)
*/
public abstract class E2ETest {
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
// Used to fuzzy-match expected report (not delivery) time
private static final long REPORT_TIME_EPSILON = TimeUnit.HOURS.toMillis(2);
- private static final EnrollmentData AD_TECH_1 =
- new EnrollmentData.Builder()
- .setEnrollmentId(UUID.randomUUID().toString())
- .setCompanyId("ad-tech-1")
- .setSdkNames("sdk")
- .setAttributionSourceRegistrationUrl(Arrays.asList("https://www.ad-tech1.com"))
- .setAttributionTriggerRegistrationUrl(Arrays.asList("https://www.ad-tech1.com"))
- .setAttributionReportingUrl(Arrays.asList("https://www.ad-tech1.com"))
- .setRemarketingResponseBasedRegistrationUrl(
- Arrays.asList("https://www.ad-tech1.com"))
- .setEncryptionKeyUrl(Arrays.asList("https://www.ad-tech1.com/keys"))
- .build();
- private static final EnrollmentData AD_TECH_2 =
- new EnrollmentData.Builder()
- .setEnrollmentId(UUID.randomUUID().toString())
- .setCompanyId("ad-tech-2")
- .setSdkNames("sdk")
- .setAttributionSourceRegistrationUrl(Arrays.asList("https://www.ad-tech2.com"))
- .setAttributionTriggerRegistrationUrl(Arrays.asList("https://www.ad-tech2.com"))
- .setAttributionReportingUrl(Arrays.asList("https://www.ad-tech2.com"))
- .setRemarketingResponseBasedRegistrationUrl(
- Arrays.asList("https://www.ad-tech2.com"))
- .setEncryptionKeyUrl(Arrays.asList("https://www.ad-tech2.com/keys"))
- .build();
- private static final EnrollmentData AD_TECH_3 =
- new EnrollmentData.Builder()
- .setEnrollmentId(UUID.randomUUID().toString())
- .setCompanyId("ad-tech-3")
- .setSdkNames("sdk")
- .setAttributionSourceRegistrationUrl(Arrays.asList("https://www.ad-tech3.com"))
- .setAttributionTriggerRegistrationUrl(Arrays.asList("https://www.ad-tech3.com"))
- .setAttributionReportingUrl(Arrays.asList("https://www.ad-tech3.com"))
- .setRemarketingResponseBasedRegistrationUrl(
- Arrays.asList("https://www.ad-tech3.com"))
- .setEncryptionKeyUrl(Arrays.asList("https://www.ad-tech3.com/keys"))
- .build();
static final Context sContext = ApplicationProvider.getApplicationContext();
- static final EnrollmentDao sEnrollmentDao = EnrollmentDao.getInstance(
- ApplicationProvider.getApplicationContext());
- static final DatastoreManager sDatastoreManager = DatastoreManagerFactory.getDatastoreManager(
- ApplicationProvider.getApplicationContext());
private final Collection<Action> mActionsList;
final ReportObjects mExpectedOutput;
// Extenders of the class populate in their own ways this container for actual output.
final ReportObjects mActualOutput;
- // Class extensions may choose to disable or enable added noise.
- AttributionJobHandlerWrapper mAttributionHelper;
- MeasurementImpl mMeasurementImpl;
+
+ enum ReportType {
+ EVENT,
+ AGGREGATE
+ }
+
+ private enum OutputType {
+ EXPECTED,
+ ACTUAL
+ }
private interface EventReportPayloadKeys {
// Keys used to compare actual with expected output
@@ -153,6 +112,8 @@ public abstract class E2ETest {
interface AggregateReportPayloadKeys {
String ATTRIBUTION_DESTINATION = "attribution_destination";
String HISTOGRAMS = "histograms";
+ String SOURCE_DEBUG_KEY = "source_debug_key";
+ String TRIGGER_DEBUG_KEY = "trigger_debug_key";
}
interface AggregateHistogramKeys {
@@ -161,6 +122,7 @@ public abstract class E2ETest {
}
public interface TestFormatJsonMapping {
+ String API_CONFIG_KEY = "api_config";
String TEST_INPUT_KEY = "input";
String TEST_OUTPUT_KEY = "output";
String SOURCE_REGISTRATIONS_KEY = "sources";
@@ -174,12 +136,14 @@ public abstract class E2ETest {
String URI_TO_RESPONSE_HEADERS_RESPONSE_KEY = "response";
String REGISTRATION_REQUEST_KEY = "registration_request";
String ATTRIBUTION_SOURCE_KEY = "registrant";
+ String ATTRIBUTION_SOURCE_DEFAULT = "com.interop.app";
String SOURCE_TOP_ORIGIN_URI_KEY = "source_origin";
String TRIGGER_TOP_ORIGIN_URI_KEY = "destination_origin";
String SOURCE_APP_DESTINATION_URI_KEY = "app_destination";
String SOURCE_WEB_DESTINATION_URI_KEY = "web_destination";
String SOURCE_VERIFIED_DESTINATION_URI_KEY = "verified_destination";
String REGISTRATION_URI_KEY = "attribution_src_url";
+ String IS_ADID_PERMISSION_GRANTED_KEY = "is_adid_permission_granted";
String INPUT_EVENT_KEY = "source_type";
String SOURCE_VIEW_TYPE = "event";
String TIMESTAMP_KEY = "timestamp";
@@ -192,7 +156,90 @@ public abstract class E2ETest {
String REPORT_TIME_KEY = "report_time";
String REPORT_TO_KEY = "report_url";
String PAYLOAD_KEY = "payload";
- String DEBUG_KEY = "debug_key";
+ }
+
+ private interface ApiConfigKeys {
+ String RATE_LIMIT_MAX_ATTRIBUTIONS = "rate_limit_max_attributions";
+ String NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY =
+ "navigation_source_trigger_data_cardinality";
+ String RATE_LIMIT_MAX_ATTRIBUTION_REPORTING_ORIGINS =
+ "rate_limit_max_attribution_reporting_origins";
+ String MAX_DESTINATIONS_PER_SOURCE_SITE_REPORTING_ORIGIN =
+ "max_destinations_per_source_site_reporting_origin";
+ String RATE_LIMIT_MAX_SOURCE_REGISTRATION_REPORTING_ORIGINS =
+ "rate_limit_max_source_registration_reporting_origins";
+ }
+
+ public static class PrivacyParamsProvider {
+ private Integer mMaxAttributionPerRateLimitWindow;
+ private Integer mNavigationTriggerDataCardinality;
+ private Integer mMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution;
+ private Integer mMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource;
+ private Integer mMaxDistinctEnrollmentsPerPublisherXDestinationInSource;
+
+ public PrivacyParamsProvider(JSONObject json) throws JSONException {
+ if (!json.isNull(ApiConfigKeys.RATE_LIMIT_MAX_ATTRIBUTIONS)) {
+ mMaxAttributionPerRateLimitWindow = json.getInt(
+ ApiConfigKeys.RATE_LIMIT_MAX_ATTRIBUTIONS);
+ } else {
+ mMaxAttributionPerRateLimitWindow =
+ PrivacyParams.getMaxAttributionPerRateLimitWindow();
+ }
+ if (!json.isNull(ApiConfigKeys.NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY)) {
+ mNavigationTriggerDataCardinality = json.getInt(
+ ApiConfigKeys.NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY);
+ } else {
+ mNavigationTriggerDataCardinality =
+ PrivacyParams.getNavigationTriggerDataCardinality();
+ }
+ if (!json.isNull(ApiConfigKeys
+ .RATE_LIMIT_MAX_ATTRIBUTION_REPORTING_ORIGINS)) {
+ mMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution = json.getInt(
+ ApiConfigKeys.RATE_LIMIT_MAX_ATTRIBUTION_REPORTING_ORIGINS);
+ } else {
+ mMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution =
+ PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution();
+ }
+ if (!json.isNull(ApiConfigKeys
+ .MAX_DESTINATIONS_PER_SOURCE_SITE_REPORTING_ORIGIN)) {
+ mMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource = json.getInt(
+ ApiConfigKeys.MAX_DESTINATIONS_PER_SOURCE_SITE_REPORTING_ORIGIN);
+ } else {
+ mMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource =
+ PrivacyParams
+ .getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource();
+ }
+ if (!json.isNull(ApiConfigKeys
+ .RATE_LIMIT_MAX_SOURCE_REGISTRATION_REPORTING_ORIGINS)) {
+ mMaxDistinctEnrollmentsPerPublisherXDestinationInSource = json.getInt(
+ ApiConfigKeys.RATE_LIMIT_MAX_SOURCE_REGISTRATION_REPORTING_ORIGINS);
+ } else {
+ mMaxDistinctEnrollmentsPerPublisherXDestinationInSource =
+ PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInSource();
+ }
+ }
+
+ public Integer getMaxAttributionPerRateLimitWindow() {
+ return mMaxAttributionPerRateLimitWindow;
+ }
+
+ public Integer getNavigationTriggerDataCardinality() {
+ return mNavigationTriggerDataCardinality;
+ }
+
+ public Integer getMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution() {
+ return mMaxDistinctEnrollmentsPerPublisherXDestinationInAttribution;
+ }
+
+ public Integer getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource() {
+ return mMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource;
+ }
+
+ public Integer getMaxDistinctEnrollmentsPerPublisherXDestinationInSource() {
+ return mMaxDistinctEnrollmentsPerPublisherXDestinationInSource;
+ }
}
static Collection<Object[]> data(String testDirName) throws IOException, JSONException {
@@ -205,8 +252,8 @@ public abstract class E2ETest {
return getTestCasesFrom(inputStreams, testDirectoryList);
}
- public static Map<String, List<Map<String, List<String>>>>
- getUriToResponseHeadersMap(JSONObject obj) throws JSONException {
+ public static Map<String, List<Map<String, List<String>>>> getUriToResponseHeadersMap(
+ JSONObject obj) throws JSONException {
JSONArray uriToResArray = obj.getJSONArray(
TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY);
Map<String, List<Map<String, List<String>>>> uriToResponseHeadersMap = new HashMap<>();
@@ -270,8 +317,16 @@ public abstract class E2ETest {
0 /*int flags*/);
}
+ static String getReportUrl(ReportType reportType, String origin) {
+ return origin
+ + "/"
+ + (reportType == ReportType.EVENT
+ ? EVENT_ATTRIBUTION_REPORT_URI_PATH
+ : AGGREGATE_ATTRIBUTION_REPORT_URI_PATH);
+ }
+
static void clearDatabase() {
- SQLiteDatabase db = DbHelper.getInstance(sContext).getWritableDatabase();
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().getWritableDatabase();
emptyTables(db);
}
@@ -286,8 +341,6 @@ public abstract class E2ETest {
@Test
public void runTest() throws IOException, JSONException {
clearDatabase();
- unseedEnrollment();
- seedEnrollment();
for (Action action : mActionsList) {
if (action instanceof RegisterSource) {
processAction((RegisterSource) action);
@@ -297,8 +350,10 @@ public abstract class E2ETest {
processAction((RegisterWebSource) action);
} else if (action instanceof RegisterWebTrigger) {
processAction((RegisterWebTrigger) action);
- } else if (action instanceof ReportingJob) {
- processAction((ReportingJob) action);
+ } else if (action instanceof EventReportingJob) {
+ processAction((EventReportingJob) action);
+ } else if (action instanceof AggregateReportingJob) {
+ processAction((AggregateReportingJob) action);
} else if (action instanceof InstallApp) {
processAction((InstallApp) action);
} else if (action instanceof UninstallApp) {
@@ -307,14 +362,20 @@ public abstract class E2ETest {
}
evaluateResults();
clearDatabase();
- unseedEnrollment();
}
/**
* The reporting job may be handled differently depending on whether network requests are mocked
* or a test server is used.
*/
- abstract void processAction(ReportingJob reportingJob) throws IOException, JSONException;
+ abstract void processAction(EventReportingJob reportingJob) throws IOException, JSONException;
+
+ /**
+ * The reporting job may be handled differently depending on whether network requests are mocked
+ * or a test server is used.
+ */
+ abstract void processAction(AggregateReportingJob reportingJob)
+ throws IOException, JSONException;
/**
* Override with HTTP response mocks, for example.
@@ -336,44 +397,57 @@ public abstract class E2ETest {
abstract void prepareRegistrationServer(RegisterWebTrigger triggerRegistration)
throws IOException;
- private static int hashForEventReportObject(JSONObject obj) {
+ private static int hashForEventReportObject(OutputType outputType, JSONObject obj) {
int n = EventReportPayloadKeys.STRINGS.size();
Object[] objArray = new Object[n + 2];
// We cannot use report time due to fuzzy matching between actual and expected output.
- objArray[0] = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
+ String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
+ objArray[0] =
+ outputType == OutputType.EXPECTED ? url : getReportUrl(ReportType.EVENT, url);
JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
- objArray[1] = payload.optDouble(EventReportPayloadKeys.DOUBLE, 0);
+ objArray[1] = normaliseDouble(payload.optDouble(EventReportPayloadKeys.DOUBLE, 0));
for (int i = 0; i < n; i++) {
objArray[i + 2] = payload.optString(EventReportPayloadKeys.STRINGS.get(i), "");
}
return Arrays.hashCode(objArray);
}
- private static int hashForAggregateReportObject(JSONObject obj) {
- Object[] objArray = new Object[3];
+ private static int hashForAggregateReportObject(OutputType outputType,
+ JSONObject obj) {
+ Object[] objArray = new Object[5];
// We cannot use report time due to fuzzy matching between actual and expected output.
- objArray[0] = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
+ String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
+ objArray[0] =
+ outputType == OutputType.EXPECTED ? url : getReportUrl(ReportType.AGGREGATE, url);
JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
objArray[1] = payload.optString(AggregateReportPayloadKeys.ATTRIBUTION_DESTINATION, "");
// To compare histograms, we already converted them to an ordered string of value pairs.
objArray[2] = getComparableHistograms(
payload.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS));
+ objArray[3] = payload.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, "");
+ objArray[4] = payload.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, "");
return Arrays.hashCode(objArray);
}
+ // Used in interop tests, where we have known discrepancies.
+ private static double normaliseDouble(double d) {
+ return d == 0.0024263D ? 0.0024D : d;
+ }
+
private static long reportTimeFrom(JSONObject obj) {
return obj.optLong(TestFormatJsonMapping.REPORT_TIME_KEY, 0);
}
- private static boolean matchReportTimeAndReportTo(JSONObject obj1, JSONObject obj2)
- throws JSONException {
+ // 'obj1' is the expected result, 'obj2' is the actual result.
+ private static boolean matchReportTimeAndReportTo(ReportType reportType, JSONObject obj1,
+ JSONObject obj2) throws JSONException {
if (Math.abs(obj1.getLong(TestFormatJsonMapping.REPORT_TIME_KEY)
- obj2.getLong(TestFormatJsonMapping.REPORT_TIME_KEY))
> REPORT_TIME_EPSILON) {
return false;
}
if (!obj1.getString(TestFormatJsonMapping.REPORT_TO_KEY).equals(
- obj2.getString(TestFormatJsonMapping.REPORT_TO_KEY))) {
+ getReportUrl(reportType, obj2.getString(TestFormatJsonMapping.REPORT_TO_KEY)))) {
return false;
}
return true;
@@ -383,8 +457,8 @@ public abstract class E2ETest {
throws JSONException {
JSONObject payload1 = obj1.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
JSONObject payload2 = obj2.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
- if (payload1.getDouble(EventReportPayloadKeys.DOUBLE)
- != payload2.getDouble(EventReportPayloadKeys.DOUBLE)) {
+ if (normaliseDouble(payload1.getDouble(EventReportPayloadKeys.DOUBLE))
+ != normaliseDouble(payload2.getDouble(EventReportPayloadKeys.DOUBLE))) {
return false;
}
for (String key : EventReportPayloadKeys.STRINGS) {
@@ -392,7 +466,7 @@ public abstract class E2ETest {
return false;
}
}
- return matchReportTimeAndReportTo(obj1, obj2);
+ return matchReportTimeAndReportTo(ReportType.EVENT, obj1, obj2);
}
private static boolean areEqualAggregateReportJsons(JSONObject obj1, JSONObject obj2)
@@ -403,12 +477,20 @@ public abstract class E2ETest {
payload2.optString(AggregateReportPayloadKeys.ATTRIBUTION_DESTINATION, ""))) {
return false;
}
+ if (!payload1.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, "")
+ .equals(payload2.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, ""))) {
+ return false;
+ }
+ if (!payload1.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, "")
+ .equals(payload2.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, ""))) {
+ return false;
+ }
JSONArray histograms1 = payload1.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS);
JSONArray histograms2 = payload2.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS);
if (!getComparableHistograms(histograms1).equals(getComparableHistograms(histograms2))) {
return false;
}
- return matchReportTimeAndReportTo(obj1, obj2);
+ return matchReportTimeAndReportTo(ReportType.AGGREGATE, obj1, obj2);
}
private static String getComparableHistograms(@Nullable JSONArray arr) {
@@ -429,20 +511,24 @@ public abstract class E2ETest {
}
}
- private static void sortEventReportObjects(List<JSONObject> eventReportObjects) {
+ private static void sortEventReportObjects(OutputType outputType,
+ List<JSONObject> eventReportObjects) {
eventReportObjects.sort(
// Report time can vary across implementations so cannot be included in the hash;
// they should be similarly ordered, however, so we can use them to sort.
Comparator.comparing(E2ETest::reportTimeFrom)
- .thenComparing(E2ETest::hashForEventReportObject));
+ .thenComparing(obj -> hashForEventReportObject(outputType, obj)));
}
- private static void sortAggregateReportObjects(List<JSONObject> aggregateReportObjects) {
+ private static void sortAggregateReportObjects(OutputType outputType,
+ List<JSONObject> aggregateReportObjects) {
aggregateReportObjects.sort(
- // Report time can vary across implementations so cannot be included in the hash;
- // they should be similarly ordered, however, so we can use them to sort.
- Comparator.comparing(E2ETest::reportTimeFrom)
- .thenComparing(E2ETest::hashForAggregateReportObject));
+ // Unlike event reports (sorted elsewhere in this file), aggregate reports are
+ // scheduled with randomised times, and using report time for sorting can result
+ // in unexpected variations in the sort order, depending on test timing. Without
+ // time ordering, we rely on other data across the reports to yield different
+ // hash codes.
+ Comparator.comparing(obj -> hashForAggregateReportObject(outputType, obj)));
}
private static boolean areEqual(ReportObjects p1, ReportObjects p2) throws JSONException {
@@ -467,13 +553,22 @@ public abstract class E2ETest {
private static String getTestFailureMessage(ReportObjects expectedOutput,
ReportObjects actualOutput) {
- return String.format("Actual output does not match expected.\n\n"
- + "(Note that report IDs are ignored in comparisons since they are not known in"
- + " advance.)\n\nEvent report objects:\n%s\n\n"
- + "Expected aggregate report objects: %s\n\n"
- + "Actual aggregate report objects: %s\n",
- prettify(expectedOutput.mEventReportObjects, actualOutput.mEventReportObjects),
- expectedOutput.mAggregateReportObjects, actualOutput.mAggregateReportObjects);
+ return String.format(
+ "Actual output does not match expected.\n\n"
+ + "(Note that displayed randomized_trigger_rate and report_url are not"
+ + " normalised.\n"
+ + "Note that report IDs are ignored in comparisons since they are not"
+ + " known in advance.)\n\n"
+ + "Event report objects:\n"
+ + "%s\n\n"
+ + "Expected aggregate report objects: %s\n\n"
+ + "Actual aggregate report objects: %s\n",
+ prettify(
+ expectedOutput.mEventReportObjects,
+ actualOutput.mEventReportObjects),
+ expectedOutput.mAggregateReportObjects,
+ actualOutput.mAggregateReportObjects)
+ + getDatastoreState();
}
private static String prettify(List<JSONObject> expected, List<JSONObject> actual) {
@@ -502,6 +597,11 @@ public abstract class E2ETest {
.append(" ::: ")
.append(obj2.optString(TestFormatJsonMapping.REPORT_TIME_KEY))
.append("\n");
+ result.append(TestFormatJsonMapping.REPORT_TO_KEY + ": ")
+ .append(obj1.optString(TestFormatJsonMapping.REPORT_TO_KEY))
+ .append(" ::: ")
+ .append(obj2.optString(TestFormatJsonMapping.REPORT_TO_KEY))
+ .append("\n");
JSONObject payload1 = obj1.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
JSONObject payload2 = obj2.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
for (String key : EventReportPayloadKeys.STRINGS) {
@@ -535,6 +635,47 @@ public abstract class E2ETest {
return result.toString();
}
+ protected static String getDatastoreState() {
+ StringBuilder result = new StringBuilder();
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().getWritableDatabase();
+ List<String> tableNames =
+ ImmutableList.of(
+ "msmt_source",
+ "msmt_trigger",
+ "msmt_attribution",
+ "msmt_event_report",
+ "msmt_aggregate_report",
+ "enrollment_data",
+ "msmt_async_registration_contract");
+ for (String tableName : tableNames) {
+ result.append("\n" + tableName + ":\n");
+ result.append(getTableState(db, tableName));
+ }
+ return result.toString();
+ }
+
+ private static String getTableState(SQLiteDatabase db, String tableName) {
+ Cursor cursor = getAllRows(db, tableName);
+ StringBuilder result = new StringBuilder();
+ while (cursor.moveToNext()) {
+ result.append("\n" + DatabaseUtils.dumpCurrentRowToString(cursor));
+ }
+ return result.toString();
+ }
+
+ private static Cursor getAllRows(SQLiteDatabase db, String tableName) {
+ return db.query(
+ /* boolean distinct */ false,
+ tableName,
+ /* String[] columns */ null,
+ /* String selection */ null,
+ /* String[] selectionArgs */ null,
+ /* String groupBy */ null,
+ /* String having */ null,
+ /* String orderBy */ null,
+ /* String limit */ null);
+ }
+
private static Set<Long> getExpiryTimesFrom(
Collection<List<Map<String, List<String>>>> responseHeadersCollection)
throws JSONException {
@@ -556,7 +697,7 @@ public abstract class E2ETest {
return expiryTimes;
}
- private static Set<Action> maybeAddReportingJobTimes(
+ private static Set<Action> maybeAddEventReportingJobTimes(
long sourceTime, Collection<List<Map<String, List<String>>>> responseHeaders)
throws JSONException {
Set<Action> reportingJobsActions = new HashSet<>();
@@ -569,7 +710,8 @@ public abstract class E2ETest {
validExpiry = PrivacyParams.MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
}
long jobTime = sourceTime + 1000 * validExpiry + 3600000L;
- reportingJobsActions.add(new ReportingJob(jobTime));
+
+ reportingJobsActions.add(new EventReportingJob(jobTime));
}
return reportingJobsActions;
@@ -580,7 +722,8 @@ public abstract class E2ETest {
* tests.
*
* @return A collection of Object arrays, each with
- * {@code [Collection<Object> actions, ReportObjects expectedOutput, String name]}
+ * {@code [Collection<Object> actions, ReportObjects expectedOutput,
+ * PrivacyParamsProvider privacyParamsProvider, String name]}
*/
private static Collection<Object[]> getTestCasesFrom(List<InputStream> inputStreams,
String[] filenames) throws IOException, JSONException {
@@ -593,7 +736,7 @@ public abstract class E2ETest {
inputStreams.get(i).close();
String json = new String(buffer, StandardCharsets.UTF_8);
- JSONObject testObj = new JSONObject(json);
+ JSONObject testObj = new JSONObject(json.replaceAll("\\.test(?=[\"\\/])", ".com"));
String name = filenames[i];
JSONObject input = testObj.getJSONObject(TestFormatJsonMapping.TEST_INPUT_KEY);
JSONObject output = testObj.getJSONObject(TestFormatJsonMapping.TEST_OUTPUT_KEY);
@@ -602,7 +745,7 @@ public abstract class E2ETest {
List<Action> actions = new ArrayList<>();
actions.addAll(createSourceBasedActions(input));
- actions.addAll(createTriggerActions(input));
+ actions.addAll(createTriggerBasedActions(input));
actions.addAll(createInstallActions(input));
actions.addAll(createUninstallActions(input));
@@ -610,7 +753,13 @@ public abstract class E2ETest {
ReportObjects expectedOutput = getExpectedOutput(output);
- testCases.add(new Object[] {actions, expectedOutput, name});
+ JSONObject ApiConfigObj = testObj.isNull(TestFormatJsonMapping.API_CONFIG_KEY)
+ ? new JSONObject()
+ : testObj.getJSONObject(TestFormatJsonMapping.API_CONFIG_KEY);
+
+ PrivacyParamsProvider privacyParamsProvider = new PrivacyParamsProvider(ApiConfigObj);
+
+ testCases.add(new Object[] {actions, expectedOutput, privacyParamsProvider, name});
}
return testCases;
@@ -619,7 +768,7 @@ public abstract class E2ETest {
private static List<Action> createSourceBasedActions(JSONObject input) throws JSONException {
List<Action> actions = new ArrayList<>();
// Set avoids duplicate reporting times across sources to do attribution upon.
- Set<Action> reportingJobActions = new HashSet<>();
+ Set<Action> eventReportingJobActions = new HashSet<>();
if (!input.isNull(TestFormatJsonMapping.SOURCE_REGISTRATIONS_KEY)) {
JSONArray sourceRegistrationArray = input.getJSONArray(
TestFormatJsonMapping.SOURCE_REGISTRATIONS_KEY);
@@ -628,8 +777,8 @@ public abstract class E2ETest {
new RegisterSource(sourceRegistrationArray.getJSONObject(j));
actions.add(sourceRegistration);
// Add corresponding reporting job time actions
- reportingJobActions.addAll(
- maybeAddReportingJobTimes(
+ eventReportingJobActions.addAll(
+ maybeAddEventReportingJobTimes(
sourceRegistration.mTimestamp,
sourceRegistration.mUriToResponseHeadersMap.values()));
}
@@ -643,18 +792,20 @@ public abstract class E2ETest {
new RegisterWebSource(webSourceRegistrationArray.getJSONObject(j));
actions.add(webSource);
// Add corresponding reporting job time actions
- reportingJobActions.addAll(
- maybeAddReportingJobTimes(
+ eventReportingJobActions.addAll(
+ maybeAddEventReportingJobTimes(
webSource.mTimestamp, webSource.mUriToResponseHeadersMap.values()));
}
}
- actions.addAll(reportingJobActions);
+ actions.addAll(eventReportingJobActions);
return actions;
}
- private static List<Action> createTriggerActions(JSONObject input) throws JSONException {
+ private static List<Action> createTriggerBasedActions(JSONObject input) throws JSONException {
List<Action> actions = new ArrayList<>();
+ long firstTriggerTime = Long.MAX_VALUE;
+ long lastTriggerTime = -1;
if (!input.isNull(TestFormatJsonMapping.TRIGGER_KEY)) {
JSONArray triggerRegistrationArray =
input.getJSONArray(TestFormatJsonMapping.TRIGGER_KEY);
@@ -662,6 +813,8 @@ public abstract class E2ETest {
RegisterTrigger triggerRegistration =
new RegisterTrigger(triggerRegistrationArray.getJSONObject(j));
actions.add(triggerRegistration);
+ firstTriggerTime = Math.min(firstTriggerTime, triggerRegistration.mTimestamp);
+ lastTriggerTime = Math.max(lastTriggerTime, triggerRegistration.mTimestamp);
}
}
@@ -672,9 +825,31 @@ public abstract class E2ETest {
RegisterWebTrigger webTrigger =
new RegisterWebTrigger(webTriggerRegistrationArray.getJSONObject(j));
actions.add(webTrigger);
+ firstTriggerTime = Math.min(firstTriggerTime, webTrigger.mTimestamp);
+ lastTriggerTime = Math.max(lastTriggerTime, webTrigger.mTimestamp);
}
}
+ // Aggregate reports are scheduled close to trigger time. Add aggregate report jobs to cover
+ // the time span outlined by triggers.
+ List<Action> aggregateReportingJobActions = new ArrayList<>();
+ long window = SystemHealthParams.MAX_AGGREGATE_REPORT_UPLOAD_RETRY_WINDOW_MS - 10;
+ long t = firstTriggerTime;
+
+ do {
+ t += window;
+ aggregateReportingJobActions.add(new AggregateReportingJob(t));
+ } while (t <= lastTriggerTime);
+
+ // Account for edge case of t between lastTriggerTime and the latter's max report delay.
+ if (t <= lastTriggerTime + PrivacyParams.AGGREGATE_MAX_REPORT_DELAY) {
+ // t must be greater than lastTriggerTime so adding max report
+ // delay should be beyond the report delay for lastTriggerTime.
+ aggregateReportingJobActions.add(new AggregateReportingJob(t
+ + PrivacyParams.AGGREGATE_MAX_REPORT_DELAY));
+ }
+
+ actions.addAll(aggregateReportingJobActions);
return actions;
}
@@ -706,36 +881,28 @@ public abstract class E2ETest {
private static ReportObjects getExpectedOutput(JSONObject output) throws JSONException {
List<JSONObject> eventReportObjects = new ArrayList<>();
- JSONArray eventReportObjectsArray = output.getJSONArray(
- TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY);
- for (int i = 0; i < eventReportObjectsArray.length(); i++) {
- JSONObject obj = eventReportObjectsArray.getJSONObject(i);
- String adTechDomain = obj.getString(TestFormatJsonMapping.REPORT_TO_KEY);
- eventReportObjects.add(obj.put(TestFormatJsonMapping.REPORT_TO_KEY, adTechDomain));
+ if (!output.isNull(TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY)) {
+ JSONArray eventReportObjectsArray = output.getJSONArray(
+ TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY);
+ for (int i = 0; i < eventReportObjectsArray.length(); i++) {
+ JSONObject obj = eventReportObjectsArray.getJSONObject(i);
+ String adTechDomain = obj.getString(TestFormatJsonMapping.REPORT_TO_KEY);
+ eventReportObjects.add(obj.put(TestFormatJsonMapping.REPORT_TO_KEY, adTechDomain));
+ }
}
List<JSONObject> aggregateReportObjects = new ArrayList<>();
- JSONArray aggregateReportObjectsArray =
- output.getJSONArray(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY);
- for (int i = 0; i < aggregateReportObjectsArray.length(); i++) {
- aggregateReportObjects.add(aggregateReportObjectsArray.getJSONObject(i));
+ if (!output.isNull(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY)) {
+ JSONArray aggregateReportObjectsArray =
+ output.getJSONArray(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY);
+ for (int i = 0; i < aggregateReportObjectsArray.length(); i++) {
+ aggregateReportObjects.add(aggregateReportObjectsArray.getJSONObject(i));
+ }
}
return new ReportObjects(eventReportObjects, aggregateReportObjects);
}
- private static void seedEnrollment() {
- sEnrollmentDao.insert(AD_TECH_1);
- sEnrollmentDao.insert(AD_TECH_2);
- sEnrollmentDao.insert(AD_TECH_3);
- }
-
- private static void unseedEnrollment() {
- sEnrollmentDao.delete(AD_TECH_1.getEnrollmentId());
- sEnrollmentDao.delete(AD_TECH_2.getEnrollmentId());
- sEnrollmentDao.delete(AD_TECH_3.getEnrollmentId());
- }
-
/**
* Empties measurement database tables, used for test cleanup.
*/
@@ -745,72 +912,27 @@ public abstract class E2ETest {
db.delete("msmt_event_report", null, null);
db.delete("msmt_attribution", null, null);
db.delete("msmt_aggregate_report", null, null);
+ db.delete("enrollment_data", null, null);
+ db.delete("msmt_async_registration_contract", null, null);
}
- void processAction(RegisterSource sourceRegistration) throws IOException {
- prepareRegistrationServer(sourceRegistration);
- Assert.assertEquals(
- "MeasurementImpl.register source failed",
- RESULT_OK,
- mMeasurementImpl.register(
- sourceRegistration.mRegistrationRequest, sourceRegistration.mTimestamp));
- }
-
- void processAction(RegisterWebSource sourceRegistration) throws IOException {
- prepareRegistrationServer(sourceRegistration);
- Assert.assertEquals(
- "MeasurementImpl.registerWebSource failed",
- RESULT_OK,
- mMeasurementImpl.registerWebSource(
- sourceRegistration.mRegistrationRequest, sourceRegistration.mTimestamp));
- }
-
- void processAction(RegisterWebTrigger triggerRegistration) throws IOException {
- prepareRegistrationServer(triggerRegistration);
- Assert.assertEquals(
- "MeasurementImpl.registerWebTrigger failed",
- RESULT_OK,
- mMeasurementImpl.registerWebTrigger(
- triggerRegistration.mRegistrationRequest, triggerRegistration.mTimestamp));
- Assert.assertTrue(
- "AttributionJobHandler.performPendingAttributions returned false",
- mAttributionHelper.performPendingAttributions());
- }
-
- void processAction(RegisterTrigger triggerRegistration) throws IOException {
- prepareRegistrationServer(triggerRegistration);
- Assert.assertEquals(
- "MeasurementImpl.register trigger failed",
- RESULT_OK,
- mMeasurementImpl.register(
- triggerRegistration.mRegistrationRequest, triggerRegistration.mTimestamp));
- Assert.assertTrue("AttributionJobHandler.performPendingAttributions returned false",
- mAttributionHelper.performPendingAttributions());
- }
-
- void processAction(InstallApp installApp) {
- Assert.assertTrue(
- "measurementDao.doInstallAttribution failed",
- sDatastoreManager.runInTransaction(
- measurementDao ->
- measurementDao.doInstallAttribution(
- installApp.mUri, installApp.mTimestamp)));
- }
-
- void processAction(UninstallApp uninstallApp) {
- Assert.assertTrue("measurementDao.undoInstallAttribution failed",
- sDatastoreManager.runInTransaction(
- measurementDao -> {
- measurementDao.deleteAppRecords(uninstallApp.mUri);
- measurementDao.undoInstallAttribution(uninstallApp.mUri);
- }));
- }
+ abstract void processAction(RegisterSource sourceRegistration) throws IOException;
+
+ abstract void processAction(RegisterWebSource sourceRegistration) throws IOException;
+
+ abstract void processAction(RegisterTrigger triggerRegistration) throws IOException;
+
+ abstract void processAction(RegisterWebTrigger triggerRegistration) throws IOException;
+
+ abstract void processAction(InstallApp installApp);
+
+ abstract void processAction(UninstallApp uninstallApp);
void evaluateResults() throws JSONException {
- sortEventReportObjects(mExpectedOutput.mEventReportObjects);
- sortEventReportObjects(mActualOutput.mEventReportObjects);
- sortAggregateReportObjects(mExpectedOutput.mAggregateReportObjects);
- sortAggregateReportObjects(mActualOutput.mAggregateReportObjects);
+ sortEventReportObjects(OutputType.EXPECTED, mExpectedOutput.mEventReportObjects);
+ sortEventReportObjects(OutputType.ACTUAL, mActualOutput.mEventReportObjects);
+ sortAggregateReportObjects(OutputType.EXPECTED, mExpectedOutput.mAggregateReportObjects);
+ sortAggregateReportObjects(OutputType.ACTUAL, mActualOutput.mAggregateReportObjects);
Assert.assertTrue(getTestFailureMessage(mExpectedOutput, mActualOutput),
areEqual(mExpectedOutput, mActualOutput));
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EnqueueAsyncRegistrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EnqueueAsyncRegistrationTest.java
new file mode 100644
index 000000000..45ccbbbac
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EnqueueAsyncRegistrationTest.java
@@ -0,0 +1,840 @@
+/*
+ * 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.measurement;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.adservices.measurement.WebSourceParams;
+import android.adservices.measurement.WebSourceRegistrationRequest;
+import android.adservices.measurement.WebTriggerParams;
+import android.adservices.measurement.WebTriggerRegistrationRequest;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.view.InputEvent;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.adservices.data.DbTestUtil;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.MeasurementTables;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
+import com.android.adservices.data.measurement.SqliteObjectMapper;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.registration.EnqueueAsyncRegistration;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EnqueueAsyncRegistrationTest {
+
+ private static Context sDefaultContext = ApplicationProvider.getApplicationContext();
+ private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com/bar?ad=134");
+ private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo.com/bar?ad=256");
+ private static final String DEFAULT_ENROLLMENT = "enrollment-id";
+ private static final WebSourceParams INPUT_SOURCE_REGISTRATION_1 =
+ new WebSourceParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
+
+ private static final WebSourceParams INPUT_SOURCE_REGISTRATION_2 =
+ new WebSourceParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
+
+ private static final WebTriggerParams INPUT_TRIGGER_REGISTRATION_1 =
+ new WebTriggerParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
+
+ private static final WebTriggerParams INPUT_TRIGGER_REGISTRATION_2 =
+ new WebTriggerParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
+
+ private static List<WebSourceParams> sSourceParamsList = new ArrayList<>();
+
+ private static List<WebTriggerParams> sTriggerParamsList = new ArrayList<>();
+
+ static {
+ sSourceParamsList.add(INPUT_SOURCE_REGISTRATION_1);
+ sSourceParamsList.add(INPUT_SOURCE_REGISTRATION_2);
+ sTriggerParamsList.add(INPUT_TRIGGER_REGISTRATION_1);
+ sTriggerParamsList.add(INPUT_TRIGGER_REGISTRATION_2);
+ }
+
+ @Mock private DatastoreManager mDatastoreManagerMock;
+ @Mock private EnrollmentDao mEnrollmentDao;
+ @Mock private InputEvent mInputEvent;
+
+ private MockitoSession mStaticMockSession;
+
+ private static final WebSourceRegistrationRequest
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT =
+ new WebSourceRegistrationRequest.Builder(
+ sSourceParamsList, Uri.parse("android-app://example.com/aD1"))
+ .setWebDestination(Uri.parse("android-app://example.com/aD1"))
+ .setAppDestination(Uri.parse("android-app://example.com/aD1"))
+ .setVerifiedDestination(Uri.parse("android-app://example.com/aD1"))
+ .build();
+
+ private static final WebTriggerRegistrationRequest VALID_WEB_TRIGGER_REGISTRATION =
+ new WebTriggerRegistrationRequest.Builder(
+ sTriggerParamsList, Uri.parse("android-app://com.e.abc"))
+ .build();
+
+ private static EnrollmentData getEnrollment(String enrollmentId) {
+ return new EnrollmentData.Builder().setEnrollmentId(enrollmentId).build();
+ }
+
+ @After
+ public void cleanup() {
+ SQLiteDatabase db = DbTestUtil.getDbHelperForTest().safeGetWritableDatabase();
+ for (String table : MeasurementTables.ALL_MSMT_TABLES) {
+ db.delete(table, null, null);
+ }
+ mStaticMockSession.finishMocking();
+ }
+
+ @Before
+ public void before() throws RemoteException {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+ MockitoAnnotations.initMocks(this);
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any()))
+ .thenReturn(getEnrollment(DEFAULT_ENROLLMENT));
+ }
+
+ @Test
+ public void test_appSourceRegistrationRequest_event_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ RegistrationRequest registrationRequest =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("http://baz.com"))
+ .setPackageName(sDefaultContext.getAttributionSource().getPackageName())
+ .build();
+
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ registrationRequest,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistration);
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ registrationRequest.getRegistrationUri(),
+ asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getSourceType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.APP_SOURCE, asyncRegistration.getType());
+ }
+ }
+
+ @Test
+ public void test_appSourceRegistrationRequest_navigation_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ RegistrationRequest registrationRequest =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("http://baz.com"))
+ .setPackageName(sDefaultContext.getAttributionSource().getPackageName())
+ .setInputEvent(mInputEvent)
+ .build();
+
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ registrationRequest,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistration);
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ registrationRequest.getRegistrationUri(),
+ asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getSourceType());
+ Assert.assertEquals(Source.SourceType.NAVIGATION, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.APP_SOURCE, asyncRegistration.getType());
+ }
+ }
+
+ @Test
+ public void test_appTriggerRegistrationRequest_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ RegistrationRequest registrationRequest =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
+ .setRegistrationUri(Uri.parse("http://baz.com"))
+ .setPackageName(sDefaultContext.getAttributionSource().getPackageName())
+ .build();
+
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ registrationRequest,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistration);
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ registrationRequest.getRegistrationUri(),
+ asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getRegistrant());
+ Assert.assertNull(asyncRegistration.getSourceType());
+ }
+ }
+
+ @Test
+ public void test_webSourceRegistrationRequest_event_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.webSourceRegistrationRequest(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT
+ .getSourceParams()
+ .get(0)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistration.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getWebDestination());
+ Assert.assertNotNull(asyncRegistration.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getOsDestination());
+ Assert.assertNotNull(asyncRegistration.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE, asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistrationTwo.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getWebDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getOsDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT.getTopOriginUri(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistrationTwo.getType());
+ } else if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT
+ .getSourceParams()
+ .get(1)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistration.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getWebDestination());
+ Assert.assertNotNull(asyncRegistration.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getOsDestination());
+ Assert.assertNotNull(asyncRegistration.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistration.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT.getTopOriginUri(),
+ asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE, asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistrationTwo.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getWebDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getOsDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_SOURCE_REGISTRATION_NULL_INPUT_EVENT.getTopOriginUri(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistrationTwo.getType());
+ } else {
+ Assert.fail();
+ }
+ }
+ }
+
+ @Test
+ public void test_webSourceRegistrationRequest_navigation_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ List<WebSourceParams> sourceParamsList = new ArrayList<>();
+ sourceParamsList.add(INPUT_SOURCE_REGISTRATION_1);
+ sourceParamsList.add(INPUT_SOURCE_REGISTRATION_2);
+ WebSourceRegistrationRequest validWebSourceRegistration =
+ new WebSourceRegistrationRequest.Builder(
+ sourceParamsList, Uri.parse("android-app://example.com/aD1"))
+ .setWebDestination(Uri.parse("android-app://example.com/aD1"))
+ .setAppDestination(Uri.parse("android-app://example.com/aD1"))
+ .setVerifiedDestination(Uri.parse("android-app://example.com/aD1"))
+ .setInputEvent(mInputEvent)
+ .build();
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.webSourceRegistrationRequest(
+ validWebSourceRegistration,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ validWebSourceRegistration
+ .getSourceParams()
+ .get(0)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(
+ Source.SourceType.NAVIGATION, asyncRegistration.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getWebDestination());
+ Assert.assertNotNull(asyncRegistration.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getOsDestination());
+ Assert.assertNotNull(asyncRegistration.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE, asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(
+ Source.SourceType.NAVIGATION, asyncRegistrationTwo.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getWebDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getOsDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ validWebSourceRegistration.getTopOriginUri(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistrationTwo.getType());
+ } else if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ validWebSourceRegistration
+ .getSourceParams()
+ .get(1)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(
+ Source.SourceType.NAVIGATION, asyncRegistration.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getWebDestination());
+ Assert.assertNotNull(asyncRegistration.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getOsDestination());
+ Assert.assertNotNull(asyncRegistration.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistration.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistration.getTopOrigin());
+ Assert.assertEquals(
+ validWebSourceRegistration.getTopOriginUri(),
+ asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE, asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(
+ Source.SourceType.NAVIGATION, asyncRegistrationTwo.getSourceType());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getWebDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getWebDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getOsDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getOsDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertEquals(
+ Uri.parse("android-app://example.com/aD1"),
+ asyncRegistrationTwo.getVerifiedDestination());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ validWebSourceRegistration.getTopOriginUri(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ asyncRegistrationTwo.getType());
+ } else {
+ Assert.fail();
+ }
+ }
+ }
+
+ @Test
+ public void test_webTriggerRegistrationRequest_isValid() {
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ Assert.assertTrue(
+ EnqueueAsyncRegistration.webTriggerRegistrationRequest(
+ VALID_WEB_TRIGGER_REGISTRATION,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager));
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ VALID_WEB_TRIGGER_REGISTRATION
+ .getTriggerParams()
+ .get(0)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(null, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_TRIGGER_REGISTRATION.getDestination(),
+ asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(null, asyncRegistrationTwo.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_TRIGGER_REGISTRATION.getDestination(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistrationTwo.getType());
+ } else if (asyncRegistration
+ .getRegistrationUri()
+ .equals(
+ VALID_WEB_TRIGGER_REGISTRATION
+ .getTriggerParams()
+ .get(1)
+ .getRegistrationUri())) {
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_2, asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistration.getRedirectType());
+ Assert.assertEquals(null, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_TRIGGER_REGISTRATION.getDestination(),
+ asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistration.getType());
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistrationTwo =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(REGISTRATION_URI_1, asyncRegistrationTwo.getRegistrationUri());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.NONE,
+ asyncRegistrationTwo.getRedirectType());
+ Assert.assertEquals(null, asyncRegistrationTwo.getSourceType());
+ Assert.assertNotNull(asyncRegistrationTwo.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"),
+ asyncRegistrationTwo.getRegistrant());
+ Assert.assertNotNull(asyncRegistrationTwo.getTopOrigin());
+ Assert.assertEquals(
+ VALID_WEB_TRIGGER_REGISTRATION.getDestination(),
+ asyncRegistrationTwo.getTopOrigin());
+ Assert.assertNotNull(asyncRegistrationTwo.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ asyncRegistrationTwo.getType());
+ } else {
+ Assert.fail();
+ }
+ }
+ }
+
+ @Test
+ public void test_runInTransactionFail_inValid() {
+ when(mDatastoreManagerMock.runInTransaction(any())).thenReturn(false);
+ Assert.assertFalse(
+ EnqueueAsyncRegistration.webTriggerRegistrationRequest(
+ VALID_WEB_TRIGGER_REGISTRATION,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ mDatastoreManagerMock));
+ }
+
+ @Test
+ public void test_MissingEnrollmentData_inValid() {
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
+ Assert.assertFalse(
+ EnqueueAsyncRegistration.webTriggerRegistrationRequest(
+ VALID_WEB_TRIGGER_REGISTRATION,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ mDatastoreManagerMock));
+ }
+
+ /** Test that the AsyncRegistration is inserted correctly. */
+ @Test
+ public void test_verifyAsyncRegistrationStoredCorrectly() {
+ RegistrationRequest registrationRequest =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("http://baz.com"))
+ .setPackageName(sDefaultContext.getAttributionSource().getPackageName())
+ .build();
+
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
+ EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
+ registrationRequest,
+ Uri.parse("android-app://com.destination"),
+ System.currentTimeMillis(),
+ mEnrollmentDao,
+ datastoreManager);
+
+ try (Cursor cursor =
+ DbTestUtil.getDbHelperForTest()
+ .getReadableDatabase()
+ .query(
+ MeasurementTables.AsyncRegistrationContract.TABLE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+
+ Assert.assertTrue(cursor.moveToNext());
+ AsyncRegistration asyncRegistration =
+ SqliteObjectMapper.constructAsyncRegistration(cursor);
+ Assert.assertNotNull(asyncRegistration);
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ registrationRequest.getRegistrationUri(),
+ asyncRegistration.getRegistrationUri());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getTopOrigin());
+ Assert.assertNotNull(asyncRegistration.getRegistrationUri());
+ Assert.assertNotNull(asyncRegistration.getRegistrant());
+ Assert.assertEquals(
+ Uri.parse("android-app://com.destination"), asyncRegistration.getRegistrant());
+ Assert.assertNotNull(asyncRegistration.getSourceType());
+ Assert.assertEquals(Source.SourceType.EVENT, asyncRegistration.getSourceType());
+ Assert.assertNotNull(asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RegistrationType.APP_SOURCE, asyncRegistration.getType());
+ Assert.assertEquals(
+ AsyncRegistration.RedirectType.ANY,
+ asyncRegistration.getRedirectType());
+ }
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java
index ffeed6a45..4247a8b3b 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventReportTest.java
@@ -25,16 +25,21 @@ import static com.android.adservices.service.measurement.PrivacyParams.NAVIGATIO
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.net.Uri;
import androidx.test.filters.SmallTest;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.json.JSONException;
import org.junit.Test;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
/** Unit tests for {@link EventReport} */
@@ -44,10 +49,12 @@ public final class EventReportTest {
private static final long ONE_HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
private static final double DOUBLE_MAX_DELTA = 0.0000001D;
private static final long TRIGGER_PRIORITY = 345678L;
- private static final Long TRIGGER_DEDUP_KEY = 2345678L;
- private static final Long TRIGGER_DATA = 4L;
- private static final Long SOURCE_DEBUG_KEY = 237865L;
- private static final Long TRIGGER_DEBUG_KEY = 928762L;
+ private static final UnsignedLong TRIGGER_DEDUP_KEY = new UnsignedLong(2345678L);
+ private static final UnsignedLong TRIGGER_DATA = new UnsignedLong(4L);
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(237865L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(928762L);
+ private static final String SOURCE_ID = UUID.randomUUID().toString();
+ private static final String TRIGGER_ID = UUID.randomUUID().toString();
private static final Uri APP_DESTINATION = Uri.parse("android-app://example1.app");
private static final Uri WEB_DESTINATION = Uri.parse("https://example1.com");
private static final String EVENT_TRIGGERS =
@@ -73,72 +80,84 @@ public final class EventReportTest {
public void creation_success() {
EventReport eventReport = createExample();
assertEquals("1", eventReport.getId());
- assertEquals(21, eventReport.getSourceId());
+ assertEquals(new UnsignedLong(21L), eventReport.getSourceEventId());
assertEquals("enrollment-id", eventReport.getEnrollmentId());
assertEquals("https://bar.com", eventReport.getAttributionDestination().toString());
assertEquals(1000L, eventReport.getTriggerTime());
- assertEquals(8L, eventReport.getTriggerData());
+ assertEquals(new UnsignedLong(8L), eventReport.getTriggerData());
assertEquals(2L, eventReport.getTriggerPriority());
- assertEquals(Long.valueOf(3), eventReport.getTriggerDedupKey());
+ assertEquals(new UnsignedLong(3L), eventReport.getTriggerDedupKey());
assertEquals(2000L, eventReport.getReportTime());
assertEquals(EventReport.Status.PENDING, eventReport.getStatus());
+ assertEquals(EventReport.DebugReportStatus.PENDING, eventReport.getDebugReportStatus());
assertEquals(Source.SourceType.NAVIGATION, eventReport.getSourceType());
assertEquals(SOURCE_DEBUG_KEY, eventReport.getSourceDebugKey());
assertEquals(TRIGGER_DEBUG_KEY, eventReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, eventReport.getSourceId());
+ assertEquals(TRIGGER_ID, eventReport.getTriggerId());
}
@Test
public void creationSuccessSingleSourceDebugKey() {
EventReport eventReport = createExampleSingleSourceDebugKey();
assertEquals("1", eventReport.getId());
- assertEquals(21, eventReport.getSourceId());
+ assertEquals(new UnsignedLong(21L), eventReport.getSourceEventId());
assertEquals("enrollment-id", eventReport.getEnrollmentId());
assertEquals("https://bar.com", eventReport.getAttributionDestination().toString());
assertEquals(1000L, eventReport.getTriggerTime());
- assertEquals(8L, eventReport.getTriggerData());
+ assertEquals(new UnsignedLong(8L), eventReport.getTriggerData());
assertEquals(2L, eventReport.getTriggerPriority());
- assertEquals(Long.valueOf(3), eventReport.getTriggerDedupKey());
+ assertEquals(new UnsignedLong(3L), eventReport.getTriggerDedupKey());
assertEquals(2000L, eventReport.getReportTime());
assertEquals(EventReport.Status.PENDING, eventReport.getStatus());
+ assertEquals(EventReport.DebugReportStatus.PENDING, eventReport.getDebugReportStatus());
assertEquals(Source.SourceType.NAVIGATION, eventReport.getSourceType());
assertEquals(SOURCE_DEBUG_KEY, eventReport.getSourceDebugKey());
assertNull(eventReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, eventReport.getSourceId());
+ assertEquals(TRIGGER_ID, eventReport.getTriggerId());
}
@Test
public void creationSuccessSingleTriggerDebugKey() {
EventReport eventReport = createExampleSingleTriggerDebugKey();
assertEquals("1", eventReport.getId());
- assertEquals(21, eventReport.getSourceId());
+ assertEquals(new UnsignedLong(21L), eventReport.getSourceEventId());
assertEquals("enrollment-id", eventReport.getEnrollmentId());
assertEquals("https://bar.com", eventReport.getAttributionDestination().toString());
assertEquals(1000L, eventReport.getTriggerTime());
- assertEquals(8L, eventReport.getTriggerData());
+ assertEquals(new UnsignedLong(8L), eventReport.getTriggerData());
assertEquals(2L, eventReport.getTriggerPriority());
- assertEquals(Long.valueOf(3), eventReport.getTriggerDedupKey());
+ assertEquals(new UnsignedLong(3L), eventReport.getTriggerDedupKey());
assertEquals(2000L, eventReport.getReportTime());
assertEquals(EventReport.Status.PENDING, eventReport.getStatus());
+ assertEquals(EventReport.DebugReportStatus.PENDING, eventReport.getDebugReportStatus());
assertEquals(Source.SourceType.NAVIGATION, eventReport.getSourceType());
assertNull(eventReport.getSourceDebugKey());
assertEquals(TRIGGER_DEBUG_KEY, eventReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, eventReport.getSourceId());
+ assertEquals(TRIGGER_ID, eventReport.getTriggerId());
}
@Test
public void defaults_success() {
EventReport eventReport = new EventReport.Builder().build();
assertNull(eventReport.getId());
- assertEquals(0L, eventReport.getSourceId());
+ assertNull(eventReport.getSourceEventId());
assertNull(eventReport.getEnrollmentId());
assertNull(eventReport.getAttributionDestination());
assertEquals(0L, eventReport.getTriggerTime());
- assertEquals(0L, eventReport.getTriggerData());
+ assertNull(eventReport.getTriggerData());
assertEquals(0L, eventReport.getTriggerPriority());
assertNull(eventReport.getTriggerDedupKey());
assertEquals(0L, eventReport.getReportTime());
assertEquals(EventReport.Status.PENDING, eventReport.getStatus());
+ assertEquals(EventReport.DebugReportStatus.NONE, eventReport.getDebugReportStatus());
assertNull(eventReport.getSourceType());
assertNull(eventReport.getSourceDebugKey());
assertNull(eventReport.getTriggerDebugKey());
+ assertNull(eventReport.getSourceId());
+ assertNull(eventReport.getTriggerId());
}
@Test
@@ -160,14 +179,87 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
// Truncated data 4 % 2 = 0
- assertEquals(0, report.getTriggerData());
+ assertEquals(new UnsignedLong(0L), report.getTriggerData());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
assertEquals(source.getSourceType(), report.getSourceType());
assertEquals(EVENT_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
+ }
+
+ @Test
+ public void testPopulateFromSourceAndTrigger_shouldSetTriggerData0WhenNull()
+ throws JSONException {
+ long baseTime = System.currentTimeMillis();
+ Source source =
+ createSourceForTest(
+ baseTime, Source.SourceType.EVENT, false, APP_DESTINATION, null);
+ Trigger trigger =
+ createTriggerForTest(baseTime + TimeUnit.SECONDS.toMillis(10), APP_DESTINATION);
+
+ List<EventTrigger> eventTriggers = trigger.parseEventTriggers();
+ EventTrigger eventTrigger = spy(eventTriggers.get(0));
+ when(eventTrigger.getTriggerData()).thenReturn(null);
+ EventReport report =
+ new EventReport.Builder()
+ .populateFromSourceAndTrigger(source, trigger, eventTrigger)
+ .build();
+
+ assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
+ assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
+ assertEquals(new UnsignedLong(0L), report.getTriggerData());
+ assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
+ assertEquals(source.getEventId(), report.getSourceEventId());
+ assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
+ assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
+ assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
+ assertEquals(source.getSourceType(), report.getSourceType());
+ assertEquals(EVENT_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
+ }
+
+ @Test
+ public void testPopulateFromSourceAndTrigger_shouldTruncateTriggerDataWith64thBit()
+ throws JSONException {
+ long baseTime = System.currentTimeMillis();
+ Source source =
+ createSourceForTest(
+ baseTime, Source.SourceType.NAVIGATION, false, APP_DESTINATION, null);
+ Trigger trigger =
+ createTriggerForTest(baseTime + TimeUnit.SECONDS.toMillis(10), APP_DESTINATION);
+
+ List<EventTrigger> eventTriggers = trigger.parseEventTriggers();
+ EventTrigger eventTrigger = spy(eventTriggers.get(0));
+ when(eventTrigger.getTriggerData()).thenReturn(new UnsignedLong(-50003L));
+ EventReport report =
+ new EventReport.Builder()
+ .populateFromSourceAndTrigger(source, trigger, eventTrigger)
+ .build();
+
+ assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
+ assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
+ assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
+ // uint64 18446744073709501613 = long -50003
+ // Truncated data 18446744073709501613 % 8 = 5
+ assertEquals(new UnsignedLong(5L), report.getTriggerData());
+ assertEquals(source.getEventId(), report.getSourceEventId());
+ assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
+ assertEquals(APP_DESTINATION, report.getAttributionDestination());
+ assertEquals(
+ source.getEventTime()
+ + NAVIGATION_EARLY_REPORTING_WINDOW_MILLISECONDS[0]
+ + ONE_HOUR_IN_MILLIS,
+ report.getReportTime());
+ assertEquals(Source.SourceType.NAVIGATION, report.getSourceType());
+ assertEquals(
+ NAVIGATION_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -189,14 +281,16 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
// Truncated data 4 % 2 = 0
- assertEquals(0, report.getTriggerData());
+ assertEquals(new UnsignedLong(0L), report.getTriggerData());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
assertEquals(Source.SourceType.EVENT, report.getSourceType());
assertEquals(EVENT_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -217,7 +311,7 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
@@ -226,6 +320,8 @@ public final class EventReportTest {
INSTALL_ATTR_EVENT_NOISE_PROBABILITY,
report.getRandomizedTriggerRate(),
DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -246,12 +342,14 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(source.getExpiryTime() + ONE_HOUR_IN_MILLIS, report.getReportTime());
assertEquals(Source.SourceType.EVENT, report.getSourceType());
assertEquals(EVENT_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -273,7 +371,7 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(APP_DESTINATION, report.getAttributionDestination());
assertEquals(
@@ -284,6 +382,8 @@ public final class EventReportTest {
assertEquals(Source.SourceType.NAVIGATION, report.getSourceType());
assertEquals(
NAVIGATION_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -305,7 +405,7 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
assertEquals(
@@ -314,6 +414,8 @@ public final class EventReportTest {
assertEquals(source.getSourceType(), report.getSourceType());
assertEquals(
NAVIGATION_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -334,9 +436,9 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
- assertEquals(4, report.getTriggerData());
+ assertEquals(new UnsignedLong(4L), report.getTriggerData());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
// One hour after install attributed navigation type window
@@ -350,6 +452,8 @@ public final class EventReportTest {
INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY,
report.getRandomizedTriggerRate(),
DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -370,9 +474,9 @@ public final class EventReportTest {
assertEquals(TRIGGER_PRIORITY, report.getTriggerPriority());
assertEquals(TRIGGER_DEDUP_KEY, report.getTriggerDedupKey());
- assertEquals(4, report.getTriggerData());
+ assertEquals(new UnsignedLong(4L), report.getTriggerData());
assertEquals(trigger.getTriggerTime(), report.getTriggerTime());
- assertEquals(source.getEventId(), report.getSourceId());
+ assertEquals(source.getEventId(), report.getSourceEventId());
assertEquals(source.getEnrollmentId(), report.getEnrollmentId());
assertEquals(trigger.getAttributionDestination(), report.getAttributionDestination());
// One hour after regular navigation type window (without install attribution consideration)
@@ -384,6 +488,8 @@ public final class EventReportTest {
assertEquals(source.getSourceType(), report.getSourceType());
assertEquals(
NAVIGATION_NOISE_PROBABILITY, report.getRandomizedTriggerRate(), DOUBLE_MAX_DELTA);
+ assertEquals(SOURCE_ID, report.getSourceId());
+ assertEquals(TRIGGER_ID, report.getTriggerId());
}
@Test
@@ -403,15 +509,16 @@ public final class EventReportTest {
final EventReport eventReport2 =
new EventReport.Builder()
.setId("1")
- .setSourceId(22)
+ .setSourceEventId(new UnsignedLong(22L))
.setEnrollmentId("another-enrollment-id")
.setAttributionDestination(Uri.parse("https://bar.com"))
.setTriggerTime(1000L)
- .setTriggerData(8L)
+ .setTriggerData(new UnsignedLong(8L))
.setTriggerPriority(2L)
- .setTriggerDedupKey(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
.setReportTime(2000L)
.setStatus(EventReport.Status.PENDING)
+ .setStatus(EventReport.DebugReportStatus.PENDING)
.setSourceType(Source.SourceType.NAVIGATION)
.build();
final Set<EventReport> eventReportSet1 = Set.of(eventReport1);
@@ -428,7 +535,8 @@ public final class EventReportTest {
Uri appDestination,
Uri webDestination) {
return SourceFixture.getValidSourceBuilder()
- .setEventId(10)
+ .setId(SOURCE_ID)
+ .setEventId(new UnsignedLong(10L))
.setSourceType(sourceType)
.setInstallCooldownWindow(isInstallAttributable ? 100 : 0)
.setEventTime(eventTime)
@@ -441,6 +549,7 @@ public final class EventReportTest {
private Trigger createTriggerForTest(long eventTime, Uri destination) {
return TriggerFixture.getValidTriggerBuilder()
+ .setId(TRIGGER_ID)
.setTriggerTime(eventTime)
.setEventTriggers(EVENT_TRIGGERS)
.setEnrollmentId("enrollment-id")
@@ -451,52 +560,61 @@ public final class EventReportTest {
private EventReport createExample() {
return new EventReport.Builder()
.setId("1")
- .setSourceId(21)
+ .setSourceEventId(new UnsignedLong(21L))
.setEnrollmentId("enrollment-id")
.setAttributionDestination(Uri.parse("https://bar.com"))
.setTriggerTime(1000L)
- .setTriggerData(8L)
+ .setTriggerData(new UnsignedLong(8L))
.setTriggerPriority(2L)
- .setTriggerDedupKey(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
.setReportTime(2000L)
.setStatus(EventReport.Status.PENDING)
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING)
.setSourceType(Source.SourceType.NAVIGATION)
.setSourceDebugKey(SOURCE_DEBUG_KEY)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
private EventReport createExampleSingleTriggerDebugKey() {
return new EventReport.Builder()
.setId("1")
- .setSourceId(21)
+ .setSourceEventId(new UnsignedLong(21L))
.setEnrollmentId("enrollment-id")
.setAttributionDestination(Uri.parse("https://bar.com"))
.setTriggerTime(1000L)
- .setTriggerData(8L)
+ .setTriggerData(new UnsignedLong(8L))
.setTriggerPriority(2L)
- .setTriggerDedupKey(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
.setReportTime(2000L)
.setStatus(EventReport.Status.PENDING)
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING)
.setSourceType(Source.SourceType.NAVIGATION)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
private EventReport createExampleSingleSourceDebugKey() {
return new EventReport.Builder()
.setId("1")
- .setSourceId(21)
+ .setSourceEventId(new UnsignedLong(21L))
.setEnrollmentId("enrollment-id")
.setAttributionDestination(Uri.parse("https://bar.com"))
.setTriggerTime(1000L)
- .setTriggerData(8L)
+ .setTriggerData(new UnsignedLong(8L))
.setTriggerPriority(2L)
- .setTriggerDedupKey(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
.setReportTime(2000L)
.setStatus(EventReport.Status.PENDING)
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING)
.setSourceType(Source.SourceType.NAVIGATION)
.setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java
index d31d653bd..10b4b4bf8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/EventTriggerTest.java
@@ -18,7 +18,7 @@ package com.android.adservices.service.measurement;
import static org.junit.Assert.*;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONException;
import org.json.JSONObject;
@@ -78,29 +78,29 @@ public class EventTriggerTest {
EventTrigger eventTrigger1 =
new EventTrigger.Builder()
.setTriggerPriority(1L)
- .setTriggerData(101L)
- .setDedupKey(1001L)
+ .setTriggerData(new UnsignedLong(101L))
+ .setDedupKey(new UnsignedLong(1001L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build();
EventTrigger eventTrigger2 =
new EventTrigger.Builder()
.setTriggerPriority(1L)
- .setTriggerData(101L)
- .setDedupKey(1001L)
+ .setTriggerData(new UnsignedLong(101L))
+ .setDedupKey(new UnsignedLong(1001L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build();
@@ -113,35 +113,35 @@ public class EventTriggerTest {
new EventTrigger.Builder().setTriggerPriority(1L).build(),
new EventTrigger.Builder().setTriggerPriority(2L).build());
assertNotEquals(
- new EventTrigger.Builder().setTriggerData(1L).build(),
- new EventTrigger.Builder().setTriggerData(2L).build());
+ new EventTrigger.Builder().setTriggerData(new UnsignedLong(1L)).build(),
+ new EventTrigger.Builder().setTriggerData(new UnsignedLong(2L)).build());
assertNotEquals(
- new EventTrigger.Builder().setDedupKey(1L).build(),
- new EventTrigger.Builder().setDedupKey(2L).build());
+ new EventTrigger.Builder().setDedupKey(new UnsignedLong(1L)).build(),
+ new EventTrigger.Builder().setDedupKey(new UnsignedLong(2L)).build());
assertNotEquals(
new EventTrigger.Builder()
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.build(),
new EventTrigger.Builder()
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData2)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData2)
.build())
.build());
assertNotEquals(
new EventTrigger.Builder()
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build(),
new EventTrigger.Builder()
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData2)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData2)
.build())
.build());
}
@@ -163,15 +163,15 @@ public class EventTriggerTest {
EventTrigger eventTrigger2 =
new EventTrigger.Builder()
.setTriggerPriority(2L)
- .setTriggerData(101L)
- .setDedupKey(1001L)
+ .setTriggerData(new UnsignedLong(101L))
+ .setDedupKey(new UnsignedLong(1001L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build();
Set<EventTrigger> eventTriggerSet1 = Set.of(eventTrigger1);
@@ -184,15 +184,15 @@ public class EventTriggerTest {
private EventTrigger createExample() throws JSONException {
return new EventTrigger.Builder()
.setTriggerPriority(1L)
- .setTriggerData(101L)
- .setDedupKey(1001L)
+ .setTriggerData(new UnsignedLong(101L))
+ .setDedupKey(new UnsignedLong(1001L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sFilterData1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(sNotFilterData1)
+ new FilterData.Builder()
+ .buildFilterData(sNotFilterData1)
.build())
.build();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateFilterDataTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/FilterDataTest.java
index 644ef1a07..034b42e7d 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateFilterDataTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/FilterDataTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.adservices.service.measurement.aggregation;
+package com.android.adservices.service.measurement;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -30,13 +30,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-/** Unit tests for {@link AggregateFilterData} */
+/** Unit tests for {@link FilterData} */
@SmallTest
-public final class AggregateFilterDataTest {
+public final class FilterDataTest {
@Test
public void testCreation() throws Exception {
- AggregateFilterData attributionFilterData = createExample();
+ FilterData attributionFilterData = createExample();
assertEquals(attributionFilterData.getAttributionFilterMap().size(), 2);
assertEquals(attributionFilterData.getAttributionFilterMap().get("type").size(), 4);
@@ -45,16 +45,16 @@ public final class AggregateFilterDataTest {
@Test
public void testDefaults() throws Exception {
- AggregateFilterData data = new AggregateFilterData.Builder().build();
+ FilterData data = new FilterData.Builder().build();
assertEquals(data.getAttributionFilterMap().size(), 0);
}
@Test
public void testHashCode_equals() throws Exception {
- final AggregateFilterData data1 = createExample();
- final AggregateFilterData data2 = createExample();
- final Set<AggregateFilterData> dataSet1 = Set.of(data1);
- final Set<AggregateFilterData> dataSet2 = Set.of(data2);
+ final FilterData data1 = createExample();
+ final FilterData data2 = createExample();
+ final Set<FilterData> dataSet1 = Set.of(data1);
+ final Set<FilterData> dataSet2 = Set.of(data2);
assertEquals(data1.hashCode(), data2.hashCode());
assertEquals(data1, data2);
assertEquals(dataSet1, dataSet2);
@@ -62,29 +62,29 @@ public final class AggregateFilterDataTest {
@Test
public void testHashCode_notEquals() throws Exception {
- final AggregateFilterData data1 = createExample();
+ final FilterData data1 = createExample();
Map<String, List<String>> attributionFilterMap = new HashMap<>();
attributionFilterMap.put("type", Arrays.asList("2", "3", "4"));
attributionFilterMap.put("ctid", Collections.singletonList("id"));
- final AggregateFilterData data2 =
- new AggregateFilterData.Builder()
+ final FilterData data2 =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionFilterMap)
.build();
- final Set<AggregateFilterData> dataSet1 = Set.of(data1);
- final Set<AggregateFilterData> dataSet2 = Set.of(data2);
+ final Set<FilterData> dataSet1 = Set.of(data1);
+ final Set<FilterData> dataSet2 = Set.of(data2);
assertNotEquals(data1.hashCode(), data2.hashCode());
assertNotEquals(data1, data2);
assertNotEquals(dataSet1, dataSet2);
}
- private AggregateFilterData createExample() {
+ private FilterData createExample() {
Map<String, List<String>> attributionFilterMap = new HashMap<>();
attributionFilterMap.put("type", Arrays.asList("1", "2", "3", "4"));
attributionFilterMap.put("ctid", Collections.singletonList("id"));
- return new AggregateFilterData.Builder()
+ return new FilterData.Builder()
.setAttributionFilterMap(attributionFilterMap)
.build();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java
index b52294d8c..8ccb89a21 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementHttpClientTest.java
@@ -28,6 +28,7 @@ import com.android.adservices.MockWebServerRuleFactory;
import com.android.modules.utils.testing.TestableDeviceConfig;
import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
import org.json.JSONObject;
import org.junit.Assert;
@@ -70,6 +71,33 @@ public final class MeasurementHttpClientTest {
}
@Test
+ public void testSetup_headersLeakingInfoAreOverridden() throws Exception {
+ final MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
+ MockWebServer server = null;
+ try {
+ server =
+ mMockWebServerRule.startMockWebServer(
+ request -> {
+ Assert.assertNotNull(request);
+ final String userAgentHeader = request.getHeader("user-agent");
+ Assert.assertNotNull(userAgentHeader);
+ Assert.assertEquals("", userAgentHeader);
+ return new MockResponse().setResponseCode(200);
+ });
+
+ final URL url = server.getUrl("/test");
+ final HttpURLConnection urlConnection =
+ (HttpURLConnection) mNetworkConnection.setup(url);
+
+ Assert.assertEquals(200, urlConnection.getResponseCode());
+ } finally {
+ if (server != null) {
+ server.shutdown();
+ }
+ }
+ }
+
+ @Test
public void testOpenAndSetupConnectionOverrideTimeoutValues_success() throws Exception {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_ADSERVICES,
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java
index 7cf2175b2..b81c5f795 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementImplTest.java
@@ -18,73 +18,46 @@ package com.android.adservices.service.measurement;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
-import static android.adservices.common.AdServicesStatusUtils.STATUS_IO_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
import static android.view.MotionEvent.ACTION_BUTTON_PRESS;
-import static com.android.adservices.data.measurement.DatastoreManager.ThrowingCheckedConsumer;
import static com.android.adservices.service.measurement.attribution.TriggerContentProvider.TRIGGER_URI;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.adservices.measurement.DeletionParam;
import android.adservices.measurement.DeletionRequest;
-import android.adservices.measurement.MeasurementManager;
-import android.adservices.measurement.RegistrationRequest;
import android.adservices.measurement.WebSourceParams;
import android.adservices.measurement.WebSourceRegistrationRequest;
import android.adservices.measurement.WebSourceRegistrationRequestInternal;
import android.adservices.measurement.WebTriggerParams;
import android.adservices.measurement.WebTriggerRegistrationRequest;
import android.adservices.measurement.WebTriggerRegistrationRequestInternal;
-import android.content.AttributionSource;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.RemoteException;
-import android.test.mock.MockContext;
-import android.test.mock.MockPackageManager;
import android.view.InputEvent;
import android.view.MotionEvent;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.enrollment.EnrollmentDao;
-import com.android.adservices.data.measurement.DatastoreException;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
-import com.android.adservices.data.measurement.IMeasurementDao;
-import com.android.adservices.data.measurement.ITransaction;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
+import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
-import com.android.adservices.service.consent.AdServicesApiConsent;
-import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.measurement.inputverification.ClickVerifier;
-import com.android.adservices.service.measurement.registration.SourceFetcher;
-import com.android.adservices.service.measurement.registration.SourceRegistration;
-import com.android.adservices.service.measurement.registration.TriggerFetcher;
-import com.android.adservices.service.measurement.registration.TriggerRegistration;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.modules.utils.testing.TestableDeviceConfig;
@@ -92,26 +65,16 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.Spy;
import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
import java.time.Instant;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
/** Unit tests for {@link MeasurementImpl} */
@SmallTest
@@ -119,160 +82,48 @@ public final class MeasurementImplTest {
@Rule
public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
new TestableDeviceConfig.TestableDeviceConfigRule();
-
private static final Context DEFAULT_CONTEXT = ApplicationProvider.getApplicationContext();
- private static final Uri URI_WITHOUT_APP_SCHEME = Uri.parse("com.example.abc");
private static final Uri DEFAULT_URI = Uri.parse("android-app://com.example.abc");
private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com/bar?ad=134");
private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo.com/bar?ad=256");
private static final String DEFAULT_ENROLLMENT = "enrollment-id";
+ private static final Uri INVALID_WEB_DESTINATION = Uri.parse("https://example.not_a_tld");
private static final Uri WEB_DESTINATION = Uri.parse("https://web-destination.com");
- private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN =
- Uri.parse("https://subdomain.web-destination.com");
private static final Uri OTHER_WEB_DESTINATION = Uri.parse("https://other-web-destination.com");
- private static final Uri INVALID_WEB_DESTINATION = Uri.parse("https://example.not_a_tld");
private static final Uri APP_DESTINATION = Uri.parse("android-app://com.app_destination");
private static final Uri OTHER_APP_DESTINATION =
Uri.parse("android-app://com.other_app_destination");
- private static final String ANDROID_APP_SCHEME = "android-app://";
- private static final String VENDING_PREFIX = "https://play.google.com/store/apps/details?id=";
- private static final RegistrationRequest SOURCE_REGISTRATION_REQUEST =
- createRegistrationRequest(RegistrationRequest.REGISTER_SOURCE);
- private static final RegistrationRequest TRIGGER_REGISTRATION_REQUEST =
- createRegistrationRequest(RegistrationRequest.REGISTER_TRIGGER);
- private static final String TOP_LEVEL_FILTERS_JSON_STRING =
- "{\n"
- + " \"key_1\": [\"value_1\", \"value_2\"],\n"
- + " \"key_2\": [\"value_1\", \"value_2\"]\n"
- + "}\n";
- private static final long TRIGGER_PRIORITY = 345678L;
- private static final Long TRIGGER_DEDUP_KEY = 2345678L;
- private static final Long TRIGGER_DATA = 1L;
- private static final String EVENT_TRIGGERS =
- "[\n"
- + "{\n"
- + " \"trigger_data\": \""
- + TRIGGER_DATA
- + "\",\n"
- + " \"priority\": \""
- + TRIGGER_PRIORITY
- + "\",\n"
- + " \"deduplication_key\": \""
- + TRIGGER_DEDUP_KEY
- + "\",\n"
- + " \"filters\": {\n"
- + " \"source_type\": [\"navigation\"],\n"
- + " \"key_1\": [\"value_1\"] \n"
- + " }\n"
- + "}"
- + "]\n";
- private static final TriggerRegistration VALID_TRIGGER_REGISTRATION =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse("https://foo.com"))
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setEventTriggers(EVENT_TRIGGERS)
- .setAggregateTriggerData(
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]")
- .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
- .setFilters(TOP_LEVEL_FILTERS_JSON_STRING)
- .build();
- private static final SourceRegistration VALID_SOURCE_REGISTRATION_1 =
- new com.android.adservices.service.measurement.registration.SourceRegistration.Builder()
- .setSourceEventId(1L)
- .setSourcePriority(100L)
- .setAppDestination(Uri.parse("android-app://com.destination"))
- .setWebDestination(Uri.parse("https://web-destination.com"))
- .setExpiry(8640000010L)
- .setInstallAttributionWindow(841839879274L)
- .setInstallCooldownWindow(8418398274L)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setTopOrigin(Uri.parse("android-app://com.source"))
- .build();
- private static final SourceRegistration VALID_SOURCE_REGISTRATION_2 =
- new com.android.adservices.service.measurement.registration.SourceRegistration.Builder()
- .setSourceEventId(2)
- .setSourcePriority(200L)
- .setAppDestination(Uri.parse("android-app://com.destination2"))
- .setWebDestination(Uri.parse("https://web-destination2.com"))
- .setExpiry(865000010L)
- .setInstallAttributionWindow(841839879275L)
- .setInstallCooldownWindow(7418398274L)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setTopOrigin(Uri.parse("android-app://com.source2"))
- .build();
+
private static final WebSourceParams INPUT_SOURCE_REGISTRATION_1 =
new WebSourceParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
-
private static final WebSourceParams INPUT_SOURCE_REGISTRATION_2 =
new WebSourceParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
-
private static final WebTriggerParams INPUT_TRIGGER_REGISTRATION_1 =
new WebTriggerParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
-
private static final WebTriggerParams INPUT_TRIGGER_REGISTRATION_2 =
new WebTriggerParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
-
private static final long REQUEST_TIME = 10000L;
@Spy
private DatastoreManager mDatastoreManager =
- DatastoreManagerFactory.getDatastoreManager(DEFAULT_CONTEXT);
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
@Mock
private ContentProviderClient mMockContentProviderClient;
@Mock
private ContentResolver mContentResolver;
- @Mock
- private SourceFetcher mSourceFetcher;
- @Mock
- private TriggerFetcher mTriggerFetcher;
- @Mock
- private IMeasurementDao mMeasurementDao;
- @Mock
- private ConsentManager mConsentManager;
@Mock private ClickVerifier mClickVerifier;
private MeasurementImpl mMeasurementImpl;
@Mock
- ITransaction mTransaction;
- @Mock
EnrollmentDao mEnrollmentDao;
+ @Mock MeasurementDataDeleter mMeasurementDataDeleter;
private static EnrollmentData getEnrollment(String enrollmentId) {
return new EnrollmentData.Builder().setEnrollmentId(enrollmentId).build();
}
-
- class FakeDatastoreManager extends DatastoreManager {
-
- @Override
- public ITransaction createNewTransaction() {
- return mTransaction;
- }
-
- @Override
- public IMeasurementDao getMeasurementDao() {
- return mMeasurementDao;
- }
- }
-
- private interface AppVendorPackages {
- String PLAY_STORE = "com.android.vending";
- }
-
public static InputEvent getInputEvent() {
return MotionEvent.obtain(0, 0, ACTION_BUTTON_PRESS, 0, 0, 0);
}
-
- private static RegistrationRequest createRegistrationRequest(int type) {
- return new RegistrationRequest.Builder()
- .setRegistrationUri(REGISTRATION_URI_1)
- .setTopOriginUri(DEFAULT_URI)
- .setPackageName(DEFAULT_CONTEXT.getAttributionSource().getPackageName())
- .setRegistrationType(type)
- .build();
- }
-
private static WebTriggerRegistrationRequestInternal createWebTriggerRegistrationRequest(
Uri destination) {
WebTriggerRegistrationRequest webTriggerRegistrationRequest =
@@ -284,29 +135,32 @@ public final class MeasurementImplTest {
return new WebTriggerRegistrationRequestInternal.Builder(
webTriggerRegistrationRequest,
DEFAULT_CONTEXT.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(true)
.build();
}
-
private static WebSourceRegistrationRequestInternal createWebSourceRegistrationRequest(
Uri appDestination, Uri webDestination, Uri verifiedDestination) {
-
+ return createWebSourceRegistrationRequest(
+ appDestination, webDestination, verifiedDestination, DEFAULT_URI);
+ }
+ private static WebSourceRegistrationRequestInternal createWebSourceRegistrationRequest(
+ Uri appDestination, Uri webDestination, Uri verifiedDestination, Uri topOriginUri) {
WebSourceRegistrationRequest sourceRegistrationRequest =
new WebSourceRegistrationRequest.Builder(
Arrays.asList(
INPUT_SOURCE_REGISTRATION_1, INPUT_SOURCE_REGISTRATION_2),
- DEFAULT_URI)
+ topOriginUri)
.setAppDestination(appDestination)
.setWebDestination(webDestination)
.setVerifiedDestination(verifiedDestination)
.build();
-
return new WebSourceRegistrationRequestInternal.Builder(
sourceRegistrationRequest,
DEFAULT_CONTEXT.getAttributionSource().getPackageName(),
REQUEST_TIME)
+ .setAdIdPermissionGranted(true)
.build();
}
-
@Before
public void before() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -317,264 +171,25 @@ public final class MeasurementImplTest {
spy(
new MeasurementImpl(
DEFAULT_CONTEXT,
- mContentResolver,
mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
+ mClickVerifier,
+ mMeasurementDataDeleter,
+ mEnrollmentDao));
doReturn(true).when(mClickVerifier).isInputEventVerifiable(any(), anyLong());
when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any()))
.thenReturn(getEnrollment(DEFAULT_ENROLLMENT));
}
@Test
- public void testRegister_registrationTypeSource_sourceFetchSuccess()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- RegistrationRequest registrationRequest = SOURCE_REGISTRATION_REQUEST;
- doReturn(Optional.of(sourceRegistrationsOut)).when(mSourceFetcher).fetchSource(any());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurementImpl =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurementImpl).generateFakeEventReports(any());
- final int result = measurementImpl.register(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchSource(any());
- verify(datastoreManager, times(2))
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(4))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, times(4)).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchTrigger(any());
-
- List<ThrowingCheckedConsumer> insertionLogicExecutor =
- insertionLogicExecutorCaptor.getAllValues();
- assertEquals(2, insertionLogicExecutor.size());
-
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_1,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_2,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- }
-
- @Test
- public void testRegister_registrationTypeSource_sourceFetchFailure() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(mMeasurementImpl).generateFakeEventReports(any());
- final int result =
- mMeasurementImpl.register(SOURCE_REGISTRATION_REQUEST, System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty()
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchSource(any());
- verify(mTriggerFetcher, never()).fetchTrigger(any());
- }
-
- @Test
- public void testRegister_registrationTypeSource_exceedsPrivacyParam_destination()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- RegistrationRequest registrationRequest = SOURCE_REGISTRATION_REQUEST;
- doReturn(Optional.of(sourceRegistrationsOut)).when(mSourceFetcher).fetchSource(any());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(100));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurement =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurement).generateFakeEventReports(any());
- final int result = measurement.register(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchSource(any());
- verify(datastoreManager, never())
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(2))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, never()).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchTrigger(any());
- }
-
- @Test
- public void testRegister_registrationTypeSource_exceedsPrivacyParam_adTech()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- RegistrationRequest registrationRequest = SOURCE_REGISTRATION_REQUEST;
- doReturn(Optional.of(sourceRegistrationsOut)).when(mSourceFetcher).fetchSource(any());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(100))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurement =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurement).generateFakeEventReports(any());
- final int result = measurement.register(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchSource(any());
- verify(datastoreManager, times(1))
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(4))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, times(3)).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchTrigger(any());
-
- List<ThrowingCheckedConsumer> insertionLogicExecutor =
- insertionLogicExecutorCaptor.getAllValues();
- assertEquals(1, insertionLogicExecutor.size());
-
- // First registration was removed for exceeding the privacy bound.
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_2,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- }
-
- @Test
- public void testRegister_registrationTypeTrigger_triggerFetchSuccess() throws Exception {
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgumentCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- final long triggerTime = System.currentTimeMillis();
- Answer<Optional<List<TriggerRegistration>>> populateTriggerRegistrations =
- invocation -> {
- List<TriggerRegistration> triggerRegs = new ArrayList<>();
- triggerRegs.add(VALID_TRIGGER_REGISTRATION);
- return Optional.of(triggerRegs);
- };
- doAnswer(populateTriggerRegistrations)
- .when(mTriggerFetcher)
- .fetchTrigger(TRIGGER_REGISTRATION_REQUEST);
-
- // Execution
- final int result = mMeasurementImpl.register(TRIGGER_REGISTRATION_REQUEST, triggerTime);
- verify(mDatastoreManager).runInTransaction(consumerArgumentCaptor.capture());
- consumerArgumentCaptor.getValue().accept(mMeasurementDao);
-
- // Assertions
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient).insert(any(), any());
- verify(mSourceFetcher, never()).fetchSource(any());
- verify(mTriggerFetcher, times(1)).fetchTrigger(any());
- Trigger trigger =
- createTrigger(
- triggerTime,
- DEFAULT_CONTEXT.getAttributionSource(),
- DEFAULT_URI,
- EventSurfaceType.APP);
- verify(mMeasurementDao).insertTrigger(trigger);
- }
-
- @Test
- public void testRegister_registrationTypeTrigger_triggerFetchFailure() throws RemoteException {
- when(mTriggerFetcher.fetchTrigger(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.register(TRIGGER_REGISTRATION_REQUEST, System.currentTimeMillis());
- assertEquals(STATUS_IO_ERROR, result);
-
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, never()).fetchSource(any());
- verify(mTriggerFetcher, times(1)).fetchTrigger(any());
- }
-
- @Test
public void testDeleteRegistrations_successfulNoOptionalParameters() {
MeasurementImpl measurement =
new MeasurementImpl(
DEFAULT_CONTEXT,
- mContentResolver,
- DatastoreManagerFactory.getDatastoreManager(DEFAULT_CONTEXT),
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest()),
+ mClickVerifier,
+ mMeasurementDataDeleter,
+ mEnrollmentDao);
+ doReturn(true).when(mMeasurementDataDeleter).delete(any());
final int result =
measurement.deleteRegistrations(
new DeletionParam.Builder()
@@ -589,7 +204,18 @@ public final class MeasurementImplTest {
}
@Test
+ public void testRegisterWebSource_verifiedDestination_webDestinationMismatch() {
+ final int result =
+ mMeasurementImpl.registerWebSource(
+ createWebSourceRegistrationRequest(
+ APP_DESTINATION, WEB_DESTINATION, OTHER_WEB_DESTINATION),
+ System.currentTimeMillis());
+ assertEquals(STATUS_INVALID_ARGUMENT, result);
+ }
+
+ @Test
public void testDeleteRegistrations_successfulWithRange() {
+ doReturn(true).when(mMeasurementDataDeleter).delete(any());
final int result =
mMeasurementImpl.deleteRegistrations(
new DeletionParam.Builder()
@@ -607,24 +233,25 @@ public final class MeasurementImplTest {
@Test
public void testDeleteRegistrations_successfulWithOrigin() {
- final int result =
- mMeasurementImpl.deleteRegistrations(
- new DeletionParam.Builder()
- .setPackageName(
- DEFAULT_CONTEXT.getAttributionSource().getPackageName())
- .setDomainUris(Collections.emptyList())
- .setOriginUris(Collections.singletonList(DEFAULT_URI))
- .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
- .setDeletionMode(DeletionRequest.DELETION_MODE_ALL)
- .setStart(Instant.ofEpochMilli(Long.MIN_VALUE))
- .setEnd(Instant.ofEpochMilli(Long.MAX_VALUE))
- .build());
+ DeletionParam deletionParam =
+ new DeletionParam.Builder()
+ .setPackageName(DEFAULT_CONTEXT.getAttributionSource().getPackageName())
+ .setDomainUris(Collections.emptyList())
+ .setOriginUris(Collections.singletonList(DEFAULT_URI))
+ .setMatchBehavior(DeletionRequest.MATCH_BEHAVIOR_DELETE)
+ .setDeletionMode(DeletionRequest.DELETION_MODE_ALL)
+ .setStart(Instant.ofEpochMilli(Long.MIN_VALUE))
+ .setEnd(Instant.ofEpochMilli(Long.MAX_VALUE))
+ .build();
+ when(mMeasurementDataDeleter.delete(deletionParam)).thenReturn(true);
+ final int result = mMeasurementImpl.deleteRegistrations(deletionParam);
assertEquals(STATUS_SUCCESS, result);
}
@Test
public void testDeleteRegistrations_internalError() {
doReturn(false).when(mDatastoreManager).runInTransaction(any());
+ doReturn(false).when(mMeasurementDataDeleter).delete(any());
final int result =
mMeasurementImpl.deleteRegistrations(
new DeletionParam.Builder()
@@ -638,607 +265,34 @@ public final class MeasurementImplTest {
.setEnd(Instant.MAX)
.build());
assertEquals(STATUS_INTERNAL_ERROR, result);
- }
-
- @Test
- public void testSourceRegistration_callsImpressionNoiseCreator() throws DatastoreException {
- long eventTime = System.currentTimeMillis();
- long expiry = TimeUnit.DAYS.toSeconds(20);
- // Creating source for easy comparison
- Source sampleSource =
- SourceFixture.getValidSourceBuilder()
- .setSourceType(Source.SourceType.NAVIGATION)
- .setExpiryTime(eventTime + TimeUnit.SECONDS.toMillis(expiry))
- .setEventTime(eventTime)
- .setPublisher(DEFAULT_URI)
- .setAppDestination(Uri.parse("android-app://com.example.abc"))
- .setEventId(123L)
- .build();
- // Mocking fetchSource call to populate source registrations.
- List<SourceRegistration> sourceRegistrations =
- Collections.singletonList(
- new SourceRegistration.Builder()
- .setSourceEventId(sampleSource.getEventId())
- .setAppDestination(sampleSource.getAppDestination())
- .setTopOrigin(sampleSource.getPublisher())
- .setExpiry(expiry)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .build());
- doReturn(Optional.of(sourceRegistrations)).when(mSourceFetcher).fetchSource(any());
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
-
- MeasurementImpl measurementImpl =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- new FakeDatastoreManager(),
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- InputEvent inputEvent = getInputEvent();
- final int result =
- measurementImpl.register(
- new RegistrationRequest.Builder()
- .setRegistrationUri(REGISTRATION_URI_1)
- .setTopOriginUri(DEFAULT_URI)
- .setPackageName(
- DEFAULT_CONTEXT.getAttributionSource().getPackageName())
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setInputEvent(inputEvent)
- .build(),
- eventTime);
- assertEquals(STATUS_SUCCESS, result);
- ArgumentCaptor<Source> sourceArgs = ArgumentCaptor.forClass(Source.class);
- verify(measurementImpl).generateFakeEventReports(sourceArgs.capture());
- Source capturedSource = sourceArgs.getValue();
- assertEquals(sampleSource.getSourceType(), capturedSource.getSourceType());
- assertEquals(sampleSource.getEventId(), capturedSource.getEventId());
- assertEquals(sampleSource.getEventTime(), capturedSource.getEventTime());
- assertEquals(sampleSource.getAggregateSource(), capturedSource.getAggregateSource());
- assertEquals(sampleSource.getAppDestination(), capturedSource.getAppDestination());
- assertEquals(sampleSource.getPublisher(), capturedSource.getPublisher());
- assertEquals(sampleSource.getPriority(), capturedSource.getPriority());
-
- // Check Attribution Mode assignment
- assertNotEquals(Source.AttributionMode.UNASSIGNED, capturedSource.getAttributionMode());
- }
-
- @Test
- public void testGetSourceEventReports() {
- long eventTime = System.currentTimeMillis();
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setEventId(123L)
- .setEventTime(eventTime)
- .setExpiryTime(eventTime + TimeUnit.DAYS.toMillis(20))
- .setSourceType(Source.SourceType.NAVIGATION)
- .setAppDestination(DEFAULT_URI)
- .setPublisher(DEFAULT_URI)
- .build());
- when(source.getRandomAttributionProbability()).thenReturn(1.1D);
- DatastoreManager mockDatastoreManager = Mockito.mock(DatastoreManager.class);
- SourceFetcher mockSourceFetcher = Mockito.mock(SourceFetcher.class);
- TriggerFetcher mockTriggerFetcher = Mockito.mock(TriggerFetcher.class);
-
- List<EventReport> fakeEventReports = mMeasurementImpl.generateFakeEventReports(source);
-
- // Generate valid report times
- Set<Long> reportingTimes = new HashSet<>();
- reportingTimes.add(
- source.getReportingTime(
- eventTime + TimeUnit.DAYS.toMillis(1), EventSurfaceType.APP));
- reportingTimes.add(
- source.getReportingTime(
- eventTime + TimeUnit.DAYS.toMillis(3), EventSurfaceType.APP));
- reportingTimes.add(
- source.getReportingTime(
- eventTime + TimeUnit.DAYS.toMillis(8), EventSurfaceType.APP));
-
- for (EventReport report : fakeEventReports) {
- Assert.assertEquals(source.getEventId(), report.getSourceId());
- Assert.assertTrue(reportingTimes.stream().anyMatch(x -> x == report.getReportTime()));
- Assert.assertEquals(source.getEventTime(), report.getTriggerTime());
- Assert.assertEquals(0, report.getTriggerPriority());
- Assert.assertEquals(source.getAppDestination(), report.getAttributionDestination());
- Assert.assertTrue(report.getTriggerData()
- < source.getTriggerDataCardinality());
- Assert.assertNull(report.getTriggerDedupKey());
- Assert.assertEquals(EventReport.Status.PENDING, report.getStatus());
- Assert.assertEquals(source.getSourceType(), report.getSourceType());
- Assert.assertEquals(source.getRandomAttributionProbability(),
- report.getRandomizedTriggerRate(), /* delta= */ 0.00001D);
- }
- }
-
- @Test
- public void testInstallAttribution() throws DatastoreException {
- // Setup
- long systemTime = System.currentTimeMillis();
-
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgumentCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- // Execution
- mMeasurementImpl.doInstallAttribution(URI_WITHOUT_APP_SCHEME, systemTime);
- verify(mDatastoreManager).runInTransaction(consumerArgumentCaptor.capture());
-
- consumerArgumentCaptor.getValue().accept(mMeasurementDao);
-
- // Assertion
- verify(mMeasurementDao).doInstallAttribution(DEFAULT_URI, systemTime);
- }
-
- @Test
- public void testGetMeasurementApiStatus_enabled() {
- MockitoSession session =
- ExtendedMockito.mockitoSession()
- .spyStatic(ConsentManager.class)
- .initMocks(this)
- .startMocking();
- try {
- ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any());
- ExtendedMockito.doReturn(mConsentManager).when(() -> ConsentManager.getInstance(any()));
- final int result = mMeasurementImpl.getMeasurementApiStatus();
- assertEquals(MeasurementManager.MEASUREMENT_API_STATE_ENABLED, result);
- } finally {
- session.finishMocking();
}
- }
-
- @Test
- public void testGetMeasurementApiStatus_disabled() {
- MockitoSession session =
- ExtendedMockito.mockitoSession()
- .spyStatic(ConsentManager.class)
- .initMocks(this)
- .startMocking();
- try {
- ExtendedMockito.doReturn(AdServicesApiConsent.REVOKED)
- .when(mConsentManager)
- .getConsent(any());
- ExtendedMockito.doReturn(mConsentManager).when(() -> ConsentManager.getInstance(any()));
- final int result = mMeasurementImpl.getMeasurementApiStatus();
- assertEquals(MeasurementManager.MEASUREMENT_API_STATE_DISABLED, result);
- } finally {
- session.finishMocking();
- }
- }
-
- @Test
- public void testDeleteAllMeasurementData() throws DatastoreException {
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgumentCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- // Execution
- mMeasurementImpl.deleteAllMeasurementData(Collections.emptyList());
- verify(mDatastoreManager).runInTransaction(consumerArgumentCaptor.capture());
-
- consumerArgumentCaptor.getValue().accept(mMeasurementDao);
- verify(mMeasurementDao, times(1)).deleteAllMeasurementData(any());
- }
-
- @Test
- public void registerWebSource_sourceFetchSuccess() throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- WebSourceRegistrationRequestInternal registrationRequest =
- createWebSourceRegistrationRequest(APP_DESTINATION, WEB_DESTINATION, null);
- doReturn(Optional.of(sourceRegistrationsOut))
- .when(mSourceFetcher)
- .fetchWebSources(any(), anyBoolean());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurementImpl =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurementImpl).generateFakeEventReports(any());
- final int result = measurementImpl.registerWebSource(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- verify(datastoreManager, times(2))
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(4))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, times(4)).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
-
- List<ThrowingCheckedConsumer> insertionLogicExecutor =
- insertionLogicExecutorCaptor.getAllValues();
- assertEquals(2, insertionLogicExecutor.size());
-
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_1,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_2,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- }
-
- @Test
- public void registerWebSource_sourceFetchFailure() {
- when(mSourceFetcher.fetchWebSources(any(), anyBoolean())).thenReturn(Optional.empty());
-
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(mMeasurementImpl).generateFakeEventReports(any());
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(APP_DESTINATION, WEB_DESTINATION, null),
- System.currentTimeMillis());
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
- }
@Test
- public void registerWebSource_invalidWebDestination() {
+ public void testRegisterWebSource_invalidWebDestination() {
final int result =
mMeasurementImpl.registerWebSource(
createWebSourceRegistrationRequest(null, INVALID_WEB_DESTINATION, null),
System.currentTimeMillis());
assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mSourceFetcher, never()).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_exceedsPrivacyParam_destination()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- WebSourceRegistrationRequestInternal registrationRequest =
- createWebSourceRegistrationRequest(APP_DESTINATION, WEB_DESTINATION, null);
- doReturn(Optional.of(sourceRegistrationsOut))
- .when(mSourceFetcher)
- .fetchWebSources(any(), anyBoolean());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(100));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurement =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurement).generateFakeEventReports(any());
- final int result = measurement.registerWebSource(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- verify(datastoreManager, never())
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(2))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, never()).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_exceedsPrivacyParam_adTech()
- throws RemoteException, DatastoreException {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- WebSourceRegistrationRequestInternal registrationRequest =
- createWebSourceRegistrationRequest(APP_DESTINATION, WEB_DESTINATION, null);
- doReturn(Optional.of(sourceRegistrationsOut))
- .when(mSourceFetcher)
- .fetchWebSources(any(), anyBoolean());
- ArgumentCaptor<ThrowingCheckedConsumer> insertionLogicExecutorCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
-
- when(mMeasurementDao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(0));
- when(mMeasurementDao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong()))
- .thenReturn(Integer.valueOf(100))
- .thenReturn(Integer.valueOf(0));
- DatastoreManager datastoreManager = spy(new FakeDatastoreManager());
-
- // Test
- MeasurementImpl measurement =
- spy(
- new MeasurementImpl(
- null,
- mContentResolver,
- datastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier));
-
- long eventTime = System.currentTimeMillis();
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurement).generateFakeEventReports(any());
- final int result = measurement.registerWebSource(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- verify(datastoreManager, times(1))
- .runInTransaction(insertionLogicExecutorCaptor.capture());
- verify(mMeasurementDao, times(4))
- .countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
- any(), anyInt(), any(), any(), anyInt(), anyLong(), anyLong());
- verify(mMeasurementDao, times(3)).countDistinctEnrollmentsPerPublisherXDestinationInSource(
- any(), anyInt(), any(), any(), anyLong(), anyLong());
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
-
- List<ThrowingCheckedConsumer> insertionLogicExecutor =
- insertionLogicExecutorCaptor.getAllValues();
- assertEquals(1, insertionLogicExecutor.size());
-
- // First registration was removed for exceeding the privacy bound.
- verifyInsertSource(
- registrationRequest,
- VALID_SOURCE_REGISTRATION_2,
- eventTime,
- VALID_SOURCE_REGISTRATION_1.getAppDestination(),
- VALID_SOURCE_REGISTRATION_1.getWebDestination());
- }
-
- @Test
- public void registerWebSource_webDestinationIsValidWhenNull() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(APP_DESTINATION, null, null),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_exactWebDestinationMatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, WEB_DESTINATION),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_topPrivateDomainMatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, WEB_DESTINATION_WITH_SUBDOMAIN),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
}
@Test
- public void registerWebSource_verifiedDestination_webDestinationMismatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
+ public void testRegisterWebTrigger_invalidDestination() throws RemoteException {
final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, OTHER_WEB_DESTINATION),
+ mMeasurementImpl.registerWebTrigger(
+ createWebTriggerRegistrationRequest(INVALID_WEB_DESTINATION),
System.currentTimeMillis());
assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mSourceFetcher, times(0)).fetchWebSources(any(), anyBoolean());
}
-
@Test
- public void registerWebSource_verifiedDestination_appDestinationMatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, APP_DESTINATION),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_appDestinationMismatch() {
- when(mSourceFetcher.fetchSource(any())).thenReturn(Optional.empty());
-
+ public void testRegisterWebSource_verifiedDestination_appDestinationMismatch() {
final int result =
mMeasurementImpl.registerWebSource(
createWebSourceRegistrationRequest(
APP_DESTINATION, WEB_DESTINATION, OTHER_APP_DESTINATION),
System.currentTimeMillis());
assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mSourceFetcher, times(0)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_vendingMatch() {
- when(mSourceFetcher.fetchWebSources(any(), anyBoolean())).thenReturn(Optional.empty());
- Uri vendingUri = Uri.parse(VENDING_PREFIX + APP_DESTINATION.getHost());
- MeasurementImpl measurementImpl = getMeasurementImplWithMockedIntentResolution();
- final int result = measurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, vendingUri),
- System.currentTimeMillis());
- // STATUS_IO_ERROR is expected when fetchSource returns Optional.empty();
- // it means validation passed and the procedure called the fetcher.
- assertEquals(STATUS_IO_ERROR, result);
- verify(mSourceFetcher, times(1)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebSource_verifiedDestination_vendingMismatch() {
- when(mSourceFetcher.fetchWebSources(any(), anyBoolean())).thenReturn(Optional.empty());
- Uri vendingUri = Uri.parse(VENDING_PREFIX + OTHER_APP_DESTINATION.getHost());
- MeasurementImpl measurementImpl = getMeasurementImplWithMockedIntentResolution();
- final int result = measurementImpl.registerWebSource(
- createWebSourceRegistrationRequest(
- APP_DESTINATION, WEB_DESTINATION, vendingUri),
- System.currentTimeMillis());
- assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mSourceFetcher, times(0)).fetchWebSources(any(), anyBoolean());
- }
-
- @Test
- public void registerWebTrigger_triggerFetchSuccess() throws Exception {
- // Setup
- when(mTriggerFetcher.fetchWebTriggers(any(), anyBoolean()))
- .thenReturn(Optional.of(Collections.singletonList(VALID_TRIGGER_REGISTRATION)));
-
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgumentCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- final long triggerTime = System.currentTimeMillis();
-
- // Execution
- final int result =
- mMeasurementImpl.registerWebTrigger(
- createWebTriggerRegistrationRequest(WEB_DESTINATION), triggerTime);
-
- verify(mDatastoreManager).runInTransaction(consumerArgumentCaptor.capture());
- consumerArgumentCaptor.getValue().accept(mMeasurementDao);
-
- // Assertions
- assertEquals(STATUS_SUCCESS, result);
- verify(mMockContentProviderClient).insert(any(), any());
- verify(mSourceFetcher, never()).fetchSource(any());
- verify(mTriggerFetcher, times(1)).fetchWebTriggers(any(), anyBoolean());
- verify(mMeasurementDao)
- .insertTrigger(
- eq(
- createTrigger(
- triggerTime,
- DEFAULT_CONTEXT.getAttributionSource(),
- WEB_DESTINATION,
- EventSurfaceType.WEB)));
- }
-
- @Test
- public void registerWebTrigger_triggerFetchFailure() throws RemoteException {
- when(mTriggerFetcher.fetchWebTriggers(any(), anyBoolean())).thenReturn(Optional.empty());
-
- final int result =
- mMeasurementImpl.registerWebTrigger(
- createWebTriggerRegistrationRequest(WEB_DESTINATION),
- System.currentTimeMillis());
-
- assertEquals(STATUS_IO_ERROR, result);
- verify(mMockContentProviderClient, never()).insert(any(), any());
- verify(mSourceFetcher, never()).fetchWebSources(any(), anyBoolean());
- verify(mTriggerFetcher, times(1)).fetchWebTriggers(any(), anyBoolean());
- }
-
- @Test
-
- public void registerWebTrigger_invalidDestination() throws RemoteException {
- final int result =
- mMeasurementImpl.registerWebTrigger(
- createWebTriggerRegistrationRequest(INVALID_WEB_DESTINATION),
- System.currentTimeMillis());
- assertEquals(STATUS_INVALID_ARGUMENT, result);
- verify(mTriggerFetcher, never()).fetchWebTriggers(any(), anyBoolean());
}
-
- @Test
- public void testRegister_registrationTypeSource_clickNotVerifiedFailure() {
- // setup
- List<SourceRegistration> sourceRegistrationsOut =
- Arrays.asList(VALID_SOURCE_REGISTRATION_1, VALID_SOURCE_REGISTRATION_2);
- doReturn(Optional.of(sourceRegistrationsOut)).when(mSourceFetcher).fetchSource(any());
-
- // Disable Impression Noise
- doReturn(Collections.emptyList()).when(mMeasurementImpl).generateFakeEventReports(any());
-
- doReturn(false).when(mClickVerifier).isInputEventVerifiable(any(), anyLong());
-
- RegistrationRequest registrationRequest =
- new RegistrationRequest.Builder()
- .setRegistrationUri(REGISTRATION_URI_1)
- .setTopOriginUri(DEFAULT_URI)
- .setPackageName(DEFAULT_CONTEXT.getAttributionSource().getPackageName())
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setInputEvent(getInputEvent())
- .build();
-
- long eventTime = System.currentTimeMillis();
- final int result = mMeasurementImpl.register(registrationRequest, eventTime);
-
- // Assert
- assertEquals(STATUS_SUCCESS, result);
- verify(mSourceFetcher, times(1)).fetchSource(any());
- }
-
@Test
public void testGetSourceType_verifiedInputEvent_returnsNavigationSourceType() {
doReturn(true).when(mClickVerifier).isInputEventVerifiable(any(), anyLong());
@@ -1246,12 +300,10 @@ public final class MeasurementImplTest {
Source.SourceType.NAVIGATION,
mMeasurementImpl.getSourceType(getInputEvent(), 1000L));
}
-
@Test
public void testGetSourceType_noInputEventGiven() {
assertEquals(Source.SourceType.EVENT, mMeasurementImpl.getSourceType(null, 1000L));
}
-
@Test
public void testGetSourceType_inputEventNotVerifiable_returnsEventSourceType() {
doReturn(false).when(mClickVerifier).isInputEventVerifiable(any(), anyLong());
@@ -1276,11 +328,10 @@ public final class MeasurementImplTest {
MeasurementImpl measurementImpl =
new MeasurementImpl(
DEFAULT_CONTEXT,
- mContentResolver,
mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mockClickVerifier);
+ mockClickVerifier,
+ mMeasurementDataDeleter,
+ mEnrollmentDao);
// Because click verification is disabled, the SourceType is NAVIGATION even if the
// input event is not verifiable.
@@ -1293,381 +344,4 @@ public final class MeasurementImplTest {
session.finishMocking();
}
}
-
- @Test
- public void insertSource_withFakeReportsFalseAppAttribution_accountsForFakeReportAttribution()
- throws DatastoreException {
- // Setup
- int fakeReportsCount = 2;
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setAppDestination(
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
- .setWebDestination(null)
- .build());
- List<Source.FakeReport> fakeReports =
- createFakeReports(
- source,
- fakeReportsCount,
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION);
- MeasurementImpl measurementImpl =
- new MeasurementImpl(
- null,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- Answer<?> falseAttributionAnswer =
- (arg) -> {
- source.setAttributionMode(Source.AttributionMode.FALSELY);
- return fakeReports;
- };
- doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
- ArgumentCaptor.forClass(Attribution.class);
-
- // Execution
- measurementImpl.insertSource(source);
-
- // Assertion
- verify(mDatastoreManager).runInTransaction(consumerArgCaptor.capture());
-
- consumerArgCaptor.getValue().accept(mMeasurementDao);
-
- verify(mMeasurementDao).insertSource(source);
- verify(mMeasurementDao, times(2)).insertEventReport(any());
- verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getAppDestination().toString())
- .setDestinationSite(source.getAppDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getValue());
- }
-
- @Test
- public void insertSource_withFakeReportsFalseWebAttribution_accountsForFakeReportAttribution()
- throws DatastoreException {
- // Setup
- int fakeReportsCount = 2;
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setAppDestination(null)
- .setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
- .build());
- List<Source.FakeReport> fakeReports =
- createFakeReports(
- source, fakeReportsCount, SourceFixture.ValidSourceParams.WEB_DESTINATION);
- MeasurementImpl measurementImpl =
- new MeasurementImpl(
- null,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- Answer<?> falseAttributionAnswer =
- (arg) -> {
- source.setAttributionMode(Source.AttributionMode.FALSELY);
- return fakeReports;
- };
- doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
- ArgumentCaptor.forClass(Attribution.class);
-
- // Execution
- measurementImpl.insertSource(source);
-
- // Assertion
- verify(mDatastoreManager).runInTransaction(consumerArgCaptor.capture());
-
- consumerArgCaptor.getValue().accept(mMeasurementDao);
-
- verify(mMeasurementDao).insertSource(source);
- verify(mMeasurementDao, times(2)).insertEventReport(any());
- verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getWebDestination().toString())
- .setDestinationSite(source.getWebDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getValue());
- }
-
- @Test
- public void insertSource_withFalseAppAndWebAttribution_accountsForFakeReportAttribution()
- throws DatastoreException {
- // Setup
- int fakeReportsCount = 2;
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setAppDestination(
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
- .setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
- .build());
- List<Source.FakeReport> fakeReports =
- createFakeReports(
- source,
- fakeReportsCount,
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION);
- MeasurementImpl measurementImpl =
- new MeasurementImpl(
- null,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- Answer<?> falseAttributionAnswer =
- (arg) -> {
- source.setAttributionMode(Source.AttributionMode.FALSELY);
- return fakeReports;
- };
- doAnswer(falseAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
- ArgumentCaptor.forClass(Attribution.class);
-
- // Execution
- measurementImpl.insertSource(source);
-
- // Assertion
- verify(mDatastoreManager).runInTransaction(consumerArgCaptor.capture());
-
- consumerArgCaptor.getValue().accept(mMeasurementDao);
-
- verify(mMeasurementDao).insertSource(source);
- verify(mMeasurementDao, times(2)).insertEventReport(any());
- verify(mMeasurementDao, times(2))
- .insertAttribution(attributionRateLimitArgCaptor.capture());
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getAppDestination().toString())
- .setDestinationSite(source.getAppDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getAllValues().get(0));
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getWebDestination().toString())
- .setDestinationSite(source.getWebDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getAllValues().get(1));
- }
-
- @Test
- public void insertSource_withFakeReportsNeverAppAttribution_accountsForFakeReportAttribution()
- throws DatastoreException {
- // Setup
- Source source =
- spy(
- SourceFixture.getValidSourceBuilder()
- .setAppDestination(
- SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
- .setWebDestination(null)
- .build());
- List<Source.FakeReport> fakeReports = Collections.emptyList();
- MeasurementImpl measurementImpl =
- new MeasurementImpl(
- null,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- Answer<?> neverAttributionAnswer =
- (arg) -> {
- source.setAttributionMode(Source.AttributionMode.NEVER);
- return fakeReports;
- };
- doAnswer(neverAttributionAnswer).when(source).assignAttributionModeAndGenerateFakeReports();
- ArgumentCaptor<ThrowingCheckedConsumer> consumerArgCaptor =
- ArgumentCaptor.forClass(ThrowingCheckedConsumer.class);
- ArgumentCaptor<Attribution> attributionRateLimitArgCaptor =
- ArgumentCaptor.forClass(Attribution.class);
-
- // Execution
- measurementImpl.insertSource(source);
-
- // Assertion
- verify(mDatastoreManager).runInTransaction(consumerArgCaptor.capture());
-
- consumerArgCaptor.getValue().accept(mMeasurementDao);
-
- verify(mMeasurementDao).insertSource(source);
- verify(mMeasurementDao, never()).insertEventReport(any());
- verify(mMeasurementDao).insertAttribution(attributionRateLimitArgCaptor.capture());
-
- assertEquals(
- new Attribution.Builder()
- .setDestinationOrigin(source.getAppDestination().toString())
- .setDestinationSite(source.getAppDestination().toString())
- .setEnrollmentId(source.getEnrollmentId())
- .setSourceOrigin(source.getPublisher().toString())
- .setSourceSite(source.getPublisher().toString())
- .setRegistrant(source.getRegistrant().toString())
- .setTriggerTime(source.getEventTime())
- .build(),
- attributionRateLimitArgCaptor.getValue());
- }
-
- private List<Source.FakeReport> createFakeReports(Source source, int count, Uri destination) {
- return IntStream.range(0, count)
- .mapToObj(
- x ->
- new Source.FakeReport(
- 0, source.getReportingTimeForNoising(0), destination))
- .collect(Collectors.toList());
- }
-
- private void verifyInsertSource(
- RegistrationRequest registrationRequest,
- SourceRegistration sourceRegistration,
- long eventTime,
- Uri firstSourceDestination,
- Uri firstSourceWebDestination)
- throws DatastoreException {
- Source source =
- createSource(
- sourceRegistration,
- eventTime,
- firstSourceDestination,
- firstSourceWebDestination,
- registrationRequest.getTopOriginUri(),
- EventSurfaceType.APP,
- registrationRequest.getPackageName());
- verify(mMeasurementDao).insertSource(source);
- }
-
- private void verifyInsertSource(
- WebSourceRegistrationRequestInternal registrationRequest,
- SourceRegistration sourceRegistration,
- long eventTime,
- Uri firstSourceDestination,
- Uri firstSourceWebDestination)
- throws DatastoreException {
- Source source =
- createSource(
- sourceRegistration,
- eventTime,
- firstSourceDestination,
- firstSourceWebDestination,
- registrationRequest.getSourceRegistrationRequest().getTopOriginUri(),
- EventSurfaceType.WEB,
- registrationRequest.getPackageName());
- verify(mMeasurementDao).insertSource(source);
- }
-
- private Source createSource(
- SourceRegistration sourceRegistration,
- long eventTime,
- Uri firstSourceDestination,
- Uri firstSourceWebDestination,
- Uri topOrigin,
- @EventSurfaceType int publisherType,
- String packageName) {
- return SourceFixture.getValidSourceBuilder()
- .setEventId(sourceRegistration.getSourceEventId())
- .setPublisher(topOrigin)
- .setPublisherType(publisherType)
- .setAppDestination(firstSourceDestination)
- .setWebDestination(firstSourceWebDestination)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setRegistrant(Uri.parse("android-app://" + packageName))
- .setEventTime(eventTime)
- .setExpiryTime(
- eventTime + TimeUnit.SECONDS.toMillis(sourceRegistration.getExpiry()))
- .setPriority(sourceRegistration.getSourcePriority())
- .setSourceType(Source.SourceType.EVENT)
- .setInstallAttributionWindow(
- TimeUnit.SECONDS.toMillis(sourceRegistration.getInstallAttributionWindow()))
- .setInstallCooldownWindow(
- TimeUnit.SECONDS.toMillis(sourceRegistration.getInstallCooldownWindow()))
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateSource(sourceRegistration.getAggregateSource())
- .setAggregateFilterData(sourceRegistration.getAggregateFilterData())
- .setDebugKey(sourceRegistration.getDebugKey())
- .build();
- }
-
- private Trigger createTrigger(long triggerTime, AttributionSource attributionSource,
- Uri destination, @EventSurfaceType int destinationType) {
- return TriggerFixture.getValidTriggerBuilder()
- .setAttributionDestination(destination)
- .setDestinationType(destinationType)
- .setEnrollmentId(DEFAULT_ENROLLMENT)
- .setRegistrant(Uri.parse(ANDROID_APP_SCHEME + attributionSource.getPackageName()))
- .setTriggerTime(triggerTime)
- .setEventTriggers(EVENT_TRIGGERS)
- .setAggregateTriggerData(
- MeasurementImplTest.VALID_TRIGGER_REGISTRATION.getAggregateTriggerData())
- .setAggregateValues(
- MeasurementImplTest.VALID_TRIGGER_REGISTRATION.getAggregateValues())
- .setFilters(MeasurementImplTest.VALID_TRIGGER_REGISTRATION.getFilters())
- .setDebugKey(MeasurementImplTest.VALID_TRIGGER_REGISTRATION.getDebugKey())
- .build();
- }
-
- private MeasurementImpl getMeasurementImplWithMockedIntentResolution() {
- ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.packageName = AppVendorPackages.PLAY_STORE;
- ActivityInfo activityInfo = new ActivityInfo();
- activityInfo.applicationInfo = applicationInfo;
- activityInfo.name = "non null String";
- ResolveInfo resolveInfo = new ResolveInfo();
- resolveInfo.activityInfo = activityInfo;
- MockPackageManager mockPackageManager =
- new MockPackageManager() {
- @Override
- public ResolveInfo resolveActivity(Intent intent, int flags) {
- return resolveInfo;
- }
- };
- MockContext mockContext =
- new MockContext() {
- @Override
- public PackageManager getPackageManager() {
- return mockPackageManager;
- }
- };
- return new MeasurementImpl(
- mockContext,
- mContentResolver,
- mDatastoreManager,
- mSourceFetcher,
- mTriggerFetcher,
- mClickVerifier);
- }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java
index 4811f2a5b..4cd9613a3 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/MeasurementServiceImplTest.java
@@ -156,8 +156,7 @@ public final class MeasurementServiceImplTest {
when(mMockMeasurementImpl.registerWebSource(
any(WebSourceRegistrationRequestInternal.class), anyLong()))
.thenReturn(STATUS_SUCCESS);
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.GIVEN);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
when(mMockFlags.getWebContextClientAppAllowList()).thenReturn("*");
when(mMockThrottler.tryAcquire(any(), any())).thenReturn(true).thenReturn(false);
when(mMockFlags.getWebContextClientAppAllowList()).thenReturn(ALLOW_ALL_PACKAGES);
@@ -1206,12 +1205,10 @@ public final class MeasurementServiceImplTest {
try {
ExtendedMockito.doReturn(1).when(Binder::getCallingUidOrThrow);
- ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any());
+ ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
ExtendedMockito.doReturn(mConsentManager).when(() -> ConsentManager.getInstance(any()));
MeasurementImpl measurementImpl =
- new MeasurementImpl(mMockContext, null, null, null, null, null);
+ new MeasurementImpl(mMockContext, null, null, null, null);
when(mMockFlags.getPpapiAppAllowList()).thenReturn("*");
CountDownLatch countDownLatch = new CountDownLatch(1);
final AtomicInteger resultWrapper = new AtomicInteger();
@@ -1259,9 +1256,7 @@ public final class MeasurementServiceImplTest {
try {
ExtendedMockito.doReturn(1).when(Binder::getCallingUidOrThrow);
- ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any());
+ ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
doNothing()
.when(mMockAppImportanceFilter)
@@ -1269,7 +1264,7 @@ public final class MeasurementServiceImplTest {
ExtendedMockito.doReturn(mConsentManager).when(() -> ConsentManager.getInstance(any()));
MeasurementImpl measurementImpl =
- new MeasurementImpl(mMockContext, null, null, null, null, null);
+ new MeasurementImpl(mMockContext, null, null, null, null);
when(mMockFlags.getPpapiAppAllowList()).thenReturn("*");
CountDownLatch countDownLatch = new CountDownLatch(1);
final AtomicInteger resultWrapper = new AtomicInteger();
@@ -1317,9 +1312,7 @@ public final class MeasurementServiceImplTest {
try {
ExtendedMockito.doReturn(1).when(Binder::getCallingUidOrThrow);
- ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN)
- .when(mConsentManager)
- .getConsent(any());
+ ExtendedMockito.doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
doThrow(new AppImportanceFilter.WrongCallingApplicationStateException())
.when(mMockAppImportanceFilter)
@@ -2137,8 +2130,7 @@ public final class MeasurementServiceImplTest {
public void testRegister_userRevokedConsent() {
try {
mockAccessControl(true, true, true);
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.REVOKED);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.REVOKED);
mMeasurementServiceImpl.register(
getDefaultRegistrationSourceRequest(),
mCallerMetadata,
@@ -2163,9 +2155,7 @@ public final class MeasurementServiceImplTest {
public void testRegisterWebSource_userRevokedConsent() {
try {
mockAccessControl(true, true, true);
- doReturn(AdServicesApiConsent.REVOKED)
- .when(mConsentManager)
- .getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent();
mMeasurementServiceImpl.registerWebSource(
createWebSourceRegistrationRequest(),
@@ -2191,7 +2181,7 @@ public final class MeasurementServiceImplTest {
public void testRegisterWebSource_packageNotAllowListed() throws InterruptedException {
try {
mockAccessControl(true, true, true);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
doReturn(ALLOW_LIST_WITHOUT_TEST_PACKAGE)
.when(mMockFlags)
.getWebContextClientAppAllowList();
@@ -2226,9 +2216,7 @@ public final class MeasurementServiceImplTest {
public void testRegisterWebTrigger_userRevokedConsent() {
try {
mockAccessControl(true, true, true);
- doReturn(AdServicesApiConsent.REVOKED)
- .when(mConsentManager)
- .getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent();
mMeasurementServiceImpl.registerWebTrigger(
createWebTriggerRegistrationRequest(),
@@ -2256,7 +2244,7 @@ public final class MeasurementServiceImplTest {
try {
// Setup
mockAccessControl(true, true, true);
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
doReturn(ALLOW_LIST_WITHOUT_TEST_PACKAGE)
.when(mMockFlags)
.getWebContextClientAppAllowList();
@@ -2543,7 +2531,6 @@ public final class MeasurementServiceImplTest {
return new RegistrationRequest.Builder()
.setPackageName(PACKAGE_NAME)
.setRegistrationUri(Uri.parse("https://registration-uri.com"))
- .setTopOriginUri(Uri.parse("android-app://com.example"))
.setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
.build();
}
@@ -2552,7 +2539,6 @@ public final class MeasurementServiceImplTest {
return new RegistrationRequest.Builder()
.setPackageName(PACKAGE_NAME)
.setRegistrationUri(Uri.parse("https://registration-uri.com"))
- .setTopOriginUri(Uri.parse("android-app://com.example"))
.setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
.build();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
index 6bc9730a0..60f80cd8e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
@@ -29,9 +29,9 @@ import android.net.Uri;
import androidx.annotation.Nullable;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionSource;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
import com.android.adservices.service.measurement.noising.ImpressionNoiseParams;
import com.android.adservices.service.measurement.noising.ImpressionNoiseUtil;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
@@ -51,8 +51,8 @@ import java.util.stream.LongStream;
public class SourceTest {
private static final double ZERO_DELTA = 0D;
- private static final Long DEBUG_KEY_1 = 81786463L;
- private static final Long DEBUG_KEY_2 = 23487834L;
+ private static final UnsignedLong DEBUG_KEY_1 = new UnsignedLong(81786463L);
+ private static final UnsignedLong DEBUG_KEY_2 = new UnsignedLong(23487834L);
@Test
public void testDefaults() {
@@ -77,10 +77,10 @@ public class SourceTest {
aggregateSource.put(jsonObject1);
aggregateSource.put(jsonObject2);
- JSONObject aggregateFilterData = new JSONObject();
- aggregateFilterData.put(
+ JSONObject filterData = new JSONObject();
+ filterData.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
- aggregateFilterData.put("product", Arrays.asList("1234", "2345"));
+ filterData.put("product", Arrays.asList("1234", "2345"));
assertEquals(
new Source.Builder()
.setEnrollmentId("enrollment-id")
@@ -89,15 +89,18 @@ public class SourceTest {
.setPublisher(Uri.parse("https://example.com/aS"))
.setPublisherType(EventSurfaceType.WEB)
.setId("1")
- .setEventId(2L)
+ .setEventId(new UnsignedLong(2L))
.setPriority(3L)
.setEventTime(5L)
.setExpiryTime(5L)
- .setDedupKeys(LongStream.range(0, 2).boxed().collect(Collectors.toList()))
+ .setDedupKeys(LongStream.range(0, 2)
+ .boxed()
+ .map(UnsignedLong::new)
+ .collect(Collectors.toList()))
.setStatus(Source.Status.ACTIVE)
.setSourceType(Source.SourceType.EVENT)
.setRegistrant(Uri.parse("android-app://com.example.abc"))
- .setAggregateFilterData(aggregateFilterData.toString())
+ .setFilterData(filterData.toString())
.setAggregateSource(aggregateSource.toString())
.setAggregateContributions(50001)
.setDebugKey(DEBUG_KEY_1)
@@ -111,15 +114,18 @@ public class SourceTest {
.setPublisher(Uri.parse("https://example.com/aS"))
.setPublisherType(EventSurfaceType.WEB)
.setId("1")
- .setEventId(2L)
+ .setEventId(new UnsignedLong(2L))
.setPriority(3L)
.setEventTime(5L)
.setExpiryTime(5L)
- .setDedupKeys(LongStream.range(0, 2).boxed().collect(Collectors.toList()))
+ .setDedupKeys(LongStream.range(0, 2)
+ .boxed()
+ .map(UnsignedLong::new)
+ .collect(Collectors.toList()))
.setStatus(Source.Status.ACTIVE)
.setSourceType(Source.SourceType.EVENT)
.setRegistrant(Uri.parse("android-app://com.example.abc"))
- .setAggregateFilterData(aggregateFilterData.toString())
+ .setFilterData(filterData.toString())
.setAggregateSource(aggregateSource.toString())
.setAggregateContributions(50001)
.setDebugKey(DEBUG_KEY_1)
@@ -134,8 +140,8 @@ public class SourceTest {
SourceFixture.getValidSourceBuilder().setId("1").build(),
SourceFixture.getValidSourceBuilder().setId("2").build());
assertNotEquals(
- SourceFixture.getValidSourceBuilder().setEventId(1).build(),
- SourceFixture.getValidSourceBuilder().setEventId(2).build());
+ SourceFixture.getValidSourceBuilder().setEventId(new UnsignedLong(1L)).build(),
+ SourceFixture.getValidSourceBuilder().setEventId(new UnsignedLong(2L)).build());
assertNotEquals(
SourceFixture.getValidSourceBuilder()
.setAppDestination(Uri.parse("android-app://1.com"))
@@ -188,11 +194,11 @@ public class SourceTest {
.setStatus(Source.Status.IGNORED).build());
assertNotEquals(
SourceFixture.getValidSourceBuilder()
- .setDedupKeys(LongStream.range(0, 2).boxed()
+ .setDedupKeys(LongStream.range(0, 2).boxed().map(UnsignedLong::new)
.collect(Collectors.toList()))
.build(),
SourceFixture.getValidSourceBuilder()
- .setDedupKeys(LongStream.range(1, 3).boxed()
+ .setDedupKeys(LongStream.range(1, 3).boxed().map(UnsignedLong::new)
.collect(Collectors.toList()))
.build());
assertNotEquals(
@@ -255,7 +261,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
assertInvalidSourceArguments(
SourceFixture.ValidSourceParams.SOURCE_EVENT_ID,
@@ -273,7 +279,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -294,7 +300,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
assertInvalidSourceArguments(
SourceFixture.ValidSourceParams.SOURCE_EVENT_ID,
@@ -312,7 +318,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
assertInvalidSourceArguments(
SourceFixture.ValidSourceParams.SOURCE_EVENT_ID,
@@ -330,7 +336,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -351,7 +357,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -372,7 +378,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
assertInvalidSourceArguments(
SourceFixture.ValidSourceParams.SOURCE_EVENT_ID,
@@ -390,7 +396,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -411,7 +417,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.DEBUG_KEY,
SourceFixture.ValidSourceParams.ATTRIBUTION_MODE,
SourceFixture.ValidSourceParams.buildAggregateSource(),
- SourceFixture.ValidSourceParams.buildAggregateFilterData());
+ SourceFixture.ValidSourceParams.buildFilterData());
}
@Test
@@ -591,8 +597,8 @@ public class SourceTest {
final AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(
- new AggregateFilterData.Builder()
+ .setFilterData(
+ new FilterData.Builder()
.setAttributionFilterMap(filterMap)
.build())
.build();
@@ -604,14 +610,14 @@ public class SourceTest {
assertNotNull(source.getAggregatableAttributionSource());
assertNotNull(source.getAggregatableAttributionSource().getAggregatableSource());
- assertNotNull(source.getAggregatableAttributionSource().getAggregateFilterData());
+ assertNotNull(source.getAggregatableAttributionSource().getFilterData());
assertEquals(
aggregatableSource,
source.getAggregatableAttributionSource().getAggregatableSource());
assertEquals(
filterMap,
source.getAggregatableAttributionSource()
- .getAggregateFilterData()
+ .getFilterData()
.getAttributionFilterMap());
}
@@ -627,7 +633,7 @@ public class SourceTest {
.setSourceType(Source.SourceType.NAVIGATION)
.build();
assertEquals(
- PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY,
+ PrivacyParams.getNavigationTriggerDataCardinality(),
navigationSource.getTriggerDataCardinality());
}
@@ -792,7 +798,7 @@ public class SourceTest {
.setWebDestination(null)
.setExpiryTime(expiry)
.build()),
- PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY);
+ PrivacyParams.getNavigationTriggerDataCardinality());
// Single (Web) destination, EVENT type
verifyAlgorithmicFakeReportGeneration(
@@ -814,7 +820,7 @@ public class SourceTest {
.setAppDestination(null)
.setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
.build()),
- PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY);
+ PrivacyParams.getNavigationTriggerDataCardinality());
// Both destinations set, EVENT type
verifyAlgorithmicFakeReportGeneration(
@@ -838,7 +844,7 @@ public class SourceTest {
SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATION)
.setWebDestination(SourceFixture.ValidSourceParams.WEB_DESTINATION)
.build()),
- PrivacyParams.NAVIGATION_TRIGGER_DATA_CARDINALITY);
+ PrivacyParams.getNavigationTriggerDataCardinality());
// App destination with cooldown window
verifyAlgorithmicFakeReportGeneration(
@@ -1258,16 +1264,52 @@ public class SourceTest {
}
@Test
+ public void testParseFilterData_nonEmpty() throws JSONException {
+ JSONObject filterDataJson = new JSONObject();
+ filterDataJson.put("conversion", new JSONArray(Collections.singletonList("electronics")));
+ filterDataJson.put("product", new JSONArray(Arrays.asList("1234", "2345")));
+ Source source = SourceFixture.getValidSourceBuilder()
+ .setSourceType(Source.SourceType.NAVIGATION)
+ .setFilterData(filterDataJson.toString())
+ .build();
+ FilterData filterData = source.parseFilterData();
+ assertEquals(filterData.getAttributionFilterMap().size(), 3);
+ assertEquals(Collections.singletonList("electronics"),
+ filterData.getAttributionFilterMap().get("conversion"));
+ assertEquals(Arrays.asList("1234", "2345"),
+ filterData.getAttributionFilterMap().get("product"));
+ assertEquals(Collections.singletonList("navigation"),
+ filterData.getAttributionFilterMap().get("source_type"));
+ }
+
+ @Test
+ public void testParseFilterData_nullFilterData() throws JSONException {
+ Source source = SourceFixture.getValidSourceBuilder()
+ .setSourceType(Source.SourceType.EVENT)
+ .build();
+ FilterData filterData = source.parseFilterData();
+ assertEquals(filterData.getAttributionFilterMap().size(), 1);
+ assertEquals(Collections.singletonList("event"),
+ filterData.getAttributionFilterMap().get("source_type"));
+ }
+
+ @Test
+ public void testParseFilterData_emptyFilterData() throws JSONException {
+ Source source = SourceFixture.getValidSourceBuilder()
+ .setSourceType(Source.SourceType.EVENT)
+ .setFilterData("")
+ .build();
+ FilterData filterData = source.parseFilterData();
+ assertEquals(filterData.getAttributionFilterMap().size(), 1);
+ assertEquals(Collections.singletonList("event"),
+ filterData.getAttributionFilterMap().get("source_type"));
+ }
+
+ @Test
public void testParseAggregateSource() throws JSONException {
- JSONArray aggregatableSource = new JSONArray();
- JSONObject jsonObject1 = new JSONObject();
- jsonObject1.put("id", "campaignCounts");
- jsonObject1.put("key_piece", "0x159");
- JSONObject jsonObject2 = new JSONObject();
- jsonObject2.put("id", "geoValue");
- jsonObject2.put("key_piece", "0x5");
- aggregatableSource.put(jsonObject1);
- aggregatableSource.put(jsonObject2);
+ JSONObject aggregatableSource = new JSONObject();
+ aggregatableSource.put("campaignCounts", "0x159");
+ aggregatableSource.put("geoValue", "0x5");
JSONObject filterData = new JSONObject();
filterData.put("conversion_subdomain",
@@ -1275,8 +1317,9 @@ public class SourceTest {
filterData.put("product", new JSONArray(Arrays.asList("1234", "2345")));
Source source = SourceFixture.getValidSourceBuilder()
+ .setSourceType(Source.SourceType.NAVIGATION)
.setAggregateSource(aggregatableSource.toString())
- .setAggregateFilterData(filterData.toString()).build();
+ .setFilterData(filterData.toString()).build();
Optional<AggregatableAttributionSource> aggregatableAttributionSource =
source.parseAggregateSource();
assertTrue(aggregatableAttributionSource.isPresent());
@@ -1285,7 +1328,7 @@ public class SourceTest {
assertEquals(
aggregateSource.getAggregatableSource().get("campaignCounts").longValue(), 345L);
assertEquals(aggregateSource.getAggregatableSource().get("geoValue").longValue(), 5L);
- assertEquals(aggregateSource.getAggregateFilterData().getAttributionFilterMap().size(), 2);
+ assertEquals(aggregateSource.getFilterData().getAttributionFilterMap().size(), 3);
}
private void verifyAlgorithmicFakeReportGeneration(Source source, int expectedCardinality) {
@@ -1304,7 +1347,8 @@ public class SourceTest {
assertTrue(
source.getExpiryTime() + TimeUnit.HOURS.toMillis(1)
>= report.getReportingTime());
- assertTrue(report.getTriggerData() < expectedCardinality);
+ Long triggerData = report.getTriggerData().getValue();
+ assertTrue(0 <= triggerData && triggerData < expectedCardinality);
}
} else if (source.getAttributionMode() == Source.AttributionMode.NEVER) {
neverCount++;
@@ -1331,7 +1375,7 @@ public class SourceTest {
.map(
reportState ->
new Source.FakeReport(
- reportState[0],
+ new UnsignedLong(Long.valueOf(reportState[0])),
source.getReportingTimeForNoising(reportState[1]),
reportState[2] == 0
? source.getAppDestination()
@@ -1340,7 +1384,7 @@ public class SourceTest {
}
private void assertInvalidSourceArguments(
- Long sourceEventId,
+ UnsignedLong sourceEventId,
Uri publisher,
Uri appDestination,
Uri webDestination,
@@ -1352,10 +1396,10 @@ public class SourceTest {
Source.SourceType sourceType,
Long installAttributionWindow,
Long installCooldownWindow,
- @Nullable Long debugKey,
+ @Nullable UnsignedLong debugKey,
@Source.AttributionMode int attributionMode,
@Nullable String aggregateSource,
- @Nullable String aggregateFilterData) {
+ @Nullable String filterData) {
assertThrows(
IllegalArgumentException.class,
() ->
@@ -1374,7 +1418,7 @@ public class SourceTest {
.setInstallCooldownWindow(installCooldownWindow)
.setAttributionMode(attributionMode)
.setAggregateSource(aggregateSource)
- .setAggregateFilterData(aggregateFilterData)
+ .setFilterData(filterData)
.setDebugKey(debugKey)
.build());
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java
index 07974cfb4..288989b7a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TestObjectProvider.java
@@ -24,12 +24,15 @@ import static org.mockito.Mockito.spy;
import android.annotation.IntDef;
import android.test.mock.MockContentResolver;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
import com.android.adservices.service.Flags;
import com.android.adservices.service.measurement.attribution.AttributionJobHandlerWrapper;
import com.android.adservices.service.measurement.inputverification.ClickVerifier;
-import com.android.adservices.service.measurement.registration.SourceFetcher;
-import com.android.adservices.service.measurement.registration.TriggerFetcher;
+import com.android.adservices.service.measurement.registration.AsyncSourceFetcher;
+import com.android.adservices.service.measurement.registration.AsyncTriggerFetcher;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.mockito.stubbing.Answer;
@@ -57,35 +60,49 @@ class TestObjectProvider {
}
static MeasurementImpl getMeasurementImpl(
- @Type int type,
DatastoreManager datastoreManager,
- SourceFetcher sourceFetcher,
- TriggerFetcher triggerFetcher,
ClickVerifier clickVerifier,
- Flags flags) {
+ Flags flags,
+ MeasurementDataDeleter measurementDataDeleter,
+ EnrollmentDao enrollmentDao) {
+ return spy(
+ new MeasurementImpl(
+ null,
+ datastoreManager,
+ clickVerifier,
+ measurementDataDeleter,
+ enrollmentDao));
+ }
+
+ static AsyncRegistrationQueueRunner getAsyncRegistrationQueueRunner(
+ @Type int type,
+ DatastoreManager datastoreManager,
+ AsyncSourceFetcher asyncSourceFetcher,
+ AsyncTriggerFetcher asyncTriggerFetcher,
+ EnrollmentDao enrollmentDao) {
if (type == Type.DENOISED) {
- MeasurementImpl measurementImpl =
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
spy(
- new MeasurementImpl(
- null,
+ new AsyncRegistrationQueueRunner(
new MockContentResolver(),
- datastoreManager,
- sourceFetcher,
- triggerFetcher,
- clickVerifier));
+ asyncSourceFetcher,
+ asyncTriggerFetcher,
+ enrollmentDao,
+ datastoreManager));
// Disable Impression Noise
- doReturn(Collections.emptyList()).when(measurementImpl).generateFakeEventReports(any());
- return measurementImpl;
+ doReturn(Collections.emptyList())
+ .when(asyncRegistrationQueueRunner)
+ .generateFakeEventReports(any());
+ return asyncRegistrationQueueRunner;
} else if (type == Type.NOISY) {
- MeasurementImpl measurementImpl =
+ AsyncRegistrationQueueRunner asyncRegistrationQueueRunner =
spy(
- new MeasurementImpl(
- null,
+ new AsyncRegistrationQueueRunner(
new MockContentResolver(),
- datastoreManager,
- sourceFetcher,
- triggerFetcher,
- clickVerifier));
+ asyncSourceFetcher,
+ asyncTriggerFetcher,
+ enrollmentDao,
+ datastoreManager));
// Create impression noise with 100% probability
Answer<?> answerSourceEventReports =
invocation -> {
@@ -93,9 +110,9 @@ class TestObjectProvider {
source.setAttributionMode(Source.AttributionMode.FALSELY);
return Collections.singletonList(
new EventReport.Builder()
- .setSourceId(source.getEventId())
+ .setSourceEventId(source.getEventId())
.setReportTime(source.getExpiryTime() + ONE_HOUR_IN_MILLIS)
- .setTriggerData(0)
+ .setTriggerData(new UnsignedLong(0L))
.setAttributionDestination(source.getAppDestination())
.setEnrollmentId(source.getEnrollmentId())
.setTriggerTime(0)
@@ -106,17 +123,16 @@ class TestObjectProvider {
.build());
};
doAnswer(answerSourceEventReports)
- .when(measurementImpl)
+ .when(asyncRegistrationQueueRunner)
.generateFakeEventReports(any());
- return measurementImpl;
+ return asyncRegistrationQueueRunner;
}
- return new MeasurementImpl(
- null,
+ return new AsyncRegistrationQueueRunner(
new MockContentResolver(),
- datastoreManager,
- sourceFetcher,
- triggerFetcher,
- clickVerifier);
+ asyncSourceFetcher,
+ asyncTriggerFetcher,
+ enrollmentDao,
+ datastoreManager);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java
index c563d9a42..d3250af48 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/TriggerTest.java
@@ -19,14 +19,15 @@ package com.android.adservices.service.measurement;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import android.net.Uri;
import com.android.adservices.service.measurement.aggregation.AggregatableAttributionTrigger;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
import com.android.adservices.service.measurement.aggregation.AggregateTriggerData;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
@@ -65,7 +66,17 @@ public class TriggerTest {
+ "}"
+ "]\n";
- private static final Long DEBUG_KEY = 2367372L;
+ private static final UnsignedLong DEBUG_KEY = new UnsignedLong(2367372L);
+ private static final Uri APP_DESTINATION = Uri.parse("android-app://com.android.app");
+ private static final Uri APP_DESTINATION_WITH_PATH =
+ Uri.parse("android-app://com.android.app/with/path");
+ private static final Uri WEB_DESTINATION = Uri.parse("https://example.com");
+ private static final Uri WEB_DESTINATION_WITH_PATH = Uri.parse("https://example.com/with/path");
+ private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN =
+ Uri.parse("https://subdomain.example.com");
+ private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN_PATH_QUERY_FRAGMENT =
+ Uri.parse("https://subdomain.example.com/with/path?query=0#fragment");
+ private static final Uri WEB_DESTINATION_INVALID = Uri.parse("https://example.notatld");
@Test
public void testEqualsPass() throws JSONException {
@@ -96,6 +107,7 @@ public class TriggerTest {
.setAggregateTriggerData(aggregateTriggerDatas.toString())
.setAggregateValues(values.toString())
.setFilters(TOP_LEVEL_FILTERS_JSON_STRING)
+ .setNotFilters(TOP_LEVEL_FILTERS_JSON_STRING)
.setDebugKey(DEBUG_KEY)
.setAggregatableAttributionTrigger(
TriggerFixture.getValidTrigger()
@@ -113,6 +125,7 @@ public class TriggerTest {
.setAggregateTriggerData(aggregateTriggerDatas.toString())
.setAggregateValues(values.toString())
.setFilters(TOP_LEVEL_FILTERS_JSON_STRING)
+ .setNotFilters(TOP_LEVEL_FILTERS_JSON_STRING)
.setDebugKey(DEBUG_KEY)
.setAggregatableAttributionTrigger(
TriggerFixture.getValidTrigger()
@@ -187,6 +200,11 @@ public class TriggerTest {
.setFilters(TOP_LEVEL_FILTERS_JSON_STRING).build(),
TriggerFixture.getValidTriggerBuilder()
.setFilters(TOP_LEVEL_FILTERS_JSON_STRING_X).build());
+ assertNotEquals(
+ TriggerFixture.getValidTriggerBuilder()
+ .setNotFilters(TOP_LEVEL_FILTERS_JSON_STRING).build(),
+ TriggerFixture.getValidTriggerBuilder()
+ .setNotFilters(TOP_LEVEL_FILTERS_JSON_STRING_X).build());
}
@Test
@@ -222,6 +240,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
assertInvalidTriggerArguments(
Uri.parse("com.destination"),
@@ -232,6 +251,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
}
@@ -246,6 +266,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
}
@@ -260,6 +281,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
assertInvalidTriggerArguments(
TriggerFixture.ValidTriggerParams.ATTRIBUTION_DESTINATION,
@@ -270,6 +292,7 @@ public class TriggerTest {
TriggerFixture.ValidTriggerParams.AGGREGATE_TRIGGER_DATA,
TriggerFixture.ValidTriggerParams.AGGREGATE_VALUES,
TriggerFixture.ValidTriggerParams.TOP_LEVEL_FILTERS_JSON_STRING,
+ TriggerFixture.ValidTriggerParams.TOP_LEVEL_NOT_FILTERS_JSON_STRING,
TriggerFixture.ValidTriggerParams.DEBUG_KEY);
}
@@ -343,6 +366,73 @@ public class TriggerTest {
}
@Test
+ public void testGetAttributionDestinationBaseUri_appDestination() throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(APP_DESTINATION)
+ .setDestinationType(EventSurfaceType.APP)
+ .build();
+ assertEquals(APP_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_trimsAppDestination() throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(APP_DESTINATION_WITH_PATH)
+ .setDestinationType(EventSurfaceType.APP)
+ .build();
+ assertEquals(APP_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_webDestination() throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertEquals(WEB_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_trimsWebDestinationWithSubdomain()
+ throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION_WITH_SUBDOMAIN)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertEquals(WEB_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_trimsWebDestinationWithPath()
+ throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION_WITH_PATH)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertEquals(WEB_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_trimsWebDestinationWithSubdomainPathQueryFrag()
+ throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION_WITH_SUBDOMAIN_PATH_QUERY_FRAGMENT)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertEquals(WEB_DESTINATION, trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
+ public void testGetAttributionDestinationBaseUri_invalidWebDestination()
+ throws JSONException {
+ Trigger trigger = TriggerFixture.getValidTriggerBuilder()
+ .setAttributionDestination(WEB_DESTINATION_INVALID)
+ .setDestinationType(EventSurfaceType.WEB)
+ .build();
+ assertNull(trigger.getAttributionDestinationBaseUri());
+ }
+
+ @Test
public void parseEventTriggers() throws JSONException {
// setup
JSONObject filters1 =
@@ -392,25 +482,25 @@ public class TriggerTest {
EventTrigger eventTrigger1 =
new EventTrigger.Builder()
.setTriggerPriority(2L)
- .setTriggerData(2L)
- .setDedupKey(2L)
+ .setTriggerData(new UnsignedLong(2L))
+ .setDedupKey(new UnsignedLong(2L))
.setFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(filters1)
+ new FilterData.Builder()
+ .buildFilterData(filters1)
.build())
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(notFilters1)
+ new FilterData.Builder()
+ .buildFilterData(notFilters1)
.build())
.build();
EventTrigger eventTrigger2 =
new EventTrigger.Builder()
.setTriggerPriority(3L)
- .setTriggerData(3L)
- .setDedupKey(3L)
+ .setTriggerData(new UnsignedLong(3L))
+ .setDedupKey(new UnsignedLong(3L))
.setNotFilter(
- new AggregateFilterData.Builder()
- .buildAggregateFilterData(notFilters2)
+ new FilterData.Builder()
+ .buildFilterData(notFilters2)
.build())
.build();
@@ -430,7 +520,8 @@ public class TriggerTest {
String aggregateTriggerData,
String aggregateValues,
String filters,
- Long debugKey) {
+ String notFilters,
+ UnsignedLong debugKey) {
assertThrows(
IllegalArgumentException.class,
() ->
@@ -443,6 +534,7 @@ public class TriggerTest {
.setAggregateTriggerData(aggregateTriggerData)
.setAggregateValues(aggregateValues)
.setFilters(filters)
+ .setNotFilters(notFilters)
.setDebugKey(debugKey)
.build());
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java
index 553cb029b..02a7724e3 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/ManifestBasedAdtechAccessResolverTest.java
@@ -68,13 +68,14 @@ public class ManifestBasedAdtechAccessResolverTest {
mMockEnrollmentDao = mock(EnrollmentDao.class);
doReturn(EnrollmentFixture.getValidEnrollment())
.when(mMockEnrollmentDao)
- .getEnrollmentDataFromMeasurementUrl(eq(ENROLLED_AD_TECH_URL.toString()));
+ .getEnrollmentDataFromMeasurementUrl(eq(ENROLLED_AD_TECH_URL));
mMockitoSession =
ExtendedMockito.mockitoSession()
.mockStatic(AppManifestConfigHelper.class)
.strictness(Strictness.LENIENT)
.initMocks(this)
.startMocking();
+ when(mFlags.isEnrollmentBlocklisted(any())).thenReturn(false);
}
@After
@@ -193,6 +194,21 @@ public class ManifestBasedAdtechAccessResolverTest {
}
@Test
+ public void isNotAllowed_enrollmentInBlocklist() {
+ mClassUnderTest =
+ new ManifestBasedAdtechAccessResolver(
+ mMockEnrollmentDao, mFlags, PACKAGE, ENROLLED_AD_TECH_URL);
+ when(mFlags.isDisableMeasurementEnrollmentCheck()).thenReturn(false);
+ when(AppManifestConfigHelper.isAllowedAttributionAccess(any(), any(), any()))
+ .thenReturn(true);
+
+ String enrollmentId = EnrollmentFixture.getValidEnrollment().getEnrollmentId();
+ when(mFlags.isEnrollmentBlocklisted(enrollmentId)).thenReturn(true);
+
+ assertFalse(mClassUnderTest.isAllowed(CONTEXT));
+ }
+
+ @Test
public void getErrorMessage() {
mClassUnderTest =
new ManifestBasedAdtechAccessResolver(
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java
index c0a7cb654..34af05bb3 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/access/UserConsentAccessResolverTest.java
@@ -53,7 +53,7 @@ public class UserConsentAccessResolverTest {
@Test
public void isAllowed_consented_success() {
// Setup
- doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.GIVEN).when(mConsentManager).getConsent();
// Execution
assertTrue(mClassUnderTest.isAllowed(mContext));
@@ -62,7 +62,7 @@ public class UserConsentAccessResolverTest {
@Test
public void isAllowed_notConsented_success() {
// Setup
- doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent(mPackageManager);
+ doReturn(AdServicesApiConsent.REVOKED).when(mConsentManager).getConsent();
// Execution
assertFalse(mClassUnderTest.isAllowed(mContext));
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/AggregateReportingJob.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/AggregateReportingJob.java
new file mode 100644
index 000000000..19667858d
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/AggregateReportingJob.java
@@ -0,0 +1,44 @@
+/*
+ * 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.measurement.actions;
+
+import java.util.Objects;
+
+public final class AggregateReportingJob implements Action {
+ public final long mTimestamp;
+
+ public AggregateReportingJob(long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ public long getComparable() {
+ return mTimestamp;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AggregateReportingJob)) return false;
+ AggregateReportingJob that = (AggregateReportingJob) o;
+ return mTimestamp == that.mTimestamp;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTimestamp);
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/ReportingJob.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/EventReportingJob.java
index 23f1e6b1c..12b1551ea 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/ReportingJob.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/EventReportingJob.java
@@ -18,13 +18,14 @@ package com.android.adservices.service.measurement.actions;
import java.util.Objects;
-public final class ReportingJob implements Action {
+public final class EventReportingJob implements Action {
public final long mTimestamp;
- public ReportingJob(long timestamp) {
+ public EventReportingJob(long timestamp) {
mTimestamp = timestamp;
}
+ @Override
public long getComparable() {
return mTimestamp;
}
@@ -32,8 +33,8 @@ public final class ReportingJob implements Action {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof ReportingJob)) return false;
- ReportingJob that = (ReportingJob) o;
+ if (!(o instanceof EventReportingJob)) return false;
+ EventReportingJob that = (EventReportingJob) o;
return mTimestamp == that.mTimestamp;
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java
index 180d46e2a..7de221322 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/InstallApp.java
@@ -33,6 +33,7 @@ public final class InstallApp implements Action {
mTimestamp = obj.getLong(TestFormatJsonMapping.INSTALLS_TIMESTAMP_KEY);
}
+ @Override
public long getComparable() {
return mTimestamp;
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java
index 4c17b47f4..c81a9571d 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterSource.java
@@ -36,21 +36,22 @@ public final class RegisterSource implements Action {
public final RegistrationRequest mRegistrationRequest;
public final Map<String, List<Map<String, List<String>>>> mUriToResponseHeadersMap;
public final long mTimestamp;
+ // Used in interop tests
+ public final String mPublisher;
public RegisterSource(JSONObject obj) throws JSONException {
JSONObject regParamsJson = obj.getJSONObject(
TestFormatJsonMapping.REGISTRATION_REQUEST_KEY);
AttributionSource attributionSource = getAttributionSource(
- regParamsJson.optString(TestFormatJsonMapping.ATTRIBUTION_SOURCE_KEY));
+ regParamsJson.optString(TestFormatJsonMapping.ATTRIBUTION_SOURCE_KEY,
+ TestFormatJsonMapping.ATTRIBUTION_SOURCE_DEFAULT));
+
+ mPublisher = regParamsJson.optString(TestFormatJsonMapping.SOURCE_TOP_ORIGIN_URI_KEY);
mRegistrationRequest =
new RegistrationRequest.Builder()
.setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setTopOriginUri(
- Uri.parse(
- regParamsJson.getString(
- TestFormatJsonMapping.SOURCE_TOP_ORIGIN_URI_KEY)))
.setRegistrationUri(
Uri.parse(
regParamsJson.getString(
@@ -61,6 +62,9 @@ public final class RegisterSource implements Action {
.equals(TestFormatJsonMapping.SOURCE_VIEW_TYPE)
? null
: getInputEvent())
+ .setAdIdPermissionGranted(
+ regParamsJson.optBoolean(
+ TestFormatJsonMapping.IS_ADID_PERMISSION_GRANTED_KEY, true))
.setPackageName(attributionSource.getPackageName())
.build();
mUriToResponseHeadersMap = getUriToResponseHeadersMap(obj);
@@ -71,4 +75,8 @@ public final class RegisterSource implements Action {
public long getComparable() {
return mTimestamp;
}
+
+ public String getPublisher() {
+ return mPublisher;
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java
index dd9f562de..cdba2ac30 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterTrigger.java
@@ -35,25 +35,29 @@ public final class RegisterTrigger implements Action {
public final RegistrationRequest mRegistrationRequest;
public final Map<String, List<Map<String, List<String>>>> mUriToResponseHeadersMap;
public final long mTimestamp;
+ // Used in interop tests
+ public final String mDestination;
public RegisterTrigger(JSONObject obj) throws JSONException {
JSONObject regParamsJson = obj.getJSONObject(
TestFormatJsonMapping.REGISTRATION_REQUEST_KEY);
AttributionSource attributionSource = getAttributionSource(
- regParamsJson.optString(TestFormatJsonMapping.ATTRIBUTION_SOURCE_KEY));
+ regParamsJson.optString(TestFormatJsonMapping.ATTRIBUTION_SOURCE_KEY,
+ TestFormatJsonMapping.ATTRIBUTION_SOURCE_DEFAULT));
+
+ mDestination = regParamsJson.optString(TestFormatJsonMapping.TRIGGER_TOP_ORIGIN_URI_KEY);
mRegistrationRequest =
new RegistrationRequest.Builder()
.setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
- .setTopOriginUri(
- Uri.parse(
- regParamsJson.getString(
- TestFormatJsonMapping.TRIGGER_TOP_ORIGIN_URI_KEY)))
.setRegistrationUri(
Uri.parse(
regParamsJson.getString(
TestFormatJsonMapping.REGISTRATION_URI_KEY)))
+ .setAdIdPermissionGranted(
+ regParamsJson.optBoolean(
+ TestFormatJsonMapping.IS_ADID_PERMISSION_GRANTED_KEY, true))
.setPackageName(attributionSource.getPackageName())
.build();
@@ -61,7 +65,12 @@ public final class RegisterTrigger implements Action {
mTimestamp = obj.getLong(TestFormatJsonMapping.TIMESTAMP_KEY);
}
+ @Override
public long getComparable() {
return mTimestamp;
}
+
+ public String getDestination() {
+ return mDestination;
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java
index f65778126..ac6c7660a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebSource.java
@@ -80,6 +80,9 @@ public final class RegisterWebSource implements Action {
mRegistrationRequest =
new WebSourceRegistrationRequestInternal.Builder(
registrationRequest, attributionSource.getPackageName(), 2000L)
+ .setAdIdPermissionGranted(
+ regParamsJson.optBoolean(
+ TestFormatJsonMapping.IS_ADID_PERMISSION_GRANTED_KEY, true))
.build();
mUriToResponseHeadersMap = getUriToResponseHeadersMap(obj);
@@ -102,8 +105,7 @@ public final class RegisterWebSource implements Action {
Uri.parse(
sourceParams.getString(
TestFormatJsonMapping.REGISTRATION_URI_KEY)))
- .setDebugKeyAllowed(
- sourceParams.optBoolean(TestFormatJsonMapping.DEBUG_KEY, false))
+ .setDebugKeyAllowed(true)
.build());
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java
index 2b86d3055..a52397486 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/RegisterWebTrigger.java
@@ -61,6 +61,9 @@ public final class RegisterWebTrigger implements Action {
mRegistrationRequest =
new WebTriggerRegistrationRequestInternal.Builder(
registrationRequest, attributionSource.getPackageName())
+ .setAdIdPermissionGranted(
+ regParamsJson.optBoolean(
+ TestFormatJsonMapping.IS_ADID_PERMISSION_GRANTED_KEY, true))
.build();
mUriToResponseHeadersMap = getUriToResponseHeadersMap(obj);
@@ -83,9 +86,7 @@ public final class RegisterWebTrigger implements Action {
Uri.parse(
triggerParams.getString(
TestFormatJsonMapping.REGISTRATION_URI_KEY)))
- .setDebugKeyAllowed(
- triggerParams.optBoolean(
- TestFormatJsonMapping.DEBUG_KEY, false))
+ .setDebugKeyAllowed(true)
.build());
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java
index e73ad82d7..ed11d75f8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/actions/UninstallApp.java
@@ -33,6 +33,7 @@ public final class UninstallApp implements Action {
mTimestamp = obj.getLong(TestFormatJsonMapping.INSTALLS_TIMESTAMP_KEY);
}
+ @Override
public long getComparable() {
return mTimestamp;
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java
index 3599e1d05..e45de1339 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateEncryptionKeyManagerIntegrationTest.java
@@ -20,10 +20,11 @@ import static org.mockito.Mockito.when;
import android.net.Uri;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.measurement.AbstractDbIntegrationTest;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
import com.android.adservices.data.measurement.DbState;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
import org.json.JSONException;
import org.junit.Assert;
@@ -74,7 +75,8 @@ public class AggregateEncryptionKeyManagerIntegrationTest extends AbstractDbInte
@Override
public void runActionToTest() {
- DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
AggregateEncryptionKeyManager aggregateEncryptionKeyManager =
new AggregateEncryptionKeyManager(datastoreManager, mFetcher, mClock,
MEASUREMENT_AGGREGATE_ENCRYPTION_KEY_COORDINATOR_URL);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java
index baeab5ff7..f55d7d28e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregatePayloadGeneratorTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement.aggregation;
+import com.android.adservices.service.measurement.FilterData;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -48,12 +50,12 @@ public final class AggregatePayloadGeneratorTest {
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
@@ -67,9 +69,9 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(1024L))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build())
- .setNotFilter(new AggregateFilterData.Builder()
+ .setNotFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataNotFilter1).build()).build());
// Apply this key_piece to "geoValue".
triggerDataList.add(
@@ -107,18 +109,18 @@ public final class AggregatePayloadGeneratorTest {
Map<String, BigInteger> aggregatableSource = new HashMap<>();
aggregatableSource.put("campaignCounts", BigInteger.valueOf(345L));
aggregatableSource.put("geoValue", BigInteger.valueOf(5L));
- aggregatableSource.put("thirdSource", BigInteger.valueOf(100L));
+ aggregatableSource.put("thirdSource", BigInteger.valueOf(101L));
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put("conversion_subdomain",
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
// Apply this key_piece to "campaignCounts".
@@ -131,9 +133,9 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(1024L))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build())
- .setNotFilter(new AggregateFilterData.Builder()
+ .setNotFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataNotFilter1).build())
.build());
// Apply this key_piece to "geoValue".
@@ -158,13 +160,16 @@ public final class AggregatePayloadGeneratorTest {
assertTrue(aggregateHistogramContributions.isPresent());
List<AggregateHistogramContribution> contributions = aggregateHistogramContributions.get();
- assertEquals(contributions.size(), 2);
+ assertEquals(contributions.size(), 3);
assertTrue(contributions.contains(
new AggregateHistogramContribution.Builder()
.setKey(BigInteger.valueOf(1369L)).setValue(32768).build()));
assertTrue(contributions.contains(
new AggregateHistogramContribution.Builder()
.setKey(BigInteger.valueOf(2693L)).setValue(1664).build()));
+ assertTrue(contributions.contains(
+ new AggregateHistogramContribution.Builder()
+ .setKey(BigInteger.valueOf(101L)).setValue(100).build()));
}
@Test
@@ -178,12 +183,12 @@ public final class AggregatePayloadGeneratorTest {
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
// Apply this key_piece to "campaignCounts".
@@ -196,9 +201,9 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(1024L))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build())
- .setNotFilter(new AggregateFilterData.Builder()
+ .setNotFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataNotFilter1).build())
.build());
// Apply this key_piece to "geoValue".
@@ -220,7 +225,7 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(200L))
.setSourceKeys(new HashSet<>(Arrays.asList("campaignCounts", "geoValue")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter2).build())
.build());
@@ -256,12 +261,12 @@ public final class AggregatePayloadGeneratorTest {
sourceFilterMap.put("conversion_subdomain",
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
@@ -273,7 +278,7 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(2L).shiftLeft(63))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build()).build());
Map<String, Integer> values = new HashMap<>();
@@ -305,12 +310,12 @@ public final class AggregatePayloadGeneratorTest {
sourceFilterMap.put("conversion_subdomain",
Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
- AggregateFilterData sourceFilter = new AggregateFilterData.Builder()
+ FilterData sourceFilter = new FilterData.Builder()
.setAttributionFilterMap(sourceFilterMap).build();
AggregatableAttributionSource attributionSource =
new AggregatableAttributionSource.Builder()
.setAggregatableSource(aggregatableSource)
- .setAggregateFilterData(sourceFilter).build();
+ .setFilterData(sourceFilter).build();
// Build AggregatableAttributionTrigger.
List<AggregateTriggerData> triggerDataList = new ArrayList<>();
@@ -322,7 +327,7 @@ public final class AggregatePayloadGeneratorTest {
new AggregateTriggerData.Builder()
.setKey(BigInteger.valueOf(2L).shiftLeft(63).add(BigInteger.valueOf(4L)))
.setSourceKeys(new HashSet<>(Collections.singletonList("campaignCounts")))
- .setFilter(new AggregateFilterData.Builder()
+ .setFilter(new FilterData.Builder()
.setAttributionFilterMap(triggerDataFilter1).build()).build());
Map<String, Integer> values = new HashMap<>();
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java
index 5dc25302c..4cf1de7fc 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateReportTest.java
@@ -25,16 +25,21 @@ import android.net.Uri;
import androidx.test.filters.SmallTest;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.junit.Test;
import java.util.Set;
+import java.util.UUID;
/** Unit tests for {@link AggregateReport} */
@SmallTest
public final class AggregateReportTest {
- private static final Long SOURCE_DEBUG_KEY = 237865L;
- private static final Long TRIGGER_DEBUG_KEY = 928762L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(237865L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(928762L);
+ private static final String SOURCE_ID = UUID.randomUUID().toString();
+ private static final String TRIGGER_ID = UUID.randomUUID().toString();
private AggregateReport createAttributionReport() {
return new AggregateReport.Builder()
@@ -47,9 +52,12 @@ public final class AggregateReportTest {
.setDebugCleartextPayload(" key: 1369, value: 32768; key: 3461, value: 1664;")
.setAggregateAttributionData(new AggregateAttributionData.Builder().build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
.setApiVersion("1452")
.setSourceDebugKey(SOURCE_DEBUG_KEY)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
@@ -64,8 +72,11 @@ public final class AggregateReportTest {
.setDebugCleartextPayload(" key: 1369, value: 32768; key: 3461, value: 1664;")
.setAggregateAttributionData(new AggregateAttributionData.Builder().build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
.setApiVersion("1452")
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
@@ -80,8 +91,11 @@ public final class AggregateReportTest {
.setDebugCleartextPayload(" key: 1369, value: 32768; key: 3461, value: 1664;")
.setAggregateAttributionData(new AggregateAttributionData.Builder().build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
.setApiVersion("1452")
.setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
}
@@ -99,13 +113,18 @@ public final class AggregateReportTest {
attributionReport.getDebugCleartextPayload());
assertNotNull(attributionReport.getAggregateAttributionData());
assertEquals(AggregateReport.Status.PENDING, attributionReport.getStatus());
+ assertEquals(
+ AggregateReport.DebugReportStatus.PENDING,
+ attributionReport.getDebugReportStatus());
assertEquals("1452", attributionReport.getApiVersion());
assertEquals(SOURCE_DEBUG_KEY, attributionReport.getSourceDebugKey());
assertEquals(TRIGGER_DEBUG_KEY, attributionReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, attributionReport.getSourceId());
+ assertEquals(TRIGGER_ID, attributionReport.getTriggerId());
}
@Test
- public void testCreationSingleSourceDebugKey() throws Exception {
+ public void testCreationSingleSourceDebugKey() {
AggregateReport attributionReport = createAttributionReportSingleSourceDebugKey();
assertEquals("1", attributionReport.getId());
assertEquals(Uri.parse("android-app://com.example.abc"), attributionReport.getPublisher());
@@ -119,13 +138,18 @@ public final class AggregateReportTest {
attributionReport.getDebugCleartextPayload());
assertNotNull(attributionReport.getAggregateAttributionData());
assertEquals(AggregateReport.Status.PENDING, attributionReport.getStatus());
+ assertEquals(
+ AggregateReport.DebugReportStatus.PENDING,
+ attributionReport.getDebugReportStatus());
assertEquals("1452", attributionReport.getApiVersion());
assertEquals(SOURCE_DEBUG_KEY, attributionReport.getSourceDebugKey());
assertNull(attributionReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, attributionReport.getSourceId());
+ assertEquals(TRIGGER_ID, attributionReport.getTriggerId());
}
@Test
- public void testCreationSingleTriggerDebugKey() throws Exception {
+ public void testCreationSingleTriggerDebugKey() {
AggregateReport attributionReport = createAttributionReportSingleTriggerDebugKey();
assertEquals("1", attributionReport.getId());
assertEquals(Uri.parse("android-app://com.example.abc"), attributionReport.getPublisher());
@@ -139,9 +163,14 @@ public final class AggregateReportTest {
attributionReport.getDebugCleartextPayload());
assertNotNull(attributionReport.getAggregateAttributionData());
assertEquals(AggregateReport.Status.PENDING, attributionReport.getStatus());
+ assertEquals(
+ AggregateReport.DebugReportStatus.PENDING,
+ attributionReport.getDebugReportStatus());
assertEquals("1452", attributionReport.getApiVersion());
assertNull(attributionReport.getSourceDebugKey());
assertEquals(TRIGGER_DEBUG_KEY, attributionReport.getTriggerDebugKey());
+ assertEquals(SOURCE_ID, attributionReport.getSourceId());
+ assertEquals(TRIGGER_ID, attributionReport.getTriggerId());
}
@Test
@@ -157,9 +186,13 @@ public final class AggregateReportTest {
assertNull(attributionReport.getDebugCleartextPayload());
assertNull(attributionReport.getAggregateAttributionData());
assertEquals(AggregateReport.Status.PENDING, attributionReport.getStatus());
+ assertEquals(
+ AggregateReport.DebugReportStatus.NONE, attributionReport.getDebugReportStatus());
assertNull(attributionReport.getApiVersion());
assertNull(attributionReport.getSourceDebugKey());
assertNull(attributionReport.getTriggerDebugKey());
+ assertNull(attributionReport.getSourceId());
+ assertNull(attributionReport.getTriggerId());
}
@Test
@@ -188,7 +221,10 @@ public final class AggregateReportTest {
" key: 1369, value: 32768; key: 3461, value: 1664;")
.setAggregateAttributionData(new AggregateAttributionData.Builder().build())
.setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
.setApiVersion("1452")
+ .setSourceId(SOURCE_ID)
+ .setTriggerId(TRIGGER_ID)
.build();
Set<AggregateReport> attributionReportSet1 = Set.of(attributionReport1);
Set<AggregateReport> attributionReportSet2 = Set.of(attributionReport2);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java
index 4d4c920c8..731f1554f 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/aggregation/AggregateTriggerDataTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement.aggregation;
+import com.android.adservices.service.measurement.FilterData;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -45,8 +47,8 @@ public final class AggregateTriggerDataTest {
assertEquals(attributionTriggerData.getKey().longValue(), 5L);
assertEquals(attributionTriggerData.getSourceKeys().size(), 3);
assertTrue(attributionTriggerData.getFilter().isPresent());
- AggregateFilterData filterData = attributionTriggerData.getFilter().get();
- AggregateFilterData nonFilteredData = attributionTriggerData.getNotFilter().get();
+ FilterData filterData = attributionTriggerData.getFilter().get();
+ FilterData nonFilteredData = attributionTriggerData.getNotFilter().get();
assertEquals(2, filterData.getAttributionFilterMap().get("ctid").size());
assertEquals(1, nonFilteredData.getAttributionFilterMap().get("nctid").size());
}
@@ -78,15 +80,15 @@ public final class AggregateTriggerDataTest {
Map<String, List<String>> attributionFilterMap = new HashMap<>();
attributionFilterMap.put("ctid", Arrays.asList("1"));
- AggregateFilterData filterData =
- new AggregateFilterData.Builder()
+ FilterData filterData =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionFilterMap)
.build();
Map<String, List<String>> attributionNonFilterMap = new HashMap<>();
attributionNonFilterMap.put("other", Arrays.asList("1"));
- AggregateFilterData nonFilterData =
- new AggregateFilterData.Builder()
+ FilterData nonFilterData =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionNonFilterMap)
.build();
@@ -110,15 +112,15 @@ public final class AggregateTriggerDataTest {
private AggregateTriggerData createExample() {
Map<String, List<String>> attributionFilterMap = new HashMap<>();
attributionFilterMap.put("ctid", Arrays.asList("1", "2"));
- AggregateFilterData filterData =
- new AggregateFilterData.Builder()
+ FilterData filterData =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionFilterMap)
.build();
Map<String, List<String>> attributionNonFilterMap = new HashMap<>();
attributionNonFilterMap.put("nctid", Arrays.asList("3"));
- AggregateFilterData nonFilterData =
- new AggregateFilterData.Builder()
+ FilterData nonFilterData =
+ new FilterData.Builder()
.setAttributionFilterMap(attributionNonFilterMap)
.build();
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java
index 8948dad65..936bb18df 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerIntegrationTest.java
@@ -16,10 +16,11 @@
package com.android.adservices.service.measurement.attribution;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.measurement.AbstractDbIntegrationTest;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
import com.android.adservices.data.measurement.DbState;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
import org.json.JSONException;
import org.junit.Assert;
@@ -51,7 +52,8 @@ public class AttributionJobHandlerIntegrationTest extends AbstractDbIntegrationT
@Override
public void runActionToTest() {
- DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
Assert.assertTrue("Attribution failed.",
(new AttributionJobHandler(datastoreManager))
.performPendingAttributions());
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
index 95b838867..0edcefdc8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.measurement.attribution;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -43,11 +45,14 @@ import com.android.adservices.service.measurement.Source;
import com.android.adservices.service.measurement.SourceFixture;
import com.android.adservices.service.measurement.Trigger;
import com.android.adservices.service.measurement.TriggerFixture;
+import com.android.adservices.service.measurement.aggregation.AggregateAttributionData;
+import com.android.adservices.service.measurement.aggregation.AggregateHistogramContribution;
+import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,6 +60,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -70,6 +76,8 @@ public class AttributionJobHandlerTest {
private static final Context sContext = ApplicationProvider.getApplicationContext();
private static final Uri APP_DESTINATION = Uri.parse("android-app://com.example.app");
private static final Uri PUBLISHER = Uri.parse("android-app://publisher.app");
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(111111L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(222222L);
private static final String EVENT_TRIGGERS =
"[\n"
+ "{\n"
@@ -128,7 +136,7 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
verify(mMeasurementDao).getTrigger(trigger.getId());
- verify(mMeasurementDao, never()).updateTriggerStatus(any());
+ verify(mMeasurementDao, never()).updateTriggerStatus(any(), anyInt());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -143,9 +151,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(new ArrayList<>());
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -171,7 +179,7 @@ public class AttributionJobHandlerTest {
+ "]\n")
.build();
Source source = SourceFixture.getValidSourceBuilder()
- .setDedupKeys(Arrays.asList(1L, 2L))
+ .setDedupKeys(Arrays.asList(new UnsignedLong(1L), new UnsignedLong(2L)))
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
.build();
List<Source> matchingSourceList = new ArrayList<>();
@@ -183,9 +191,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -207,9 +215,9 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
verify(mMeasurementDao).getAttributionsPerRateLimitWindow(source, trigger);
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -231,11 +239,12 @@ public class AttributionJobHandlerTest {
any(), any(), any(), anyLong(), anyLong())).thenReturn(10);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- verify(mMeasurementDao).countDistinctEnrollmentsPerPublisherXDestinationInAttribution(
- any(), any(), any(), anyLong(), anyLong());
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .countDistinctEnrollmentsPerPublisherXDestinationInAttribution(
+ any(), any(), any(), anyLong(), anyLong());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -269,9 +278,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -321,9 +330,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -345,12 +354,15 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(2L));
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(2L)));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -375,16 +387,20 @@ public class AttributionJobHandlerTest {
.setStatus(Trigger.Status.PENDING)
.setEventTriggers(eventTriggers)
.build();
- Source source1 = SourceFixture.getValidSourceBuilder()
- .setPriority(100L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setEventTime(1L)
- .build();
- Source source2 = SourceFixture.getValidSourceBuilder()
- .setPriority(200L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setEventTime(2L)
- .build();
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source1")
+ .setPriority(100L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setEventTime(1L)
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source2")
+ .setPriority(200L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setEventTime(2L)
+ .build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -396,14 +412,18 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
trigger.setStatus(Trigger.Status.ATTRIBUTED);
- verify(mMeasurementDao).updateSourceStatus(matchingSourceList, Source.Status.IGNORED);
- Assert.assertEquals(1, matchingSourceList.size());
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateSourceStatus(eq(List.of(source1.getId())), eq(Source.Status.IGNORED));
+ assertEquals(1, matchingSourceList.size());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(2L));
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(2L)));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -466,9 +486,10 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
verify(mMeasurementDao).deleteEventReport(eventReport1);
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -500,7 +521,7 @@ public class AttributionJobHandlerTest {
verify(mMeasurementDao).getTrigger(anyString());
verify(mMeasurementDao).getMatchingActiveSources(any());
verify(mMeasurementDao).getAttributionsPerRateLimitWindow(any(), any());
- verify(mMeasurementDao, never()).updateTriggerStatus(any());
+ verify(mMeasurementDao, never()).updateTriggerStatus(any(), anyInt());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction).rollback();
@@ -554,22 +575,16 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getMatchingActiveSources(trigger2)).thenReturn(matchingSourceList2);
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
- Assert.assertTrue(attributionService.performPendingAttributions());
+ assertTrue(attributionService.performPendingAttributions());
// Verify trigger status updates.
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao, times(2)).updateTriggerStatus(triggerArg.capture());
- List<Trigger> statusArgs = triggerArg.getAllValues();
- for (int i = 0; i < statusArgs.size(); i++) {
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, statusArgs.get(i).getStatus());
- Assert.assertEquals(triggers.get(i).getId(), statusArgs.get(i).getId());
- }
+ verify(mMeasurementDao, times(2)).updateTriggerStatus(any(), eq(Trigger.Status.ATTRIBUTED));
// Verify source dedup key updates.
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao, times(2))
.updateSourceDedupKeys(sourceArg.capture());
List<Source> dedupArgs = sourceArg.getAllValues();
for (int i = 0; i < dedupArgs.size(); i++) {
- Assert.assertEquals(
+ assertEquals(
dedupArgs.get(i).getDedupKeys(),
Collections.singletonList(
triggers.get(i).parseEventTriggers().get(0).getDedupKey()));
@@ -580,7 +595,7 @@ public class AttributionJobHandlerTest {
.insertEventReport(reportArg.capture());
List<EventReport> newReportArgs = reportArg.getAllValues();
for (int i = 0; i < newReportArgs.size(); i++) {
- Assert.assertEquals(
+ assertEquals(
newReportArgs.get(i).getTriggerDedupKey(),
triggers.get(i).parseEventTriggers().get(0).getDedupKey());
}
@@ -591,6 +606,7 @@ public class AttributionJobHandlerTest {
long eventTime = System.currentTimeMillis();
Trigger trigger =
TriggerFixture.getValidTriggerBuilder()
+ .setId("trigger1")
.setStatus(Trigger.Status.PENDING)
.setTriggerTime(eventTime + TimeUnit.DAYS.toMillis(5))
.setEventTriggers(
@@ -603,20 +619,24 @@ public class AttributionJobHandlerTest {
+ "]\n")
.build();
// Lower priority and older priority source.
- Source source1 = SourceFixture.getValidSourceBuilder()
- .setEventId(1)
- .setPriority(100L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setInstallAttributed(true)
- .setInstallCooldownWindow(TimeUnit.DAYS.toMillis(10))
- .setEventTime(eventTime - TimeUnit.DAYS.toMillis(2))
- .build();
- Source source2 = SourceFixture.getValidSourceBuilder()
- .setEventId(2)
- .setPriority(200L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setEventTime(eventTime)
- .build();
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source1")
+ .setEventId(new UnsignedLong(1L))
+ .setPriority(100L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setInstallAttributed(true)
+ .setInstallCooldownWindow(TimeUnit.DAYS.toMillis(10))
+ .setEventTime(eventTime - TimeUnit.DAYS.toMillis(2))
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source2")
+ .setEventId(new UnsignedLong(2L))
+ .setPriority(200L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setEventTime(eventTime)
+ .build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -628,24 +648,30 @@ public class AttributionJobHandlerTest {
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
trigger.setStatus(Trigger.Status.ATTRIBUTED);
- verify(mMeasurementDao).updateSourceStatus(matchingSourceList, Source.Status.IGNORED);
- Assert.assertEquals(1, matchingSourceList.size());
- Assert.assertEquals(source2.getEventId(), matchingSourceList.get(0).getEventId());
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateSourceStatus(eq(List.of(source2.getId())), eq(Source.Status.IGNORED));
+ assertEquals(1, matchingSourceList.size());
+ assertEquals(source2.getEventId(), matchingSourceList.get(0).getEventId());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(source1.getEventId(), sourceArg.getValue().getEventId());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(2L));
+ assertEquals(source1.getEventId(), sourceArg.getValue().getEventId());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(2L)));
verify(mMeasurementDao).insertEventReport(any());
}
@Test
public void shouldNotAttributeToOldInstallAttributedSource() throws DatastoreException {
+ // Setup
long eventTime = System.currentTimeMillis();
Trigger trigger =
TriggerFixture.getValidTriggerBuilder()
+ .setId("trigger1")
.setStatus(Trigger.Status.PENDING)
.setTriggerTime(eventTime + TimeUnit.DAYS.toMillis(10))
.setEventTriggers(
@@ -658,20 +684,24 @@ public class AttributionJobHandlerTest {
+ "]\n")
.build();
// Lower Priority. Install cooldown Window passed.
- Source source1 = SourceFixture.getValidSourceBuilder()
- .setEventId(1)
- .setPriority(100L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setInstallAttributed(true)
- .setInstallCooldownWindow(TimeUnit.DAYS.toMillis(3))
- .setEventTime(eventTime - TimeUnit.DAYS.toMillis(2))
- .build();
- Source source2 = SourceFixture.getValidSourceBuilder()
- .setEventId(2)
- .setPriority(200L)
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setEventTime(eventTime)
- .build();
+ Source source1 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source1")
+ .setEventId(new UnsignedLong(1L))
+ .setPriority(100L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setInstallAttributed(true)
+ .setInstallCooldownWindow(TimeUnit.DAYS.toMillis(3))
+ .setEventTime(eventTime - TimeUnit.DAYS.toMillis(2))
+ .build();
+ Source source2 =
+ SourceFixture.getValidSourceBuilder()
+ .setId("source2")
+ .setEventId(new UnsignedLong(2L))
+ .setPriority(200L)
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setEventTime(eventTime)
+ .build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -681,18 +711,25 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+ // Execution
attributionService.performPendingAttributions();
trigger.setStatus(Trigger.Status.ATTRIBUTED);
- verify(mMeasurementDao).updateSourceStatus(matchingSourceList, Source.Status.IGNORED);
- Assert.assertEquals(1, matchingSourceList.size());
- Assert.assertEquals(source1.getEventId(), matchingSourceList.get(0).getEventId());
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+
+ // Assertion
+ verify(mMeasurementDao)
+ .updateSourceStatus(eq(List.of(source1.getId())), eq(Source.Status.IGNORED));
+ assertEquals(1, matchingSourceList.size());
+ assertEquals(source1.getEventId(), matchingSourceList.get(0).getEventId());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(source2.getEventId(), sourceArg.getValue().getEventId());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(2L));
+ assertEquals(source2.getEventId(), sourceArg.getValue().getEventId());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(2L)));
verify(mMeasurementDao).insertEventReport(any());
}
@@ -724,9 +761,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
@@ -761,9 +798,9 @@ public class AttributionJobHandlerTest {
when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
attributionService.performPendingAttributions();
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
@@ -799,12 +836,41 @@ public class AttributionJobHandlerTest {
.setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
.build();
- Source source = SourceFixture.getValidSourceBuilder()
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateSource("[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .setAggregateFilterData("{\"product\":[\"1234\",\"2345\"]}")
- .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setId("sourceId1")
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData("{\"product\":[\"1234\",\"2345\"]}")
+ .build();
+ AggregateReport expectedAggregateReport =
+ new AggregateReport.Builder()
+ .setApiVersion("0.1")
+ .setAttributionDestination(trigger.getAttributionDestination())
+ .setDebugCleartextPayload(
+ "{\"operation\":\"histogram\","
+ + "\"data\":[{\"bucket\":2693,\"value\":1644},{\"bucket\":1369,"
+ + "\"value\":32768}]}")
+ .setEnrollmentId(source.getEnrollmentId())
+ .setPublisher(source.getRegistrant())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
+ .setAggregateAttributionData(
+ new AggregateAttributionData.Builder()
+ .setContributions(
+ Arrays.asList(
+ new AggregateHistogramContribution.Builder()
+ .setKey(new BigInteger("2693"))
+ .setValue(1644)
+ .build(),
+ new AggregateHistogramContribution.Builder()
+ .setKey(new BigInteger("1369"))
+ .setValue(32768)
+ .build()))
+ .build())
+ .build();
+
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -816,8 +882,12 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceAggregateContributions(sourceArg.capture());
- verify(mMeasurementDao).insertAggregateReport(any());
- Assert.assertEquals(sourceArg.getValue().getAggregateContributions(), 32768 + 1644);
+ ArgumentCaptor<AggregateReport> aggregateReportCaptor =
+ ArgumentCaptor.forClass(AggregateReport.class);
+ verify(mMeasurementDao).insertAggregateReport(aggregateReportCaptor.capture());
+
+ assertAggregateReportsEqual(expectedAggregateReport, aggregateReportCaptor.getValue());
+ assertEquals(sourceArg.getValue().getAggregateContributions(), 32768 + 1644);
}
@Test
@@ -842,13 +912,14 @@ public class AttributionJobHandlerTest {
.setEventTriggers(EVENT_TRIGGERS)
.build();
- Source source = SourceFixture.getValidSourceBuilder()
- .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateSource("[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .setAggregateFilterData("{\"product\":[\"1234\",\"2345\"]}")
- .setAggregateContributions(65536 - 32768 - 1644 + 1)
- .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData("{\"product\":[\"1234\",\"2345\"]}")
+ .setAggregateContributions(65536 - 32768 - 1644 + 1)
+ .build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
@@ -887,7 +958,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1_x\", \"value_2_x\"],\n"
+ " \"key_2\": [\"value_1_x\", \"value_2_x\"]\n"
@@ -907,9 +978,63 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.IGNORED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
+ verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
+ verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttributions_triggerNotFiltersWithCommonKeysIntersect_ignoreTrigger()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setNotFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2_x\"],\n"
+ + " \"key_2\": [\"value_1_x\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
verify(mMeasurementDao, never()).updateSourceDedupKeys(any());
verify(mMeasurementDao, never()).insertEventReport(any());
verify(mTransaction, times(2)).begin();
@@ -917,6 +1042,65 @@ public class AttributionJobHandlerTest {
}
@Test
+ public void performAttributions_triggerNotFiltersWithCommonKeysDontIntersect_attributeTrigger()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setNotFilters(
+ "{\n"
+ + " \"key_1\": [\"value_11_x\", \"value_12\"],\n"
+ + " \"key_2\": [\"value_21\", \"value_22_x\"]\n"
+ + "}\n")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ + " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
public void performAttributions_triggerSourceFiltersWithCommonKeysIntersect_attributeTrigger()
throws DatastoreException {
// Setup
@@ -941,7 +1125,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
@@ -961,12 +1145,198 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(1L));
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttributions_commonKeysIntersect_attributeTrigger_debugApi()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12\"],\n"
+ + " \"key_2\": [\"value_21\", \"value_22\"]\n"
+ + "}\n")
+ .setDebugKey(TRIGGER_DEBUG_KEY)
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ + " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
+ + "}\n")
+ .setDebugKey(SOURCE_DEBUG_KEY)
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
+ assertEquals(sourceArg.getValue().getDebugKey(), SOURCE_DEBUG_KEY);
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttributions_commonKeysIntersect_attributeTrigger_debugApi_sourceKey()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12\"],\n"
+ + " \"key_2\": [\"value_21\", \"value_22\"]\n"
+ + "}\n")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ + " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
+ + "}\n")
+ .setDebugKey(SOURCE_DEBUG_KEY)
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
+ assertEquals(sourceArg.getValue().getDebugKey(), SOURCE_DEBUG_KEY);
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttributions_commonKeysIntersect_attributeTrigger_debugApi_triggerKey()
+ throws DatastoreException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12\"],\n"
+ + " \"key_2\": [\"value_21\", \"value_22\"]\n"
+ + "}\n")
+ .setDebugKey(TRIGGER_DEBUG_KEY)
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_11\", \"value_12_x\"],\n"
+ + " \"key_2\": [\"value_21_x\", \"value_22\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
+ verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -997,7 +1367,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1x\": [\"value_11_x\", \"value_12_x\"],\n"
+ " \"key_2x\": [\"value_21_x\", \"value_22_x\"]\n"
@@ -1017,12 +1387,15 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
ArgumentCaptor<Source> sourceArg = ArgumentCaptor.forClass(Source.class);
verify(mMeasurementDao).updateSourceDedupKeys(sourceArg.capture());
- Assert.assertEquals(sourceArg.getValue().getDedupKeys(), Collections.singletonList(1L));
+ assertEquals(
+ sourceArg.getValue().getDedupKeys(),
+ Collections.singletonList(new UnsignedLong(1L)));
verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
@@ -1060,7 +1433,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ " \"key_2\": [\"value_1\", \"value_2\"]\n"
@@ -1070,10 +1443,10 @@ public class AttributionJobHandlerTest {
EventReport expectedEventReport =
new EventReport.Builder()
.setTriggerPriority(3L)
- .setTriggerDedupKey(3L)
- .setTriggerData(1L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
+ .setTriggerData(new UnsignedLong(1L))
.setTriggerTime(234324L)
- .setSourceId(source.getEventId())
+ .setSourceEventId(source.getEventId())
.setStatus(EventReport.Status.PENDING)
.setAttributionDestination(source.getAppDestination())
.setEnrollmentId(source.getEnrollmentId())
@@ -1082,6 +1455,8 @@ public class AttributionJobHandlerTest {
trigger.getTriggerTime(), trigger.getDestinationType()))
.setSourceType(source.getSourceType())
.setRandomizedTriggerRate(source.getRandomAttributionProbability())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
.build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
@@ -1097,9 +1472,10 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).updateSourceDedupKeys(source);
verify(mMeasurementDao).insertEventReport(eq(expectedEventReport));
verify(mTransaction, times(2)).begin();
@@ -1138,7 +1514,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ " \"key_2\": [\"value_1\", \"value_2\"]\n"
@@ -1148,10 +1524,10 @@ public class AttributionJobHandlerTest {
EventReport expectedEventReport =
new EventReport.Builder()
.setTriggerPriority(3L)
- .setTriggerDedupKey(3L)
- .setTriggerData(1L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
+ .setTriggerData(new UnsignedLong(1L))
.setTriggerTime(234324L)
- .setSourceId(source.getEventId())
+ .setSourceEventId(source.getEventId())
.setStatus(EventReport.Status.PENDING)
.setAttributionDestination(source.getAppDestination())
.setEnrollmentId(source.getEnrollmentId())
@@ -1160,6 +1536,8 @@ public class AttributionJobHandlerTest {
trigger.getTriggerTime(), trigger.getDestinationType()))
.setSourceType(source.getSourceType())
.setRandomizedTriggerRate(source.getRandomAttributionProbability())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
.build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
@@ -1175,9 +1553,10 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).updateSourceDedupKeys(source);
verify(mMeasurementDao).insertEventReport(eq(expectedEventReport));
verify(mTransaction, times(2)).begin();
@@ -1218,7 +1597,7 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ " \"key_2\": [\"value_1\", \"value_2\"]\n"
@@ -1229,10 +1608,10 @@ public class AttributionJobHandlerTest {
EventReport expectedEventReport =
new EventReport.Builder()
.setTriggerPriority(3L)
- .setTriggerDedupKey(3L)
- .setTriggerData(3L)
+ .setTriggerDedupKey(new UnsignedLong(3L))
+ .setTriggerData(new UnsignedLong(3L))
.setTriggerTime(234324L)
- .setSourceId(source.getEventId())
+ .setSourceEventId(source.getEventId())
.setStatus(EventReport.Status.PENDING)
.setAttributionDestination(source.getAppDestination())
.setEnrollmentId(source.getEnrollmentId())
@@ -1241,6 +1620,8 @@ public class AttributionJobHandlerTest {
trigger.getTriggerTime(), trigger.getDestinationType()))
.setSourceType(Source.SourceType.NAVIGATION)
.setRandomizedTriggerRate(source.getRandomAttributionProbability())
+ .setSourceId(source.getId())
+ .setTriggerId(trigger.getId())
.build();
when(mMeasurementDao.getPendingTriggerIds())
.thenReturn(Collections.singletonList(trigger.getId()));
@@ -1256,9 +1637,10 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).updateSourceDedupKeys(source);
verify(mMeasurementDao).insertEventReport(eq(expectedEventReport));
verify(mTransaction, times(2)).begin();
@@ -1299,15 +1681,14 @@ public class AttributionJobHandlerTest {
Source source =
SourceFixture.getValidSourceBuilder()
.setAttributionMode(Source.AttributionMode.TRUTHFULLY)
- .setAggregateFilterData(
+ .setFilterData(
"{\n"
+ " \"key_1\": [\"value_1_y\", \"value_2_y\"],\n"
+ " \"key_2\": [\"value_1_y\", \"value_2_y\"]\n"
+ "}\n")
.setAggregateSource(
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .setAggregateFilterData(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
"{\"product\":[\"1234\",\"2345\"], \"key_1\": "
+ "[\"value_1_y\", \"value_2_y\"]}")
.setId("sourceId")
@@ -1326,11 +1707,283 @@ public class AttributionJobHandlerTest {
attributionService.performPendingAttributions();
// Assertions
- ArgumentCaptor<Trigger> triggerArg = ArgumentCaptor.forClass(Trigger.class);
- verify(mMeasurementDao).updateTriggerStatus(triggerArg.capture());
- Assert.assertEquals(Trigger.Status.ATTRIBUTED, triggerArg.getValue().getStatus());
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mMeasurementDao).insertAggregateReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttribution_aggregateReportsExceedsLimit_insertsOnlyEventReport()
+ throws DatastoreException, JSONException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .setTriggerTime(234324L)
+ .setAggregateTriggerData(buildAggregateTriggerData().toString())
+ .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ int numAggregateReportPerDestination = 1024;
+ int numEventReportPerDestination = 1023;
+ when(mMeasurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numAggregateReportPerDestination);
+ when(mMeasurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numEventReportPerDestination);
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertions
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ verify(mMeasurementDao, never()).insertAggregateReport(any());
+ verify(mMeasurementDao).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttribution_eventReportsExceedsLimit_insertsOnlyAggregateReport()
+ throws DatastoreException, JSONException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .setTriggerTime(234324L)
+ .setAggregateTriggerData(buildAggregateTriggerData().toString())
+ .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ int numAggregateReportPerDestination = 1023;
+ int numEventReportPerDestination = 1024;
+ when(mMeasurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numAggregateReportPerDestination);
+ when(mMeasurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numEventReportPerDestination);
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertion
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
+ verify(mMeasurementDao).insertAggregateReport(any());
verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttribution_aggregateAndEventReportsExceedsLimit_noReportInsertion()
+ throws DatastoreException, JSONException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .setTriggerTime(234324L)
+ .setAggregateTriggerData(buildAggregateTriggerData().toString())
+ .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ int numAggregateReportPerDestination = 1024;
+ int numEventReportPerDestination = 1024;
+ when(mMeasurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numAggregateReportPerDestination);
+ when(mMeasurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numEventReportPerDestination);
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertion
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())), eq(Trigger.Status.IGNORED));
+ verify(mMeasurementDao, never()).insertAggregateReport(any());
+ verify(mMeasurementDao, never()).insertEventReport(any());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void performAttribution_aggregateAndEventReportsDoNotExceedsLimit_ReportInsertion()
+ throws DatastoreException, JSONException {
+ // Setup
+ Trigger trigger =
+ TriggerFixture.getValidTriggerBuilder()
+ .setId("triggerId1")
+ .setStatus(Trigger.Status.PENDING)
+ .setEventTriggers(
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \"5\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"deduplication_key\": \"1\"\n"
+ + "}"
+ + "]\n")
+ .setFilters(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .setTriggerTime(234324L)
+ .setAggregateTriggerData(buildAggregateTriggerData().toString())
+ .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+ .build();
+ Source source =
+ SourceFixture.getValidSourceBuilder()
+ .setAttributionMode(Source.AttributionMode.TRUTHFULLY)
+ .setAggregateSource(
+ "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .setFilterData(
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}\n")
+ .build();
+ when(mMeasurementDao.getPendingTriggerIds())
+ .thenReturn(Collections.singletonList(trigger.getId()));
+ when(mMeasurementDao.getTrigger(trigger.getId())).thenReturn(trigger);
+ List<Source> matchingSourceList = new ArrayList<>();
+ matchingSourceList.add(source);
+ when(mMeasurementDao.getMatchingActiveSources(trigger)).thenReturn(matchingSourceList);
+ when(mMeasurementDao.getAttributionsPerRateLimitWindow(any(), any())).thenReturn(5L);
+ when(mMeasurementDao.getSourceEventReports(any())).thenReturn(new ArrayList<>());
+ int numAggregateReportPerDestination = 1023;
+ int numEventReportPerDestination = 1023;
+ when(mMeasurementDao.getNumAggregateReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numAggregateReportPerDestination);
+ when(mMeasurementDao.getNumEventReportsPerDestination(
+ trigger.getAttributionDestination(), trigger.getDestinationType()))
+ .thenReturn(numEventReportPerDestination);
+ AttributionJobHandler attributionService = new AttributionJobHandler(mDatastoreManager);
+
+ // Execution
+ attributionService.performPendingAttributions();
+
+ // Assertion
+ verify(mMeasurementDao)
+ .updateTriggerStatus(
+ eq(Collections.singletonList(trigger.getId())),
+ eq(Trigger.Status.ATTRIBUTED));
verify(mMeasurementDao).insertAggregateReport(any());
+ verify(mMeasurementDao).insertEventReport(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -1357,4 +2010,24 @@ public class AttributionJobHandlerTest {
filterData.put("product", new JSONArray(Arrays.asList("1234", "2345")));
return filterData;
}
+
+ private void assertAggregateReportsEqual(
+ AggregateReport expectedReport, AggregateReport actualReport) {
+ // Avoids checking report time because there is randomization
+ assertEquals(expectedReport.getApiVersion(), actualReport.getApiVersion());
+ assertEquals(
+ expectedReport.getAttributionDestination(),
+ actualReport.getAttributionDestination());
+ assertEquals(
+ expectedReport.getDebugCleartextPayload(), actualReport.getDebugCleartextPayload());
+ assertEquals(expectedReport.getEnrollmentId(), actualReport.getEnrollmentId());
+ assertEquals(expectedReport.getPublisher(), actualReport.getPublisher());
+ assertEquals(expectedReport.getSourceId(), actualReport.getSourceId());
+ assertEquals(expectedReport.getTriggerId(), actualReport.getTriggerId());
+ assertEquals(
+ expectedReport.getAggregateAttributionData(),
+ actualReport.getAggregateAttributionData());
+ assertEquals(expectedReport.getSourceDebugKey(), actualReport.getSourceDebugKey());
+ assertEquals(expectedReport.getTriggerDebugKey(), actualReport.getTriggerDebugKey());
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java
index c1ced6759..d32739f30 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.attribution;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_ATTRIBUTION_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,17 +36,17 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -56,11 +57,6 @@ import java.util.Optional;
* Unit test for {@link AttributionJobService
*/
public class AttributionJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
private static final long WAIT_IN_MILLIS = 50L;
private DatastoreManager mMockDatastoreManager;
@@ -77,10 +73,11 @@ public class AttributionJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -99,11 +96,11 @@ public class AttributionJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
ExtendedMockito.doNothing()
.when(
() ->
@@ -128,11 +125,11 @@ public class AttributionJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -159,11 +156,11 @@ public class AttributionJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -190,11 +187,11 @@ public class AttributionJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -220,11 +217,11 @@ public class AttributionJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -246,11 +243,27 @@ public class AttributionJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ AttributionJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertFalse(captor.getValue().isPersisted());
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
- .spyStatic(DatastoreManagerFactory.class)
.spyStatic(AttributionJobService.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -282,10 +295,8 @@ public class AttributionJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_attribution_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getMeasurementJobAttributionKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncSourceFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncSourceFetcherTest.java
new file mode 100644
index 000000000..2ddb62286
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncSourceFetcherTest.java
@@ -0,0 +1,2635 @@
+/*
+ * 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.measurement.registration;
+
+import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
+import static com.android.adservices.service.measurement.PrivacyParams.MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_REDIRECTS_PER_REGISTRATION;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.adservices.measurement.WebSourceParams;
+import android.adservices.measurement.WebSourceRegistrationRequest;
+import android.content.Context;
+import android.net.Uri;
+import android.view.InputEvent;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.net.ssl.HttpsURLConnection;
+/** Unit tests for {@link SourceFetcher} */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+public final class AsyncSourceFetcherTest {
+ private static final String ANDROID_APP_SCHEME = "android-app";
+ private static final String ANDROID_APP_SCHEME_URI_PREFIX = ANDROID_APP_SCHEME + "://";
+ private static final String DEFAULT_REGISTRATION = "https://foo.com";
+ private static final String ENROLLMENT_ID = "enrollment-id";
+ private static final EnrollmentData ENROLLMENT =
+ new EnrollmentData.Builder().setEnrollmentId("enrollment-id").build();
+ private static final String DEFAULT_TOP_ORIGIN =
+ "https://com.android.adservices.servicecoretest";
+ ;
+ private static final String DEFAULT_DESTINATION = "android-app://com.myapps";
+ private static final String DEFAULT_DESTINATION_WITHOUT_SCHEME = "com.myapps";
+ private static final long DEFAULT_PRIORITY = 123;
+ private static final long DEFAULT_EXPIRY = 456789;
+ private static final UnsignedLong DEFAULT_EVENT_ID = new UnsignedLong(987654321L);
+ private static final UnsignedLong EVENT_ID_1 = new UnsignedLong(987654321L);
+ private static final UnsignedLong DEBUG_KEY = new UnsignedLong(823523783L);
+ private static final String LIST_TYPE_REDIRECT_URI = "https://bar.com";
+ private static final String LOCATION_TYPE_REDIRECT_URI = "https://example.com";
+ private static final String ALT_DESTINATION = "android-app://com.yourapps";
+ private static final long ALT_PRIORITY = 321;
+ private static final long ALT_EVENT_ID = 123456789;
+ private static final long ALT_EXPIRY = 456790;
+ private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com");
+ private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo2.com");
+ private static final Uri OS_DESTINATION = Uri.parse("android-app://com.os-destination");
+ private static final String LONG_FILTER_STRING = "12345678901234567890123456";
+ private static final String LONG_AGGREGATE_KEY_ID = "12345678901234567890123456";
+ private static final String LONG_AGGREGATE_KEY_PIECE = "0x123456789012345678901234567890123";
+ private static final Uri OS_DESTINATION_WITH_PATH =
+ Uri.parse("android-app://com.os-destination/my/path");
+ private static final Uri WEB_DESTINATION = Uri.parse("https://web-destination.com");
+ private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN =
+ Uri.parse("https://subdomain.web-destination.com");
+ private static final WebSourceParams SOURCE_REGISTRATION_1 =
+ new WebSourceParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
+ private static final WebSourceParams SOURCE_REGISTRATION_2 =
+ new WebSourceParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ AsyncSourceFetcher mFetcher;
+
+ @Mock HttpsURLConnection mUrlConnection;
+ @Mock EnrollmentDao mEnrollmentDao;
+ @Mock Flags mFlags;
+ @Mock AdServicesLogger mLogger;
+
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void setup() {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+
+ mFetcher = spy(new AsyncSourceFetcher(mEnrollmentDao, mFlags, mLogger));
+ // For convenience, return the same enrollment-ID since we're using many arbitrary
+ // registration URIs and not yet enforcing uniqueness of enrollment.
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(ENROLLMENT);
+ }
+
+ @After
+ public void cleanup() throws InterruptedException {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testBasicSourceRequest() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ doReturn(5000L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + " \"expiry\": \""
+ + DEFAULT_EXPIRY
+ + "\",\n"
+ + " \"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_PRIORITY, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRY),
+ result.getExpiryTime());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(DEBUG_KEY, result.getDebugKey());
+ verify(mLogger)
+ .logMeasurementRegistrationsResponseSize(
+ eq(
+ new MeasurementRegistrationResponseStats.Builder(
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS,
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
+ 190)
+ .setAdTechDomain(null)
+ .build()));
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_failsWhenNotEnrolled() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + " \"expiry\": \""
+ + DEFAULT_EXPIRY
+ + "\",\n"
+ + " \"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(
+ AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mFetcher, never()).openUrl(any());
+ }
+
+ @Test
+ public void testBasicSourceRequestWithoutAdIdPermission() throws Exception {
+ RegistrationRequest request = buildRequestWithoutAdIdPermission(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + " \"expiry\": \""
+ + DEFAULT_EXPIRY
+ + "\",\n"
+ + " \"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_PRIORITY, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRY),
+ result.getExpiryTime());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testSourceRequestWithPostInstallAttributes() throws Exception {
+ RegistrationRequest request =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("https://foo.com"))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"install_attribution_window\": \"272800\",\n"
+ + " \"post_install_exclusivity_window\": \"987654\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(123, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789), result.getExpiryTime());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(TimeUnit.SECONDS.toMillis(272800), result.getInstallAttributionWindow());
+ assertEquals(TimeUnit.SECONDS.toMillis(987654L), result.getInstallCooldownWindow());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testSourceRequestWithPostInstallAttributesReceivedAsNull() throws Exception {
+ RegistrationRequest request =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("https://foo.com"))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"install_attribution_window\": null,\n"
+ + " \"post_install_exclusivity_window\": null\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(123, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789), result.getExpiryTime());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ // fallback to default value - 30 days
+ assertEquals(TimeUnit.SECONDS.toMillis(2592000L), result.getInstallAttributionWindow());
+ // fallback to default value - 0 days
+ assertEquals(0L, result.getInstallCooldownWindow());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testSourceRequestWithInstallAttributesOutofBounds() throws IOException {
+ RegistrationRequest request =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("https://foo.com"))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ // Min value of attribution is 1 day or 86400
+ // seconds
+ + " \"install_attribution_window\": \"86300\",\n"
+ // Max value of cooldown is 30 days or 2592000
+ // seconds
+ + " \"post_install_exclusivity_window\":"
+ + " \"9876543210\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(123, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789), result.getExpiryTime());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ // Adjusted to minimum allowed value
+ assertEquals(TimeUnit.SECONDS.toMillis(86400), result.getInstallAttributionWindow());
+ // Adjusted to maximum allowed value
+ assertEquals(TimeUnit.SECONDS.toMillis((2592000L)), result.getInstallCooldownWindow());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadSourceUrl() {
+ RegistrationRequest request = buildRequest(/* registrationUri = */ "bad-schema://foo.com");
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ }
+
+ @Test
+ public void testBadSourceConnection() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doThrow(new IOException("Bad internet things"))
+ .when(mFetcher)
+ .openUrl(new URL(DEFAULT_REGISTRATION));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ }
+
+ @Test
+ public void testBadSourceJson_missingSourceEventId() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadSourceJson_missingHeader() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields()).thenReturn(Collections.emptyMap());
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadSourceJson_missingDestination() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\"")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequestMinimumFields() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_sourceEventId_negative() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\"-35\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testBasicSourceRequest_sourceEventId_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\",\"source_event_id\":\""
+ + "18446744073709551616\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testBasicSourceRequest_sourceEventId_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\"8l2\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testBasicSourceRequest_sourceEventId_uses64thBit() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\",\"source_event_id\":\""
+ + "18446744073709551615\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(0, result.getPriority());
+ assertEquals(new UnsignedLong(-1L), result.getEventId());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_debugKey_negative() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\""
+ + DEFAULT_EVENT_ID
+ + "\","
+ + "\"debug_key\":\"-18\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertNull(result.getDebugKey());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_debugKey_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\""
+ + DEFAULT_EVENT_ID
+ + "\","
+ + "\"debug_key\":\"18446744073709551616\"}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertNull(result.getDebugKey());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_debugKey_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\""
+ + DEFAULT_EVENT_ID
+ + "\","
+ + "\"debug_key\":\"987fs\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertNull(result.getDebugKey());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequest_debugKey_uses64thBit() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\"destination\":\""
+ + DEFAULT_DESTINATION
+ + "\","
+ + "\"source_event_id\":\""
+ + DEFAULT_EVENT_ID
+ + "\","
+ + "\"debug_key\":\"18446744073709551615\"}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(new UnsignedLong(-1L), result.getDebugKey());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequestMinimumFieldsAndRestNull() throws Exception {
+ RegistrationRequest request =
+ new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse("https://foo.com"))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \"android-app://com.myapps\",\n"
+ + "\"source_event_id\": \"123\",\n"
+ + "\"priority\": null,\n"
+ + "\"expiry\": null\n"
+ + "}\n")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(new UnsignedLong(123L), result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testBasicSourceRequestWithExpiryLessThan2Days() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + "\"expiry\": 1"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testBasicSourceRequestWithExpiryMoreThan30Days() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + "\"expiry\": 2678400"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(0, result.getPriority());
+ assertEquals(
+ result.getEventTime()
+ + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testNotOverHttps() throws Exception {
+ RegistrationRequest request = buildRequest("http://foo.com");
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mFetcher, never()).openUrl(any());
+ }
+
+ @Test
+ public void test500_ignoreFailure() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(500);
+ Map<String, List<String>> headersSecondRequest = new HashMap<>();
+ headersSecondRequest.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + ALT_EVENT_ID
+ + "\",\n"
+ + "\"expiry\": "
+ + ALT_EXPIRY
+ + ""
+ + "}\n"));
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersSecondRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(
+ AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testFailedParsingButValidRedirect_returnFailure() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersFirstRequest = new HashMap<>();
+ headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{}"));
+ headersFirstRequest.put("Attribution-Reporting-Redirect", List.of("https://bar.com"));
+ Map<String, List<String>> headersSecondRequest = new HashMap<>();
+ headersSecondRequest.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + ALT_EVENT_ID
+ + "\",\n"
+ + "\"expiry\": "
+ + ALT_EXPIRY
+ + ""
+ + "}\n"));
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(headersFirstRequest)
+ .thenReturn(headersSecondRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+ @Test
+ public void testRedirectDifferentDestination_keepAllReturnSuccess() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersFirstRequest = new HashMap<>();
+ headersFirstRequest.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + "\"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + "\"expiry\": "
+ + DEFAULT_EXPIRY
+ + ""
+ + "}\n"));
+ headersFirstRequest.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ Map<String, List<String>> headersSecondRequest = new HashMap<>();
+ headersSecondRequest.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + ALT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + ALT_EVENT_ID
+ + "\",\n"
+ + "\"priority\": \""
+ + ALT_PRIORITY
+ + "\",\n"
+ + "\"expiry\": "
+ + ALT_EXPIRY
+ + ""
+ + "}\n"));
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(headersFirstRequest)
+ .thenReturn(headersSecondRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(DEFAULT_PRIORITY, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRY),
+ result.getExpiryTime());
+ assertEquals(LIST_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ // Tests for redirect types
+
+ @Test
+ public void testRedirectType_bothRedirectHeaderTypes_choosesListType() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate both 'list' and 'location' type headers
+ headers.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'none' type redirects were chosen
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LIST_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectHeaderType_choosesLocationType() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'location' type redirects were chosen
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_maxCount_noRedirectReturned()
+ throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ appSourceRegistrationRequest(
+ request,
+ AsyncRegistration.RedirectType.DAISY_CHAIN,
+ MAX_REDIRECTS_PER_REGISTRATION),
+ asyncFetchStatus,
+ asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'location' type redirect but no uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_count1_redirectReturned() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(
+ request, AsyncRegistration.RedirectType.DAISY_CHAIN, 1),
+ asyncFetchStatus,
+ asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'location' type redirect and uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_ignoresListType() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate both 'list' and 'location' type headers
+ headers.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(
+ request, AsyncRegistration.RedirectType.DAISY_CHAIN, 1),
+ asyncFetchStatus,
+ asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertDefaultSourceRegistration(result);
+
+ // Assert 'location' type redirect and uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ // End tests for redirect types
+
+ @Test
+ public void testBasicSourceRequestWithFilterData() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ String filterData =
+ " \"filter_data\": {\"product\":[\"1234\",\"2345\"], \"ctid\":[\"id\"]} \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("android-app://com.myapps", result.getAppDestination().toString());
+ assertEquals(123, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789), result.getExpiryTime());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ "{\"product\":[\"1234\",\"2345\"],\"ctid\":[\"id\"]}",
+ result.getFilterData());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testSourceRequest_filterData_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ String filterData =
+ " \"filter_data\": {\"product\":\"1234\",\"2345\"], \"ctid\":[\"id\"]} \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequest_filterData_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ String filterData = " \"filter_data\": " + filters + "\n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequest_filterData_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ String filterData =
+ " \"filter_data\": {\"product\":[\"1234\",\"2345\"], \""
+ + LONG_FILTER_STRING
+ + "\":[\"id\"]} \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequest_filterData_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ String filterData = " \"filter_data\": " + filters + " \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequest_filterData_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ String filterData =
+ " \"filter_data\": {\"product\":[\"1234\",\""
+ + LONG_FILTER_STRING
+ + "\"], \"ctid\":[\"id\"]} \n";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + filterData
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testMissingHeaderButWithRedirect() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(Map.of(
+ "Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI)))
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + " \"expiry\": \""
+ + DEFAULT_EXPIRY
+ + "\",\n"
+ + " \"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequestWithAggregateSource() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": {\"campaignCounts\" :"
+ + " \"0x159\", \"geoValue\" : \"0x5\"}\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(
+ new JSONObject("{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
+ .toString(),
+ result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicSourceRequestWithAggregateSource_rejectsTooManyKeys() throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("{");
+ for (int i = 0; i < 51; i++) {
+ tooManyKeys.append(String.format("\"campaign-%1$s\": \"0x15%1$s\"", i));
+ }
+ tooManyKeys.append("}");
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\":"
+ + " \"987654321\",\"aggregation_keys\": "
+ + tooManyKeys)));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testSourceRequestWithAggregateSource_tooManyKeys() throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("{");
+ for (int i = 0; i < MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1; i++) {
+ tooManyKeys.append(String.format("\"campaign-%1$s\": \"0x15%1$s\"", i));
+ }
+ tooManyKeys.append("}");
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\":"
+ + " \"987654321\",\"aggregation_keys\": "
+ + tooManyKeys)));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testSourceRequestWithAggregateSource_keyIsNotAnObject() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": [\"campaignCounts\","
+ + " \"0x159\", \"geoValue\", \"0x5\"]\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequestWithAggregateSource_invalidKeyId() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": {\""
+ + LONG_AGGREGATE_KEY_ID
+ + "\": \"0x159\","
+ + "\"geoValue\": \"0x5\"}\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testSourceRequestWithAggregateSource_invalidKeyPiece_missingPrefix()
+ throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": {\"campaignCounts\" :"
+ + " \"0159\", \"geoValue\" : \"0x5\"}\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testSourceRequestWithAggregateSource_invalidKeyPiece_tooLong() throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com.myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"aggregation_keys\": {\"campaignCounts\":"
+ + " \"0x159\", \"geoValue\": \""
+ + LONG_AGGREGATE_KEY_PIECE
+ + "\"}\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebSources_basic_success() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Arrays.asList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(Uri.parse(DEFAULT_DESTINATION), result.getAppDestination());
+ assertEquals(EVENT_ID_1, result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSourcesSuccessWithoutAdIdPermission() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Arrays.asList(SOURCE_REGISTRATION_1, SOURCE_REGISTRATION_2),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, false), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(Uri.parse(DEFAULT_DESTINATION), result.getAppDestination());
+ assertEquals(EVENT_ID_1, result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ assertNull(result.getFilterData());
+ assertNull(result.getAggregateSource());
+ }
+ @Test
+ public void fetchWebSources_oneSuccessAndOneFailure_resultsIntoOneSourceFetched()
+ throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Arrays.asList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ // Its validation will fail due to destination mismatch
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ /* wrong destination */
+ + "android-app://com.wrongapp"
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\",\n"
+ + " \"debug_key\": \""
+ + DEBUG_KEY
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_withExtendedHeaders_success() throws IOException, JSONException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String aggregateSource = "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}";
+ String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + OS_DESTINATION
+ + "\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"filter_data\": "
+ + filterData
+ + ", "
+ + " \"aggregation_keys\": "
+ + aggregateSource
+ + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(OS_DESTINATION, result.getAppDestination());
+ assertEquals(filterData, result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertEquals(new JSONObject(aggregateSource).toString(), result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_withRedirects_ignoresRedirects() throws IOException, JSONException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String aggregateSource = "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}";
+ String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"filter_data\": "
+ + filterData
+ + " , "
+ + " \"aggregation_keys\": "
+ + aggregateSource
+ + "}"),
+ "Attribution-Reporting-Redirect",
+ List.of(LIST_TYPE_REDIRECT_URI),
+ "Location",
+ List.of(LOCATION_TYPE_REDIRECT_URI)));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(Uri.parse(DEFAULT_DESTINATION), result.getAppDestination());
+ assertEquals(filterData, result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertEquals(new JSONObject(aggregateSource).toString(), result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebSources_appDestinationDoNotMatch_failsDropsSource() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"destination\":"
+ + " android-app://wrong.os-destination,\n"
+ + " \"web_destination\": "
+ + WEB_DESTINATION
+ + ",\n"
+ + " \"filter_data\": "
+ + filterData
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebSources_webDestinationDoNotMatch_failsDropsSource() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ WEB_DESTINATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"destination\": "
+ + OS_DESTINATION
+ + ",\n"
+ + " \"web_destination\": "
+ + " https://wrong-web-destination.com,\n"
+ + " \"filter_data\": "
+ + filterData
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebSources_osAndWebDestinationMatch_recordSourceSuccess() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ WEB_DESTINATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"destination\": \""
+ + OS_DESTINATION
+ + "\",\n"
+ + "\"web_destination\": \""
+ + WEB_DESTINATION
+ + "\""
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(OS_DESTINATION, result.getAppDestination());
+ assertNull(result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertNull(result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchWebSources_extractsTopPrivateDomain() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ WEB_DESTINATION_WITH_SUBDOMAIN);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \"android-app://com"
+ + ".myapps\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + " \"destination\": \""
+ + OS_DESTINATION
+ + "\",\n"
+ + "\"web_destination\": \""
+ + WEB_DESTINATION_WITH_SUBDOMAIN
+ + "\""
+ + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(OS_DESTINATION, result.getAppDestination());
+ assertNull(result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertNull(result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_extractsDestinationBaseUri() throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION_WITH_PATH,
+ WEB_DESTINATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + " \"destination\": \""
+ + OS_DESTINATION_WITH_PATH
+ + "\",\n"
+ + " \"priority\": \"123\",\n"
+ + " \"expiry\": \"456789\",\n"
+ + " \"source_event_id\": \"987654321\",\n"
+ + "\"web_destination\": \""
+ + WEB_DESTINATION
+ + "\""
+ + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(OS_DESTINATION, result.getAppDestination());
+ assertNull(result.getFilterData());
+ assertEquals(new UnsignedLong(987654321L), result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(456789L),
+ result.getExpiryTime());
+ assertNull(result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_missingDestinations_dropsSource() throws Exception {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ OS_DESTINATION,
+ WEB_DESTINATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_withDestinationUriNotHavingScheme_attachesAppScheme()
+ throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION_WITHOUT_SCHEME),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Source result = fetch.get();
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(Uri.parse(DEFAULT_DESTINATION), result.getAppDestination());
+ assertNull(result.getFilterData());
+ assertEquals(EVENT_ID_1, result.getEventId());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(
+ MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS),
+ result.getExpiryTime());
+ assertNull(result.getAggregateSource());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void fetchWebSources_withDestinationUriHavingHttpsScheme_dropsSource()
+ throws IOException {
+ // Setup
+ WebSourceRegistrationRequest request =
+ buildWebSourceRegistrationRequest(
+ Collections.singletonList(SOURCE_REGISTRATION_1),
+ DEFAULT_TOP_ORIGIN,
+ Uri.parse(DEFAULT_DESTINATION_WITHOUT_SCHEME),
+ null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ // Invalid (https) URI for app destination
+ + WEB_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + EVENT_ID_1
+ + "\"\n"
+ + "}\n")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch = mFetcher.fetchSource(
+ webSourceRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void basicSourceRequest_headersMoreThanMaxResponseSize_emitsMetricsWithAdTechDomain()
+ throws Exception {
+ RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
+ doReturn(5L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\"\n"
+ + "}\n")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Source> fetch =
+ mFetcher.fetchSource(
+ appSourceRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+
+ assertTrue(fetch.isPresent());
+ verify(mLogger)
+ .logMeasurementRegistrationsResponseSize(
+ eq(
+ new MeasurementRegistrationResponseStats.Builder(
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS,
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
+ 115)
+ .setAdTechDomain(DEFAULT_REGISTRATION)
+ .build()));
+ }
+ private RegistrationRequest buildRequest(String registrationUri) {
+ return new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse(registrationUri))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(true)
+ .build();
+ }
+ private RegistrationRequest buildRequestWithoutAdIdPermission(String registrationUri) {
+ return new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
+ .setRegistrationUri(Uri.parse(registrationUri))
+ .setPackageName(sContext.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(false)
+ .build();
+ }
+
+ public static AsyncRegistration appSourceRegistrationRequest(
+ RegistrationRequest registrationRequest) {
+ return appSourceRegistrationRequest(
+ registrationRequest, AsyncRegistration.RedirectType.ANY, 0);
+ }
+
+ public static AsyncRegistration appSourceRegistrationRequest(
+ RegistrationRequest registrationRequest,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount) {
+ // Necessary for testing
+ String enrollmentId = "";
+ if (EnrollmentDao.getInstance(sContext)
+ .getEnrollmentDataFromMeasurementUrl(
+ registrationRequest
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ != null) {
+ enrollmentId =
+ EnrollmentDao.getInstance(sContext)
+ .getEnrollmentDataFromMeasurementUrl(
+ registrationRequest
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ .getEnrollmentId();
+ }
+ return createAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ registrationRequest.getRegistrationUri(),
+ null,
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + sContext.getPackageName()),
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + sContext.getPackageName()),
+ registrationRequest.getRegistrationType() == RegistrationRequest.REGISTER_SOURCE
+ ? AsyncRegistration.RegistrationType.APP_SOURCE
+ : AsyncRegistration.RegistrationType.APP_TRIGGER,
+ getSourceType(registrationRequest.getInputEvent()),
+ System.currentTimeMillis(),
+ 0,
+ System.currentTimeMillis(),
+ redirectType,
+ redirectCount,
+ registrationRequest.isAdIdPermissionGranted());
+ }
+
+ private static AsyncRegistration webSourceRegistrationRequest(
+ WebSourceRegistrationRequest webSourceRegistrationRequest,
+ boolean adIdPermissionGranted) {
+ if (webSourceRegistrationRequest.getSourceParams().size() > 0) {
+ WebSourceParams webSourceParams = webSourceRegistrationRequest.getSourceParams().get(0);
+ // Necessary for testing
+ String enrollmentId = "";
+ if (EnrollmentDao.getInstance(sContext)
+ .getEnrollmentDataFromMeasurementUrl(
+ webSourceRegistrationRequest
+ .getSourceParams()
+ .get(0)
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ != null) {
+ enrollmentId =
+ EnrollmentDao.getInstance(sContext)
+ .getEnrollmentDataFromMeasurementUrl(
+ webSourceParams
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ .getEnrollmentId();
+ }
+ return createAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ webSourceParams.getRegistrationUri(),
+ webSourceRegistrationRequest.getWebDestination(),
+ webSourceRegistrationRequest.getAppDestination(),
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + sContext.getPackageName()),
+ null,
+ webSourceRegistrationRequest.getTopOriginUri(),
+ AsyncRegistration.RegistrationType.WEB_SOURCE,
+ Source.SourceType.NAVIGATION,
+ System.currentTimeMillis(),
+ 0,
+ System.currentTimeMillis(),
+ AsyncRegistration.RedirectType.NONE,
+ 0,
+ adIdPermissionGranted);
+ }
+ return null;
+ }
+
+ private static AsyncRegistration createAsyncRegistration(
+ String iD,
+ String enrollmentId,
+ Uri registrationUri,
+ Uri webDestination,
+ Uri osDestination,
+ Uri registrant,
+ Uri verifiedDestination,
+ Uri topOrigin,
+ AsyncRegistration.RegistrationType registrationType,
+ Source.SourceType sourceType,
+ long mRequestTime,
+ long mRetryCount,
+ long mLastProcessingTime,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount,
+ boolean debugKeyAllowed) {
+ return new AsyncRegistration.Builder()
+ .setId(iD)
+ .setEnrollmentId(enrollmentId)
+ .setRegistrationUri(registrationUri)
+ .setWebDestination(webDestination)
+ .setOsDestination(osDestination)
+ .setRegistrant(registrant)
+ .setVerifiedDestination(verifiedDestination)
+ .setTopOrigin(topOrigin)
+ .setType(registrationType.ordinal())
+ .setSourceType(
+ registrationType == AsyncRegistration.RegistrationType.APP_SOURCE
+ || registrationType
+ == AsyncRegistration.RegistrationType.WEB_SOURCE
+ ? sourceType
+ : null)
+ .setRequestTime(mRequestTime)
+ .setRetryCount(mRetryCount)
+ .setLastProcessingTime(mLastProcessingTime)
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(debugKeyAllowed)
+ .build();
+ }
+
+ private WebSourceRegistrationRequest buildWebSourceRegistrationRequest(
+ List<WebSourceParams> sourceParamsList,
+ String topOrigin,
+ Uri appDestination,
+ Uri webDestination) {
+ WebSourceRegistrationRequest.Builder webSourceRegistrationRequestBuilder =
+ new WebSourceRegistrationRequest.Builder(sourceParamsList, Uri.parse(topOrigin))
+ .setAppDestination(appDestination);
+ if (webDestination != null) {
+ webSourceRegistrationRequestBuilder.setWebDestination(webDestination);
+ }
+ return webSourceRegistrationRequestBuilder.build();
+ }
+
+ static Source.SourceType getSourceType(InputEvent inputEvent) {
+ return inputEvent == null ? Source.SourceType.EVENT : Source.SourceType.NAVIGATION;
+ }
+
+ static Map<String, List<String>> getDefaultHeaders() {
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put(
+ "Attribution-Reporting-Register-Source",
+ List.of(
+ "{\n"
+ + "\"destination\": \""
+ + DEFAULT_DESTINATION
+ + "\",\n"
+ + "\"source_event_id\": \""
+ + DEFAULT_EVENT_ID
+ + "\",\n"
+ + "\"priority\": \""
+ + DEFAULT_PRIORITY
+ + "\",\n"
+ + "\"expiry\": "
+ + DEFAULT_EXPIRY
+ + ""
+ + "}\n"));
+ return headers;
+ }
+
+ private static void assertDefaultSourceRegistration(Source result) {
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(DEFAULT_DESTINATION, result.getAppDestination().toString());
+ assertEquals(DEFAULT_EVENT_ID, result.getEventId());
+ assertEquals(DEFAULT_PRIORITY, result.getPriority());
+ assertEquals(
+ result.getEventTime() + TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRY),
+ result.getExpiryTime());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncTriggerFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncTriggerFetcherTest.java
new file mode 100644
index 000000000..e52eae569
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/AsyncTriggerFetcherTest.java
@@ -0,0 +1,2797 @@
+/*
+ * 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.measurement.registration;
+
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATABLE_TRIGGER_DATA;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_EVENT_TRIGGER_DATA;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_REDIRECTS_PER_REGISTRATION;
+import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.adservices.measurement.RegistrationRequest;
+import android.adservices.measurement.WebTriggerParams;
+import android.adservices.measurement.WebTriggerRegistrationRequest;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.enrollment.EnrollmentData;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.Source;
+import com.android.adservices.service.measurement.Trigger;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.net.ssl.HttpsURLConnection;
+/** Unit tests for {@link AsyncTriggerFetcher} */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+public final class AsyncTriggerFetcherTest {
+ private static final String ANDROID_APP_SCHEME = "android-app";
+ private static final String ANDROID_APP_SCHEME_URI_PREFIX = ANDROID_APP_SCHEME + "://";
+ private static final String TRIGGER_URI = "https://foo.com";
+ private static final String ENROLLMENT_ID = "enrollment-id";
+ private static final EnrollmentData ENROLLMENT =
+ new EnrollmentData.Builder().setEnrollmentId("enrollment-id").build();
+ private static final String TOP_ORIGIN = "https://baz.com";
+ private static final long TRIGGER_DATA = 7;
+ private static final long PRIORITY = 1;
+ private static final String LONG_FILTER_STRING = "12345678901234567890123456";
+ private static final String LONG_AGGREGATE_KEY_ID = "12345678901234567890123456";
+ private static final String LONG_AGGREGATE_KEY_PIECE = "0x123456789012345678901234567890123";
+ private static final long DEDUP_KEY = 100;
+ private static final UnsignedLong DEBUG_KEY = new UnsignedLong(34787843L);
+ private static final String DEFAULT_REDIRECT = "https://bar.com";
+ private static final String EVENT_TRIGGERS_1 =
+ "[\n"
+ + "{\n"
+ + " \"trigger_data\": \""
+ + TRIGGER_DATA
+ + "\",\n"
+ + " \"priority\": \""
+ + PRIORITY
+ + "\",\n"
+ + " \"deduplication_key\": \""
+ + DEDUP_KEY
+ + "\",\n"
+ + " \"filters\": {\n"
+ + " \"source_type\": [\"navigation\"],\n"
+ + " \"key_1\": [\"value_1\"] \n"
+ + " }\n"
+ + "}"
+ + "]\n";
+ private static final String LIST_TYPE_REDIRECT_URI = "https://bar.com";
+ private static final String LOCATION_TYPE_REDIRECT_URI = "https://example.com";
+ private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com");
+ private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo2.com");
+ private static final WebTriggerParams TRIGGER_REGISTRATION_1 =
+ new WebTriggerParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
+ private static final WebTriggerParams TRIGGER_REGISTRATION_2 =
+ new WebTriggerParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
+ private static final Context CONTEXT =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ AsyncTriggerFetcher mFetcher;
+
+ @Mock HttpsURLConnection mUrlConnection;
+ @Mock HttpsURLConnection mUrlConnection1;
+ @Mock EnrollmentDao mEnrollmentDao;
+ @Mock Flags mFlags;
+ @Mock AdServicesLogger mLogger;
+
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void setup() {
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
+ mFetcher = spy(new AsyncTriggerFetcher(mEnrollmentDao, mFlags, mLogger));
+ // For convenience, return the same enrollment-ID since we're using many arbitrary
+ // registration URIs and not yet enforcing uniqueness of enrollment.
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(ENROLLMENT);
+ }
+
+ @After
+ public void cleanup() throws InterruptedException {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testBasicTriggerRequest() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ MeasurementRegistrationResponseStats expectedStats =
+ new MeasurementRegistrationResponseStats.Builder(
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS,
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
+ 221)
+ .setAdTechDomain(null)
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ doReturn(5000L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mLogger).logMeasurementRegistrationsResponseSize(eq(expectedStats));
+ }
+
+ // Tests for redirect types
+
+ @Test
+ public void testRedirectType_bothRedirectHeaderTypes_choosesListType() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate both 'list' and 'location' type headers
+ headers.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'none' type redirects were chosen
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LIST_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectHeaderType_choosesLocationType() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'location' type redirects were chosen
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_maxCount_noRedirectReturned()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request,
+ AsyncRegistration.RedirectType.DAISY_CHAIN, MAX_REDIRECTS_PER_REGISTRATION);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'location' type redirect but no uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_count1_redirectReturned() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate only 'location' type header
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(
+ request, AsyncRegistration.RedirectType.DAISY_CHAIN, 1);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'location' type redirect and uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testRedirectType_locationRedirectType_ignoresListType() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(302);
+ Map<String, List<String>> headers = getDefaultHeaders();
+
+ // Populate both 'list' and 'location' type headers
+ headers.put("Attribution-Reporting-Redirect", List.of(LIST_TYPE_REDIRECT_URI));
+ headers.put("Location", List.of(LOCATION_TYPE_REDIRECT_URI));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headers);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(
+ request, AsyncRegistration.RedirectType.DAISY_CHAIN, 1);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertSourceRegistration(asyncRegistration, result);
+
+ // Assert 'location' type redirect and uri
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ assertEquals(1, asyncRedirect.getRedirects().size());
+ assertEquals(LOCATION_TYPE_REDIRECT_URI, asyncRedirect.getRedirects().get(0).toString());
+
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ // End tests for redirect types
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_tooManyEntries() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder tooManyEntries = new StringBuilder("[");
+ for (int i = 0; i < MAX_ATTRIBUTION_EVENT_TRIGGER_DATA + 1; i++) {
+ tooManyEntries.append("{\"trigger_data\": \"2\",\"priority\": \"101\"}");
+ }
+ tooManyEntries.append("]");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + tooManyEntries + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_triggerData_negative() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData = "[{\"trigger_data\":\"-2\",\"priority\":\"101\"}]";
+ String expectedResult = "[{\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_triggerData_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"18446744073709551616\"," + "\"priority\":\"101\"}]";
+ String expectedResult = "[{\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_triggerData_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData = "[{\"trigger_data\":\"101z\",\"priority\":\"101\"}]";
+ String expectedResult = "[{\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_triggerData_uses64thBit() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"18446744073709551615\"," + "\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(eventTriggerData, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_priority_negative() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData = "[{\"trigger_data\":\"2\",\"priority\":\"-101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(eventTriggerData, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_priority_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":" + "\"18446744073709551615\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_priority_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData = "[{\"trigger_data\":\"2\",\"priority\":\"a101\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_deduplicationKey_negative() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"deduplication_key\":\"-34\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\",\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_deduplicationKey_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"deduplication_key\":\"18446744073709551616\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\",\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_deduplicationKey_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"deduplication_key\":\"145l\"}]";
+ String expectedResult = "[{\"trigger_data\":\"2\",\"priority\":\"101\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(expectedResult, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_deduplicationKey_uses64thBit()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"deduplication_key\":\"18446744073709551615\"}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(eventTriggerData, result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"\"\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_filters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\"," + "\"filters\":" + filters + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"2345\"], \"\"\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder notFilters = new StringBuilder("{");
+ notFilters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ notFilters.append("}");
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder notFilters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ notFilters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ notFilters.append("]}");
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_eventTriggerData_notFilters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ String eventTriggerData =
+ "[{\"trigger_data\":\"2\",\"priority\":\"101\","
+ + "\"not_filters\":"
+ + notFilters
+ + "}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + eventTriggerData + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"\"\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_filters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\":" + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"2345\"], \"\"\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_tooManyFilters() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder notFilters = new StringBuilder("{");
+ notFilters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ notFilters.append("}");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_tooManyValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder notFilters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ notFilters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ notFilters.append("]}");
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequest_notFilters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String notFilters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"not_filters\":" + notFilters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testBasicTriggerRequest_failsWhenNotEnrolled() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(
+ AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mFetcher, never()).openUrl(any());
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithDebugKey() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"debug_key\": \""
+ + DEBUG_KEY
+ + "\""
+ + "}"));
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertEquals(DEBUG_KEY, result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequest_debugKey_negative() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{\"event_trigger_data\":"
+ + EVENT_TRIGGERS_1
+ + ",\"debug_key\":\"-376\"}"));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequest_debugKey_tooLarge() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{\"event_trigger_data\":"
+ + EVENT_TRIGGERS_1
+ + ",\"debug_key\":\"18446744073709551616\"}"));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequest_debugKey_notAnInt() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{\"event_trigger_data\":"
+ + EVENT_TRIGGERS_1
+ + ",\"debug_key\":\"65g43\"}"));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequest_debugKey_uses64thBit() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{\"event_trigger_data\":"
+ + EVENT_TRIGGERS_1
+ + ",\"debug_key\":\"18446744073709551615\"}"));
+
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertEquals(new UnsignedLong(-1L), result.getDebugKey());
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithoutAdIdPermission() throws Exception {
+ RegistrationRequest request = buildRequestWithoutAdIdPermission(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"debug_key\": \""
+ + DEBUG_KEY
+ + "\""
+ + "}"));
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertNull(result.getDebugKey());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadTriggerUrl() throws Exception {
+ RegistrationRequest request = buildRequest("bad-schema://foo.com");
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ }
+
+ @Test
+ public void testBadTriggerConnection() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doThrow(new IOException("Bad internet things"))
+ .when(mFetcher)
+ .openUrl(new URL(TRIGGER_URI));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, never()).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBadRequestReturnFailure() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(400);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(
+ AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestMinimumFields() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\": " + "[{}]" + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals("[{}]", result.getEventTriggers());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testNotOverHttps() throws Exception {
+ RegistrationRequest request = buildRequest("http://foo.com");
+ // Non-https should fail.
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ }
+
+ @Test
+ public void testFirst200Next500_ignoreFailureReturnSuccess() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200).thenReturn(500);
+ Map<String, List<String>> headersFirstRequest = new HashMap<>();
+ headersFirstRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}"));
+ headersFirstRequest.put("Attribution-Reporting-Redirect", List.of(DEFAULT_REDIRECT));
+ when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testMissingHeaderButWithRedirect() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(Map.of("Attribution-Reporting-Redirect", List.of(DEFAULT_REDIRECT)))
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithAggregateTriggerData() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRegistration asyncRegistration = appTriggerRegistrationRequest(request);
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(
+ asyncRegistration.getTopOrigin().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(
+ new JSONArray(aggregatable_trigger_data).toString(),
+ result.getAggregateTriggerData());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithAggregateTriggerData_rejectsTooManyDataKeys()
+ throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("[");
+ for (int i = 0; i < 51; i++) {
+ tooManyKeys.append(
+ String.format(
+ "{\"key_piece\": \"0x15%1$s\",\"source_keys\":[\"campaign-%1$s\"]}",
+ i));
+ }
+ tooManyKeys.append("]");
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + tooManyKeys
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithAggregateValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_values = "{\"campaignCounts\":32768,\"geoValue\":1644}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_values\": "
+ + aggregatable_values
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(new JSONObject(aggregatable_values).toString(), result.getAggregateValues());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void testBasicTriggerRequestWithAggregateTriggerData_rejectsTooManyValueKeys()
+ throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("{");
+ int i = 0;
+ for (; i < 50; i++) {
+ tooManyKeys.append(String.format("\"key-%s\": 12345,", i));
+ }
+ tooManyKeys.append(String.format("\"key-%s\": 12345}", i));
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"aggregatable_values\": " + tooManyKeys + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.PARSING_ERROR, asyncFetchStatus.getStatus());
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchTrigger_withReportingFilters_success() throws IOException, JSONException {
+ // Setup
+ String filters =
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}";
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"filters\": " + filters + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(new JSONObject(filters).toString(), result.getFilters());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchWebTriggers_basic_success() throws IOException, JSONException {
+ // Setup
+ WebTriggerRegistrationRequest request =
+ buildWebTriggerRegistrationRequest(
+ Arrays.asList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
+ doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection1.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"debug_key\": \""
+ + DEBUG_KEY
+ + "\""
+ + "}"));
+ when(mUrlConnection1.getHeaderFields()).thenReturn(headersRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ webTriggerRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection1).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchWebTriggers_withExtendedHeaders_success() throws IOException, JSONException {
+ // Setup
+ WebTriggerRegistrationRequest request =
+ buildWebTriggerRegistrationRequest(
+ Collections.singletonList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ String aggregatableValues = "{\"campaignCounts\":32768,\"geoValue\":1644}";
+ String filters =
+ "{\n"
+ + " \"key_1\": [\"value_1\", \"value_2\"],\n"
+ + " \"key_2\": [\"value_1\", \"value_2\"]\n"
+ + "}";
+ String aggregatableTriggerData =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"filters\": "
+ + filters
+ + ", \"aggregatable_values\": "
+ + aggregatableValues
+ + ", \"aggregatable_trigger_data\": "
+ + aggregatableTriggerData
+ + "}")));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ webTriggerRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ assertEquals(new JSONArray(aggregatableTriggerData).toString(),
+ result.getAggregateTriggerData());
+ assertEquals(new JSONObject(aggregatableValues).toString(), result.getAggregateValues());
+ assertEquals(new JSONObject(filters).toString(), result.getFilters());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+
+ @Test
+ public void fetchWebTriggers_withRedirects_ignoresRedirects()
+ throws IOException, JSONException {
+ // Setup
+ WebTriggerRegistrationRequest request =
+ buildWebTriggerRegistrationRequest(
+ Collections.singletonList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\": " + EVENT_TRIGGERS_1 + "}"),
+ "Attribution-Reporting-Redirect",
+ List.of(LIST_TYPE_REDIRECT_URI),
+ "Location",
+ List.of(LOCATION_TYPE_REDIRECT_URI)));
+
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ webTriggerRegistrationRequest(request, true), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertEquals(AsyncFetchStatus.ResponseStatus.SUCCESS, asyncFetchStatus.getStatus());
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_tooManyEntries() throws Exception {
+ StringBuilder tooManyEntries = new StringBuilder("[");
+ for (int i = 0; i < MAX_AGGREGATABLE_TRIGGER_DATA + 1; i++) {
+ tooManyEntries.append(
+ String.format(
+ "{\"key_piece\": \"0x15%1$s\",\"source_keys\":[\"campaign-%1$s\"]}",
+ i));
+ }
+ tooManyEntries.append("]");
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + tooManyEntries
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_invalidKeyPiece_missingPrefix()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_invalidKeyPiece_tooLong()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\""
+ + LONG_AGGREGATE_KEY_PIECE
+ + "\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_sourceKeys_notAnArray()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":{\"campaignCounts\": true},"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_sourceKeys_tooManyKeys()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder tooManyKeys = new StringBuilder("[");
+ tooManyKeys.append(
+ IntStream.range(0, MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1)
+ .mapToObj(i -> "aggregate-key-" + i)
+ .collect(Collectors.joining(",")));
+ tooManyKeys.append("]");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\": "
+ + tooManyKeys
+ + ","
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_sourceKeys_invalidKeyId()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\", \""
+ + LONG_AGGREGATE_KEY_ID
+ + "\"],"
+ + "\"filters\":"
+ + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_filters_tooManyFilters()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\": "
+ + filters
+ + ","
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_filters_keyTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\": "
+ + filters
+ + ","
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_filters_tooManyValues()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\": "
+ + filters
+ + ","
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_filters_valueTooLong() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"filters\": "
+ + filters
+ + ","
+ + "\"not_filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_notFilters_tooManyFilters()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters = new StringBuilder("{");
+ filters.append(
+ IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
+ .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
+ .collect(Collectors.joining(",")));
+ filters.append("}");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"not_filters\": "
+ + filters
+ + ","
+ + "\"filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_notFilters_keyTooLong()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"not_filters\": "
+ + filters
+ + ","
+ + "\"filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_notFilters_tooManyValues()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ StringBuilder filters =
+ new StringBuilder(
+ "{"
+ + "\"filter-string-1\": [\"filter-value-1\"],"
+ + "\"filter-string-2\": [");
+ filters.append(
+ IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
+ .mapToObj(i -> "\"filter-value-" + i + "\"")
+ .collect(Collectors.joining(",")));
+ filters.append("]}");
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"not_filters\": "
+ + filters
+ + ","
+ + "\"filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregateTriggerData_notFilters_valueTooLong()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String filters =
+ "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
+ String aggregatable_trigger_data =
+ "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
+ + "\"not_filters\": "
+ + filters
+ + ","
+ + "\"filters\":{\"product\":[\"1\"]}},"
+ + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_trigger_data\": "
+ + aggregatable_trigger_data
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testBasicTriggerRequestWithAggregatableValues() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_values = "{\"campaignCounts\":32768,\"geoValue\":1644}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_values\": "
+ + aggregatable_values
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONObject(aggregatable_values).toString(), result.getAggregateValues());
+ verify(mUrlConnection).setRequestMethod("POST");
+ }
+ @Test
+ public void testTriggerRequestWithAggregatableValues_invalidJson() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_values = "{\"campaignCounts\":32768\"geoValue\":1644}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_values\": "
+ + aggregatable_values
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregatableValues_tooManyKeys() throws Exception {
+ StringBuilder tooManyKeys = new StringBuilder("{");
+ tooManyKeys.append(
+ IntStream.range(0, MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1)
+ .mapToObj(i -> String.format("\"key-%s\": 12345,", i))
+ .collect(Collectors.joining(",")));
+ tooManyKeys.append("}");
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"aggregatable_values\": " + tooManyKeys + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void testTriggerRequestWithAggregatableValues_invalidKeyId() throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ String aggregatable_values =
+ "{\"campaignCounts\":32768, \"" + LONG_AGGREGATE_KEY_ID + "\":1644}";
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"aggregatable_values\": "
+ + aggregatable_values
+ + "}")));
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertFalse(fetch.isPresent());
+ verify(mUrlConnection, times(1)).setRequestMethod("POST");
+ verify(mFetcher, times(1)).openUrl(any());
+ }
+ @Test
+ public void fetchWebTriggerSuccessWithoutAdIdPermission() throws IOException, JSONException {
+ // Setup
+ WebTriggerRegistrationRequest request =
+ buildWebTriggerRegistrationRequest(
+ Arrays.asList(TRIGGER_REGISTRATION_1, TRIGGER_REGISTRATION_2), TOP_ORIGIN);
+ doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
+ when(mUrlConnection1.getResponseCode()).thenReturn(200);
+ Map<String, List<String>> headersRequest = new HashMap<>();
+ headersRequest.put(
+ "Attribution-Reporting-Register-Trigger",
+ List.of(
+ "{"
+ + "\"event_trigger_data\": "
+ + EVENT_TRIGGERS_1
+ + ", \"debug_key\": \""
+ + DEBUG_KEY
+ + "\""
+ + "}"));
+ when(mUrlConnection1.getHeaderFields()).thenReturn(headersRequest);
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch = mFetcher.fetchTrigger(
+ webTriggerRegistrationRequest(request, false), asyncFetchStatus, asyncRedirect);
+ // Assertion
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(TOP_ORIGIN, result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection1).setRequestMethod("POST");
+ }
+ @Test
+ public void basicTriggerRequest_headersMoreThanMaxResponseSize_emitsMetricsWithAdTechDomain()
+ throws Exception {
+ RegistrationRequest request = buildRequest(TRIGGER_URI);
+ MeasurementRegistrationResponseStats expectedStats =
+ new MeasurementRegistrationResponseStats.Builder(
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS,
+ AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
+ 221)
+ .setAdTechDomain(TRIGGER_URI)
+ .build();
+ doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
+ when(mUrlConnection.getResponseCode()).thenReturn(200);
+ when(mUrlConnection.getHeaderFields())
+ .thenReturn(
+ Map.of(
+ "Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}")));
+ doReturn(5L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ // Execution
+ Optional<Trigger> fetch =
+ mFetcher.fetchTrigger(
+ appTriggerRegistrationRequest(request), asyncFetchStatus, asyncRedirect);
+ assertTrue(fetch.isPresent());
+ Trigger result = fetch.get();
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ verify(mUrlConnection).setRequestMethod("POST");
+ verify(mLogger).logMeasurementRegistrationsResponseSize(eq(expectedStats));
+ }
+ private RegistrationRequest buildRequest(String triggerUri) {
+ return new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
+ .setRegistrationUri(Uri.parse(triggerUri))
+ .setPackageName(CONTEXT.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(true)
+ .build();
+ }
+ private RegistrationRequest buildRequestWithoutAdIdPermission(String triggerUri) {
+ return new RegistrationRequest.Builder()
+ .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
+ .setRegistrationUri(Uri.parse(triggerUri))
+ .setPackageName(CONTEXT.getAttributionSource().getPackageName())
+ .setAdIdPermissionGranted(false)
+ .build();
+ }
+ private WebTriggerRegistrationRequest buildWebTriggerRegistrationRequest(
+ List<WebTriggerParams> triggerParams, String topOrigin) {
+ return new WebTriggerRegistrationRequest.Builder(triggerParams, Uri.parse(topOrigin))
+ .build();
+ }
+
+ public static AsyncRegistration appTriggerRegistrationRequest(
+ RegistrationRequest registrationRequest) {
+ return appTriggerRegistrationRequest(registrationRequest,
+ AsyncRegistration.RedirectType.ANY, 0);
+ }
+
+ public static AsyncRegistration appTriggerRegistrationRequest(
+ RegistrationRequest registrationRequest,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount) {
+ // Necessary for testing
+ String enrollmentId = "";
+ if (EnrollmentDao.getInstance(CONTEXT)
+ .getEnrollmentDataFromMeasurementUrl(
+ registrationRequest
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ != null) {
+ enrollmentId =
+ EnrollmentDao.getInstance(CONTEXT)
+ .getEnrollmentDataFromMeasurementUrl(
+ registrationRequest
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ .getEnrollmentId();
+ }
+ return createAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ registrationRequest.getRegistrationUri(),
+ null,
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + CONTEXT.getPackageName()),
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + CONTEXT.getPackageName()),
+ registrationRequest.getRegistrationType() == RegistrationRequest.REGISTER_SOURCE
+ ? AsyncRegistration.RegistrationType.APP_SOURCE
+ : AsyncRegistration.RegistrationType.APP_TRIGGER,
+ null,
+ System.currentTimeMillis(),
+ 0,
+ System.currentTimeMillis(),
+ redirectType,
+ redirectCount,
+ registrationRequest.isAdIdPermissionGranted());
+ }
+
+ private static AsyncRegistration webTriggerRegistrationRequest(
+ WebTriggerRegistrationRequest webTriggerRegistrationRequest,
+ boolean adIdPermissionGranted) {
+ if (webTriggerRegistrationRequest.getTriggerParams().size() > 0) {
+ WebTriggerParams webTriggerParams =
+ webTriggerRegistrationRequest.getTriggerParams().get(0);
+ // Necessary for testing
+ String enrollmentId = "";
+ if (EnrollmentDao.getInstance(CONTEXT)
+ .getEnrollmentDataFromMeasurementUrl(
+ webTriggerRegistrationRequest
+ .getTriggerParams()
+ .get(0)
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ != null) {
+ enrollmentId =
+ EnrollmentDao.getInstance(CONTEXT)
+ .getEnrollmentDataFromMeasurementUrl(
+ webTriggerParams
+ .getRegistrationUri()
+ .buildUpon()
+ .clearQuery()
+ .build())
+ .getEnrollmentId();
+ }
+ return createAsyncRegistration(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ webTriggerParams.getRegistrationUri(),
+ null,
+ null,
+ Uri.parse(ANDROID_APP_SCHEME_URI_PREFIX + CONTEXT.getPackageName()),
+ null,
+ webTriggerRegistrationRequest.getDestination(),
+ AsyncRegistration.RegistrationType.WEB_TRIGGER,
+ null,
+ System.currentTimeMillis(),
+ 0,
+ System.currentTimeMillis(),
+ AsyncRegistration.RedirectType.NONE,
+ 0,
+ adIdPermissionGranted);
+ }
+ return null;
+ }
+ private static AsyncRegistration createAsyncRegistration(
+ String iD,
+ String enrollmentId,
+ Uri registrationUri,
+ Uri webDestination,
+ Uri osDestination,
+ Uri registrant,
+ Uri verifiedDestination,
+ Uri topOrigin,
+ AsyncRegistration.RegistrationType registrationType,
+ Source.SourceType sourceType,
+ long mRequestTime,
+ long mRetryCount,
+ long mLastProcessingTime,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount,
+ boolean debugKeyAllowed) {
+ return new AsyncRegistration.Builder()
+ .setId(iD)
+ .setEnrollmentId(enrollmentId)
+ .setRegistrationUri(registrationUri)
+ .setWebDestination(webDestination)
+ .setOsDestination(osDestination)
+ .setRegistrant(registrant)
+ .setVerifiedDestination(verifiedDestination)
+ .setTopOrigin(topOrigin)
+ .setType(registrationType.ordinal())
+ .setSourceType(sourceType)
+ .setRequestTime(mRequestTime)
+ .setRetryCount(mRetryCount)
+ .setLastProcessingTime(mLastProcessingTime)
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(debugKeyAllowed)
+ .build();
+ }
+
+ private static Map<String, List<String>> getDefaultHeaders() {
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put("Attribution-Reporting-Register-Trigger",
+ List.of("{" + "\"event_trigger_data\":" + EVENT_TRIGGERS_1 + "}"));
+ return headers;
+ }
+
+ private static void assertSourceRegistration(AsyncRegistration asyncRegistration,
+ Trigger result) throws JSONException {
+ assertEquals(
+ asyncRegistration.getRegistrant().toString(),
+ result.getAttributionDestination().toString());
+ assertEquals(ENROLLMENT_ID, result.getEnrollmentId());
+ assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.getEventTriggers());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java
index 4a76028b7..07af7b36a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/FetcherUtilTest.java
@@ -32,6 +32,8 @@ import android.net.Uri;
import androidx.test.filters.SmallTest;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.measurement.AsyncRegistration;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
@@ -82,27 +84,49 @@ public final class FetcherUtilTest {
}
@Test
- public void testParseRedirectsNothingInitial() {
- List<Uri> redirs = FetcherUtil.parseRedirects(Map.of());
- assertEquals(0, redirs.size());
+ public void parseRedirects_noRedirectHeaders_returnsEmpty() {
+ AsyncRedirect asyncRedirect = FetcherUtil.parseRedirects(
+ Map.of(), AsyncRegistration.RedirectType.ANY);
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ assertEquals(0, asyncRedirect.getRedirects().size());
}
@Test
- public void testParseRedirectsARR() {
- List<Uri> redirs =
- FetcherUtil.parseRedirects(
- Map.of("Attribution-Reporting-Redirect", List.of("foo.com", "bar.com")));
- assertEquals(2, redirs.size());
- assertEquals(Uri.parse("foo.com"), redirs.get(0));
- assertEquals(Uri.parse("bar.com"), redirs.get(1));
+ public void parseRedirects_bothHeaderTypes_choosesListType() {
+ AsyncRedirect asyncRedirect = FetcherUtil.parseRedirects(
+ Map.of(
+ "Attribution-Reporting-Redirect", List.of("foo.test", "bar.test"),
+ "Location", List.of("baz.test")),
+ AsyncRegistration.RedirectType.ANY);
+ assertEquals(AsyncRegistration.RedirectType.NONE, asyncRedirect.getRedirectType());
+ List<Uri> redirects = asyncRedirect.getRedirects();
+ assertEquals(2, redirects.size());
+ assertEquals(Uri.parse("foo.test"), redirects.get(0));
+ assertEquals(Uri.parse("bar.test"), redirects.get(1));
}
@Test
- public void testParseRedirectsSingleElementARR() {
- List<Uri> redirs =
- FetcherUtil.parseRedirects(
- Map.of("Attribution-Reporting-Redirect", List.of("foo.com")));
- assertEquals(1, redirs.size());
+ public void parseRedirects_locationHeaderOnly_choosesLocationType() {
+ AsyncRedirect asyncRedirect = FetcherUtil.parseRedirects(
+ Map.of("Location", List.of("foo.test")),
+ AsyncRegistration.RedirectType.ANY);
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ List<Uri> redirects = asyncRedirect.getRedirects();
+ assertEquals(1, redirects.size());
+ assertEquals(Uri.parse("foo.test"), redirects.get(0));
+ }
+
+ @Test
+ public void parseRedirects_bothHeaderTypes_providedLocationType_choosesLocationType() {
+ AsyncRedirect asyncRedirect = FetcherUtil.parseRedirects(
+ Map.of(
+ "Attribution-Reporting-Redirect", List.of("foo.test", "bar.test"),
+ "Location", List.of("baz.test")),
+ AsyncRegistration.RedirectType.DAISY_CHAIN);
+ assertEquals(AsyncRegistration.RedirectType.DAISY_CHAIN, asyncRedirect.getRedirectType());
+ List<Uri> redirects = asyncRedirect.getRedirects();
+ assertEquals(1, redirects.size());
+ assertEquals(Uri.parse("baz.test"), redirects.get(0));
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceFetcherTest.java
deleted file mode 100644
index fbc3c935d..000000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceFetcherTest.java
+++ /dev/null
@@ -1,1937 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
-import static com.android.adservices.service.measurement.PrivacyParams.MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.adservices.measurement.RegistrationRequest;
-import android.adservices.measurement.WebSourceParams;
-import android.adservices.measurement.WebSourceRegistrationRequest;
-import android.content.Context;
-import android.net.Uri;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.adservices.data.enrollment.EnrollmentDao;
-import com.android.adservices.service.Flags;
-import com.android.adservices.service.enrollment.EnrollmentData;
-import com.android.adservices.service.stats.AdServicesLogger;
-import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import javax.net.ssl.HttpsURLConnection;
-
-/** Unit tests for {@link SourceFetcher} */
-@RunWith(MockitoJUnitRunner.class)
-@SmallTest
-public final class SourceFetcherTest {
- private static final String DEFAULT_REGISTRATION = "https://foo.com";
- private static final String ENROLLMENT_ID = "enrollment-id";
- private static final EnrollmentData ENROLLMENT = new EnrollmentData.Builder()
- .setEnrollmentId("enrollment-id")
- .build();
- private static final String DEFAULT_TOP_ORIGIN = "https://baz.com";
- private static final String DEFAULT_DESTINATION = "android-app://com.myapps";
- private static final String DEFAULT_DESTINATION_WITHOUT_SCHEME = "com.myapps";
- private static final long DEFAULT_PRIORITY = 123;
- private static final long DEFAULT_EXPIRY = 456789;
- private static final long DEFAULT_EVENT_ID = 987654321;
- private static final long EVENT_ID_1 = 987654321;
- private static final long EVENT_ID_2 = 987654322;
- private static final Long DEBUG_KEY = 823523783L;
- private static final String ALT_REGISTRATION = "https://bar.com";
- private static final String ALT_DESTINATION = "android-app://com.yourapps";
- private static final long ALT_PRIORITY = 321;
- private static final long ALT_EVENT_ID = 123456789;
- private static final long ALT_EXPIRY = 456790;
- private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com");
- private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo2.com");
- private static final Uri OS_DESTINATION = Uri.parse("android-app://com.os-destination");
- private static final Uri OS_DESTINATION_WITH_PATH =
- Uri.parse("android-app://com.os-destination/my/path");
- private static final Uri WEB_DESTINATION = Uri.parse("https://web-destination.com");
- private static final Uri WEB_DESTINATION_WITH_SUBDOMAIN =
- Uri.parse("https://subdomain.web-destination.com");
- private static final String LONG_FILTER_STRING = "12345678901234567890123456";
- private static final String LONG_AGGREGATE_KEY_ID = "12345678901234567890123456";
- private static final String LONG_AGGREGATE_KEY_PIECE = "0x123456789012345678901234567890123";
- private static final WebSourceParams SOURCE_REGISTRATION_1 =
- new WebSourceParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
- private static final WebSourceParams SOURCE_REGISTRATION_2 =
- new WebSourceParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
-
- private static final Context sContext =
- InstrumentationRegistry.getInstrumentation().getContext();
-
- SourceFetcher mFetcher;
- @Mock HttpsURLConnection mUrlConnection;
-
- @Mock HttpsURLConnection mUrlConnection1;
- @Mock HttpsURLConnection mUrlConnection2;
- @Mock EnrollmentDao mEnrollmentDao;
- @Mock Flags mFlags;
- @Mock AdServicesLogger mLogger;
-
- @Before
- public void setup() {
- mFetcher = spy(new SourceFetcher(mEnrollmentDao, mFlags, mLogger));
- // For convenience, return the same enrollment-ID since we're using many arbitrary
- // registration URIs and not yet enforcing uniqueness of enrollment.
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(ENROLLMENT);
- }
-
- @Test
- public void testBasicSourceRequest() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- doReturn(5000L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + " \"priority\": \""
- + DEFAULT_PRIORITY
- + "\",\n"
- + " \"expiry\": \""
- + DEFAULT_EXPIRY
- + "\",\n"
- + " \"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_PRIORITY, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(DEBUG_KEY, result.get(0).getDebugKey());
- verify(mLogger)
- .logMeasurementRegistrationsResponseSize(
- eq(
- new MeasurementRegistrationResponseStats.Builder(
- AD_SERVICES_MEASUREMENT_REGISTRATIONS,
- AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
- 190)
- .setAdTechDomain(null)
- .build()));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicSourceRequest_failsWhenNotEnrolled() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + " \"priority\": \""
- + DEFAULT_PRIORITY
- + "\",\n"
- + " \"expiry\": \""
- + DEFAULT_EXPIRY
- + "\",\n"
- + " \"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mFetcher, never()).openUrl(any());
- }
-
- @Test
- public void testBasicSourceRequestWithoutAdIdPermission() throws Exception {
- RegistrationRequest request =
- buildRequestWithoutAdIdPermission(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + " \"priority\": \""
- + DEFAULT_PRIORITY
- + "\",\n"
- + " \"expiry\": \""
- + DEFAULT_EXPIRY
- + "\",\n"
- + " \"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_PRIORITY, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertNull(result.get(0).getDebugKey());
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequestWithPostInstallAttributes() throws Exception {
- RegistrationRequest request =
- new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse("https://foo.com"))
- .setTopOriginUri(Uri.parse("https://baz.com"))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"install_attribution_window\": \"272800\",\n"
- + " \"post_install_exclusivity_window\": \"987654\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourcePriority());
- assertEquals(456789, result.get(0).getExpiry());
- assertEquals(987654321, result.get(0).getSourceEventId());
- assertEquals(272800, result.get(0).getInstallAttributionWindow());
- assertEquals(987654L, result.get(0).getInstallCooldownWindow());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequestWithPostInstallAttributesReceivedAsNull() throws Exception {
- RegistrationRequest request =
- new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse("https://foo.com"))
- .setTopOriginUri(Uri.parse("https://baz.com"))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"install_attribution_window\": null,\n"
- + " \"post_install_exclusivity_window\": null\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourcePriority());
- assertEquals(456789, result.get(0).getExpiry());
- assertEquals(987654321, result.get(0).getSourceEventId());
- // fallback to default value - 30 days
- assertEquals(2592000L, result.get(0).getInstallAttributionWindow());
- // fallback to default value - 0 days
- assertEquals(0L, result.get(0).getInstallCooldownWindow());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequestWithInstallAttributesOutofBounds() throws IOException {
- RegistrationRequest request =
- new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse("https://foo.com"))
- .setTopOriginUri(Uri.parse("https://baz.com"))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- // Min value of attribution is 2 days or 172800 seconds
- + " \"install_attribution_window\": \"172700\",\n"
- // Max value of cooldown is 30 days or 2592000 seconds
- + " \"post_install_exclusivity_window\": \"9876543210\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourcePriority());
- assertEquals(456789, result.get(0).getExpiry());
- assertEquals(987654321, result.get(0).getSourceEventId());
- // Adjusted to minimum allowed value
- assertEquals(172800, result.get(0).getInstallAttributionWindow());
- // Adjusted to maximum allowed value
- assertEquals(2592000L, result.get(0).getInstallCooldownWindow());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBadSourceUrl() {
- RegistrationRequest request = buildRequest(
- /* registrationUri = */ "bad-schema://foo.com", DEFAULT_TOP_ORIGIN);
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- }
-
- @Test
- public void testBadSourceConnection() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doThrow(new IOException("Bad internet things")).when(mFetcher).openUrl(
- new URL(DEFAULT_REGISTRATION)
- );
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- }
-
- @Test
- public void testBadSourceJson_missingSourceEventId() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\"")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBadSourceJson_missingHeader() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields()).thenReturn(Collections.emptyMap());
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBadSourceJson_missingDestination() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\"")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicSourceRequestMinimumFields() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(
- MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, result.get(0).getExpiry());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicSourceRequestMinimumFieldsAndRestNull() throws Exception {
- RegistrationRequest request =
- new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse("https://foo.com"))
- .setTopOriginUri(Uri.parse("https://baz.com"))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL("https://foo.com"));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"android-app://com.myapps\",\n"
- + "\"source_event_id\": \"123\",\n"
- + "\"priority\": null,\n"
- + "\"expiry\": null\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS,
- result.get(0).getExpiry());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicSourceRequestWithExpiryLessThan2Days() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"expiry\": 1"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(
- MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, result.get(0).getExpiry());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicSourceRequestWithExpiryMoreThan30Days() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"expiry\": 2678400"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(
- MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, result.get(0).getExpiry());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testNotOverHttps() throws Exception {
- RegistrationRequest request = buildRequest("http://foo.com", DEFAULT_TOP_ORIGIN);
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mFetcher, never()).openUrl(any());
- }
-
- @Test
- public void testFirst200Next500_ignoreFailureReturnSuccess() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200).thenReturn(500);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"expiry\": " + DEFAULT_EXPIRY + ""
- + "}\n"));
- headersFirstRequest.put("Attribution-Reporting-Redirect", List.of("https://bar.com"));
-
- Map<String, List<String>> headersSecondRequest = new HashMap<>();
- headersSecondRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + ALT_EVENT_ID + "\",\n"
- + "\"expiry\": " + ALT_EXPIRY + ""
- + "}\n"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest).thenReturn(
- headersSecondRequest);
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(0, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- verify(mUrlConnection, times(2)).setRequestMethod("POST");
- }
-
- @Test
- public void testFailedParsingButValidRedirect_returnFailure() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{}"));
- headersFirstRequest.put("Attribution-Reporting-Redirect", List.of("https://bar.com"));
-
- Map<String, List<String>> headersSecondRequest = new HashMap<>();
- headersSecondRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + ALT_EVENT_ID + "\",\n"
- + "\"expiry\": " + ALT_EXPIRY + ""
- + "}\n"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest).thenReturn(
- headersSecondRequest);
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testRedirectDifferentDestination_keepAllReturnSuccess() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"priority\": \"" + DEFAULT_PRIORITY + "\",\n"
- + "\"expiry\": " + DEFAULT_EXPIRY + ""
- + "}\n"));
- headersFirstRequest.put("Attribution-Reporting-Redirect", List.of(ALT_REGISTRATION));
-
- Map<String, List<String>> headersSecondRequest = new HashMap<>();
- headersSecondRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + ALT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + ALT_EVENT_ID + "\",\n"
- + "\"priority\": \"" + ALT_PRIORITY + "\",\n"
- + "\"expiry\": " + ALT_EXPIRY + ""
- + "}\n"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest).thenReturn(
- headersSecondRequest);
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(DEFAULT_PRIORITY, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(1).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(1).getEnrollmentId());
- assertEquals(ALT_DESTINATION, result.get(1).getAppDestination().toString());
- assertEquals(ALT_EVENT_ID, result.get(1).getSourceEventId());
- assertEquals(ALT_PRIORITY, result.get(1).getSourcePriority());
- assertEquals(ALT_EXPIRY, result.get(1).getExpiry());
- verify(mUrlConnection, times(2)).setRequestMethod("POST");
- }
-
- @Test
- public void testRedirectSameDestination_returnAllSuccessfully() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + DEFAULT_EVENT_ID + "\",\n"
- + "\"priority\": 999,\n"
- + "\"expiry\": " + DEFAULT_EXPIRY + ""
- + "}\n"));
- headersFirstRequest.put("Attribution-Reporting-Redirect",
- List.of(ALT_REGISTRATION, ALT_REGISTRATION));
-
- Map<String, List<String>> headersSecondRequest = new HashMap<>();
- headersSecondRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": \"" + ALT_EVENT_ID + "\",\n"
- + "\"priority\": 888,\n"
- + "\"expiry\": " + ALT_EXPIRY + ""
- + "}\n"));
-
- Map<String, List<String>> headersThirdRequest = new HashMap<>();
- headersThirdRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + "\"source_event_id\": 777,\n"
- + "\"priority\": 777,\n"
- + "\"expiry\": 456791"
- + "}\n"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest).thenReturn(
- headersSecondRequest, headersThirdRequest);
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(3, result.size());
- result.sort((o1, o2) -> (int) (o2.getSourcePriority() - o1.getSourcePriority()));
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(0).getAppDestination().toString());
- assertEquals(DEFAULT_EVENT_ID, result.get(0).getSourceEventId());
- assertEquals(999, result.get(0).getSourcePriority());
- assertEquals(DEFAULT_EXPIRY, result.get(0).getExpiry());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(1).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(1).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(1).getAppDestination().toString());
- assertEquals(ALT_EVENT_ID, result.get(1).getSourceEventId());
- assertEquals(888, result.get(1).getSourcePriority());
- assertEquals(ALT_EXPIRY, result.get(1).getExpiry());
- assertEquals(DEFAULT_TOP_ORIGIN, result.get(2).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(2).getEnrollmentId());
- assertEquals(DEFAULT_DESTINATION, result.get(2).getAppDestination().toString());
- assertEquals(777, result.get(2).getSourceEventId());
- assertEquals(777, result.get(2).getSourcePriority());
- assertEquals(456791, result.get(2).getExpiry());
- verify(mUrlConnection, times(3)).setRequestMethod("POST");
- }
-
- @Test
- public void testRedirectSameDestinationWithDelay_returnAllSuccessfully() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersFirstRequest =
- buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 1, DEFAULT_EXPIRY);
- headersFirstRequest.put("Attribution-Reporting-Redirect",
- List.of("https://bar2.com",
- "https://bar3.com",
- "https://bar4.com",
- "https://bar5.com",
- "https://bar6.com"));
-
- Map<String, List<String>> headersSecondRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 2, DEFAULT_EXPIRY);
-
- Map<String, List<String>> headersThirdRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 3, DEFAULT_EXPIRY);
-
- Map<String, List<String>> headersFourthRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 4, DEFAULT_EXPIRY);
-
- Map<String, List<String>> headersFifthRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 5, DEFAULT_EXPIRY);
-
- Map<String, List<String>> headersSixthRequest = buildRegisterSourceDefaultHeader(
- DEFAULT_DESTINATION, DEFAULT_EVENT_ID, /* priority = */ 6, DEFAULT_EXPIRY);
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest)
- .thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersSecondRequest;
- }).thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersThirdRequest;
- }).thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersFourthRequest;
- }).thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersFifthRequest;
- }).thenAnswer(invocation -> {
- TimeUnit.MILLISECONDS.sleep(10);
- return headersSixthRequest;
- });
-
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(6, result.size());
- long expected = 1;
- for (long priority :
- result.stream()
- .map(SourceRegistration::getSourcePriority)
- .sorted()
- .collect(Collectors.toList())) {
- Assert.assertEquals(expected++, priority);
- }
- }
-
- @Test
- public void testBasicSourceRequestWithAggregateFilterData() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- String filterData =
- " \"filter_data\": {\"product\":[\"1234\",\"2345\"], \"ctid\":[\"id\"]} \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("android-app://com.myapps", result.get(0).getAppDestination().toString());
- assertEquals(123, result.get(0).getSourcePriority());
- assertEquals(456789, result.get(0).getExpiry());
- assertEquals(987654321, result.get(0).getSourceEventId());
- assertEquals("{\"product\":[\"1234\",\"2345\"],\"ctid\":[\"id\"]}",
- result.get(0).getAggregateFilterData());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_invalidJson() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- String filterData =
- " \"filter_data\": {\"product\":\"1234\",\"2345\"], \"ctid\":[\"id\"]} \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_tooManyFilters() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{");
- filters.append(IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
- .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
- .collect(Collectors.joining(",")));
- filters.append("}");
- String filterData = " \"filter_data\": " + filters + "\n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_keyTooLong() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- String filterData =
- " \"filter_data\": {\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING
- + "\":[\"id\"]} \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_tooManyValues() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{"
- + "\"filter-string-1\": [\"filter-value-1\"],"
- + "\"filter-string-2\": [");
- filters.append(IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
- .mapToObj(i -> "\"filter-value-" + i + "\"")
- .collect(Collectors.joining(",")));
- filters.append("]}");
- String filterData = " \"filter_data\": " + filters + " \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequest_aggregateFilterData_valueTooLong() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- String filterData =
- " \"filter_data\": {\"product\":[\"1234\",\"" + LONG_FILTER_STRING
- + "\"], \"ctid\":[\"id\"]} \n";
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + filterData
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testMissingHeaderButWithRedirect() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Redirect", List.of(ALT_REGISTRATION)))
- .thenReturn(Map.of("Attribution-Reporting-Register-Source",
- List.of("{\n"
- + " \"destination\": \"" + DEFAULT_DESTINATION + "\",\n"
- + " \"priority\": \"" + DEFAULT_PRIORITY + "\",\n"
- + " \"expiry\": \"" + DEFAULT_EXPIRY + "\",\n"
- + " \"source_event_id\": \"" + DEFAULT_EVENT_ID + "\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicSourceRequestWithAggregateSource() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0x159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"0x5\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(
- new JSONArray(
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},{\"id\""
- + " : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .toString(),
- result.get(0).getAggregateSource());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_invalidJson() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0x159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"0x5\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_tooManyKeys() throws Exception {
- StringBuilder tooManyKeys = new StringBuilder("[");
- for (int i = 0; i < MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1; i++) {
- tooManyKeys.append(String.format(
- "{\"id\": \"campaign-%1$s\", \"key_piece\": \"0x15%1$s\"}", i));
- }
- tooManyKeys.append("]");
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\":"
- + " \"987654321\",'aggregation_keys': "
- + tooManyKeys)));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_keyIsNotAnObject() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0x159\"},[\"id\", \"geoValue\", \"key_piece\" ,"
- + " \"0x5\"]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_invalidKeyId() throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"" + LONG_AGGREGATE_KEY_ID + "\", \"key_piece\" :"
- + " \"0x159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"0x5\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_invalidKeyPiece_missingPrefix()
- throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"0x5\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testSourceRequestWithAggregateSource_invalidKeyPiece_tooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com.myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"aggregation_keys\": [{\"id\" :"
- + " \"campaignCounts\", \"key_piece\" :"
- + " \"0x159\"},{\"id\" : \"geoValue\", \"key_piece\" :"
- + " \"" + LONG_AGGREGATE_KEY_PIECE + "\"}]\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchWebSources_basic_success() throws IOException {
- // Setup
- SourceRegistration expectedResult1 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_1)
- .setSourcePriority(0)
- .setDebugKey(DEBUG_KEY)
- .build();
- SourceRegistration expectedResult2 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_2)
- .setSourcePriority(0)
- .build();
-
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Arrays.asList(SOURCE_REGISTRATION_1, SOURCE_REGISTRATION_2),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_2
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(
- new HashSet<>(Arrays.asList(expectedResult1, expectedResult2)),
- new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSourcesSuccessWithoutAdIdPermission() throws IOException {
- // Setup
- SourceRegistration expectedResult1 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_1)
- .setSourcePriority(0)
- .build();
- SourceRegistration expectedResult2 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_2)
- .setSourcePriority(0)
- .build();
-
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Arrays.asList(SOURCE_REGISTRATION_1, SOURCE_REGISTRATION_2),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_2
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, false);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(
- new HashSet<>(Arrays.asList(expectedResult1, expectedResult2)),
- new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_oneSuccessAndOneFailure_resultsIntoOneSourceFetched()
- throws IOException {
- // Setup
- SourceRegistration expectedResult2 =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_2)
- .setSourcePriority(0)
- .build();
-
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Arrays.asList(SOURCE_REGISTRATION_1, SOURCE_REGISTRATION_2),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
- // Its validation will fail due to destination mismatch
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- /* wrong destination */
- + "android-app://com.wrongapp"
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\",\n"
- + " \"debug_key\": \""
- + DEBUG_KEY
- + "\"\n"
- + "}\n")));
-
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_2
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedResult2, result.iterator().next());
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_withExtendedHeaders_success() throws IOException, JSONException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String aggregateSource =
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]";
- String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + OS_DESTINATION
- + "\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"filter_data\": "
- + filterData
- + ", "
- + " \"aggregation_keys\": "
- + aggregateSource
- + "}")));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(OS_DESTINATION)
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setAggregateFilterData(filterData)
- .setAggregateSource(new JSONArray(aggregateSource).toString())
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_withRedirects_ignoresRedirects() throws IOException, JSONException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION),
- null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String aggregateSource =
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]";
- String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"filter_data\": "
- + filterData
- + " , "
- + " \"aggregation_keys\": "
- + aggregateSource
- + "}"),
- "Attribution-Reporting-Redirect",
- List.of(ALT_REGISTRATION)));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setAggregateFilterData(filterData)
- .setAggregateSource(new JSONArray(aggregateSource).toString())
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchWebSources_appDestinationDoNotMatch_failsDropsSource() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"destination\":"
- + " android-app://wrong.os-destination,\n"
- + " \"web_destination\": "
- + WEB_DESTINATION
- + ",\n"
- + " \"filter_data\": "
- + filterData
- + "}")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertFalse(fetch.isPresent());
-
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchWebSources_webDestinationDoNotMatch_failsDropsSource() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- WEB_DESTINATION);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String filterData = "{\"product\":[\"1234\",\"2345\"]," + "\"ctid\":[\"id\"]}";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"destination\": "
- + OS_DESTINATION
- + ",\n"
- + " \"web_destination\": "
- + " https://wrong-web-destination.com,\n"
- + " \"filter_data\": "
- + filterData
- + "}")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertFalse(fetch.isPresent());
-
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchWebSources_osAndWebDestinationMatch_recordSourceSuccess() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- WEB_DESTINATION);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"destination\": \""
- + OS_DESTINATION
- + "\",\n"
- + "\"web_destination\": \""
- + WEB_DESTINATION
- + "\""
- + "}")));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(OS_DESTINATION)
- .setWebDestination(WEB_DESTINATION)
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_extractsTopPrivateDomain() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- WEB_DESTINATION_WITH_SUBDOMAIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \"android-app://com"
- + ".myapps\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + " \"destination\": \""
- + OS_DESTINATION
- + "\",\n"
- + "\"web_destination\": \""
- + WEB_DESTINATION_WITH_SUBDOMAIN
- + "\""
- + "}")));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(OS_DESTINATION)
- .setWebDestination(WEB_DESTINATION)
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_extractsDestinationBaseUri() throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION_WITH_PATH,
- WEB_DESTINATION);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + " \"destination\": \""
- + OS_DESTINATION_WITH_PATH
- + "\",\n"
- + " \"priority\": \"123\",\n"
- + " \"expiry\": \"456789\",\n"
- + " \"source_event_id\": \"987654321\",\n"
- + "\"web_destination\": \""
- + WEB_DESTINATION
- + "\""
- + "}")));
- SourceRegistration expectedSourceRegistration =
- new SourceRegistration.Builder()
- .setAppDestination(OS_DESTINATION)
- .setWebDestination(WEB_DESTINATION)
- .setSourcePriority(123)
- .setExpiry(456789)
- .setSourceEventId(987654321)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedSourceRegistration, result.get(0));
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_missingDestinations_dropsSource() throws Exception {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- OS_DESTINATION,
- WEB_DESTINATION);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertFalse(fetch.isPresent());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_withDestinationUriNotHavingScheme_attachesAppScheme()
- throws IOException {
- // Setup
- SourceRegistration expectedResult =
- new SourceRegistration.Builder()
- .setAppDestination(Uri.parse(DEFAULT_DESTINATION))
- .setExpiry(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS)
- .setTopOrigin(Uri.parse(DEFAULT_TOP_ORIGIN))
- .setEnrollmentId(ENROLLMENT_ID)
- .setSourceEventId(EVENT_ID_1)
- .setSourcePriority(0)
- .build();
-
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION_WITHOUT_SCHEME),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<SourceRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(
- new HashSet<>(Collections.singletonList(expectedResult)), new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebSources_withDestinationUriHavingHttpsScheme_dropsSource()
- throws IOException {
- // Setup
- WebSourceRegistrationRequest request =
- buildWebSourceRegistrationRequest(
- Collections.singletonList(SOURCE_REGISTRATION_1),
- DEFAULT_TOP_ORIGIN,
- Uri.parse(DEFAULT_DESTINATION_WITHOUT_SCHEME),
- null);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection1.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- // Invalid (https) URI for app destination
- + WEB_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + EVENT_ID_1
- + "\"\n"
- + "}\n")));
-
- // Execution
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchWebSources(request, true);
-
- // Assertion
- assertFalse(fetch.isPresent());
- verify(mUrlConnection1).setRequestMethod("POST");
- }
-
- @Test
- public void basicSourceRequest_headersMoreThanMaxResponseSize_emitsMetricsWithAdTechDomain()
- throws Exception {
- RegistrationRequest request = buildRequest(DEFAULT_REGISTRATION, DEFAULT_TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(DEFAULT_REGISTRATION));
- doReturn(5L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Source",
- List.of(
- "{\n"
- + "\"destination\": \""
- + DEFAULT_DESTINATION
- + "\",\n"
- + "\"source_event_id\": \""
- + DEFAULT_EVENT_ID
- + "\"\n"
- + "}\n")));
- Optional<List<SourceRegistration>> fetch = mFetcher.fetchSource(request);
- assertTrue(fetch.isPresent());
- verify(mLogger)
- .logMeasurementRegistrationsResponseSize(
- eq(
- new MeasurementRegistrationResponseStats.Builder(
- AD_SERVICES_MEASUREMENT_REGISTRATIONS,
- AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__SOURCE,
- 115)
- .setAdTechDomain(DEFAULT_REGISTRATION)
- .build()));
- }
-
- private RegistrationRequest buildRequest(String registrationUri, String topOrigin) {
- return new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse(registrationUri))
- .setTopOriginUri(Uri.parse(topOrigin))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .setAdIdPermissionGranted(true)
- .build();
- }
-
- private RegistrationRequest buildRequestWithoutAdIdPermission(
- String registrationUri, String topOrigin) {
- return new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_SOURCE)
- .setRegistrationUri(Uri.parse(registrationUri))
- .setTopOriginUri(Uri.parse(topOrigin))
- .setPackageName(sContext.getAttributionSource().getPackageName())
- .setAdIdPermissionGranted(false)
- .build();
- }
-
- private WebSourceRegistrationRequest buildWebSourceRegistrationRequest(
- List<WebSourceParams> sourceParamsList,
- String topOrigin,
- Uri appDestination,
- Uri webDestination) {
- WebSourceRegistrationRequest.Builder webSourceRegistrationRequestBuilder =
- new WebSourceRegistrationRequest.Builder(sourceParamsList, Uri.parse(topOrigin))
- .setAppDestination(appDestination);
-
- if (webDestination != null) {
- webSourceRegistrationRequestBuilder.setWebDestination(webDestination);
- }
-
- return webSourceRegistrationRequestBuilder.build();
- }
-
- private Map<String, List<String>> buildRegisterSourceDefaultHeader(
- String destination, long eventId, long priority, long expiry) {
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put("Attribution-Reporting-Register-Source", List.of("{\n"
- + "\"destination\": \"" + destination + "\",\n"
- + "\"source_event_id\": \"" + eventId + "\",\n"
- + "\"priority\": \"" + priority + "\",\n"
- + "\"expiry\": " + expiry + ""
- + "}\n"));
- return headersFirstRequest;
- }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceRegistrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceRegistrationTest.java
deleted file mode 100644
index e944fef62..000000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/SourceRegistrationTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static com.android.adservices.service.measurement.PrivacyParams.MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
-
-import android.net.Uri;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-
-/**
- * Unit tests for {@link SourceRegistration}
- */
-@SmallTest
-public final class SourceRegistrationTest {
- private static final Uri TOP_ORIGIN = Uri.parse("https://foo.com");
- private static final String ENROLLMENT_ID = "enrollment-id";
- private static final Long DEBUG_KEY = 2376843L;
-
- private SourceRegistration createExampleResponse() {
-
- return new SourceRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setAppDestination(Uri.parse("android-app://baz.com"))
- .setSourceEventId(1234567)
- .setExpiry(2345678)
- .setSourcePriority(345678)
- .setAggregateSource(
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]")
- .setAggregateFilterData("{\"product\":[\"1234\",\"2345\"],\"ctid\":[\"id\"]}")
- .setDebugKey(DEBUG_KEY)
- .build();
- }
-
- void verifyExampleResponse(SourceRegistration response) {
- assertEquals("https://foo.com", response.getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertEquals("android-app://baz.com", response.getAppDestination().toString());
- assertEquals(1234567, response.getSourceEventId());
- assertEquals(2345678, response.getExpiry());
- assertEquals(345678, response.getSourcePriority());
- assertEquals(DEBUG_KEY, response.getDebugKey());
- assertEquals(
- "[{\"id\" : \"campaignCounts\", \"key_piece\" : \"0x159\"},"
- + "{\"id\" : \"geoValue\", \"key_piece\" : \"0x5\"}]",
- response.getAggregateSource());
- assertEquals("{\"product\":[\"1234\",\"2345\"],\"ctid\":[\"id\"]}",
- response.getAggregateFilterData());
- }
-
- @Test
- public void testCreation() throws Exception {
- verifyExampleResponse(createExampleResponse());
- }
-
- @Test
- public void sourceRegistration_onlyAppDestination_success() {
- Uri destination = Uri.parse("android-app://baz.com");
- SourceRegistration response =
- new SourceRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setAppDestination(destination)
- .build();
- assertEquals(TOP_ORIGIN, response.getTopOrigin());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertEquals(destination, response.getAppDestination());
- assertNull(response.getWebDestination());
- assertEquals(0, response.getSourceEventId());
- assertEquals(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, response.getExpiry());
- assertEquals(0, response.getSourcePriority());
- assertNull(response.getAggregateSource());
- assertNull(response.getAggregateFilterData());
- }
-
- @Test
- public void sourceRegistration_onlyWebDestination_success() {
- Uri destination = Uri.parse("https://baz.com");
- SourceRegistration response =
- new SourceRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setWebDestination(destination)
- .build();
- assertEquals(TOP_ORIGIN, response.getTopOrigin());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertEquals(destination, response.getWebDestination());
- assertNull(response.getAppDestination());
- assertEquals(0, response.getSourceEventId());
- assertEquals(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, response.getExpiry());
- assertEquals(0, response.getSourcePriority());
- assertNull(response.getAggregateSource());
- assertNull(response.getAggregateFilterData());
- }
-
- @Test
- public void sourceRegistration_bothOsAndWebDestination_success() {
- Uri webDestination = Uri.parse("https://baz.com");
- Uri destination = Uri.parse("android-app://baz.com");
- SourceRegistration response =
- new SourceRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setAppDestination(destination)
- .setWebDestination(webDestination)
- .build();
- assertEquals(TOP_ORIGIN, response.getTopOrigin());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertEquals(webDestination, response.getWebDestination());
- assertEquals(destination, response.getAppDestination());
- assertEquals(0, response.getSourceEventId());
- assertEquals(MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS, response.getExpiry());
- assertEquals(0, response.getSourcePriority());
- assertNull(response.getAggregateSource());
- assertNull(response.getAggregateFilterData());
- }
-
- @Test
- public void sourceRegistration_bothDestinationsNull_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () ->
- new SourceRegistration.Builder()
- .setAppDestination(null)
- .setWebDestination(null)
- .build());
- }
-
- @Test
- public void equals_success() {
- assertEquals(createExampleResponse(), createExampleResponse());
- }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerFetcherTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerFetcherTest.java
deleted file mode 100644
index e7ae8f09b..000000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerFetcherTest.java
+++ /dev/null
@@ -1,1231 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATABLE_TRIGGER_DATA;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_EVENT_TRIGGER_DATA;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_FILTERS;
-import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.adservices.measurement.RegistrationRequest;
-import android.adservices.measurement.WebTriggerParams;
-import android.adservices.measurement.WebTriggerRegistrationRequest;
-import android.content.Context;
-import android.net.Uri;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.adservices.data.enrollment.EnrollmentDao;
-import com.android.adservices.service.Flags;
-import com.android.adservices.service.enrollment.EnrollmentData;
-import com.android.adservices.service.stats.AdServicesLogger;
-import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import javax.net.ssl.HttpsURLConnection;
-
-/** Unit tests for {@link TriggerFetcher} */
-@RunWith(MockitoJUnitRunner.class)
-@SmallTest
-public final class TriggerFetcherTest {
- private static final String TRIGGER_URI = "https://foo.com";
- private static final String ENROLLMENT_ID = "enrollment-id";
- private static final EnrollmentData ENROLLMENT = new EnrollmentData.Builder()
- .setEnrollmentId("enrollment-id")
- .build();
- private static final String TOP_ORIGIN = "https://baz.com";
- private static final long TRIGGER_DATA = 7;
- private static final long PRIORITY = 1;
- private static final String LONG_FILTER_STRING = "12345678901234567890123456";
- private static final String LONG_AGGREGATE_KEY_ID = "12345678901234567890123456";
- private static final String LONG_AGGREGATE_KEY_PIECE = "0x123456789012345678901234567890123";
- private static final long DEDUP_KEY = 100;
- private static final Long DEBUG_KEY = 34787843L;
-
- private static final String DEFAULT_REDIRECT = "https://bar.com";
-
- private static final String EVENT_TRIGGERS_1 =
- "[\n"
- + "{\n"
- + " \"trigger_data\": \""
- + TRIGGER_DATA
- + "\",\n"
- + " \"priority\": \""
- + PRIORITY
- + "\",\n"
- + " \"deduplication_key\": \""
- + DEDUP_KEY
- + "\",\n"
- + " \"filters\": {\n"
- + " \"source_type\": [\"navigation\"],\n"
- + " \"key_1\": [\"value_1\"] \n"
- + " }\n"
- + "}"
- + "]\n";
- private static final String EVENT_TRIGGERS_2 =
- "[\n"
- + "{\n"
- + " \"trigger_data\": \""
- + 11
- + "\",\n"
- + " \"priority\": \""
- + 21
- + "\",\n"
- + " \"deduplication_key\": \""
- + 31
- + "\"}"
- + "]\n";
-
- private static final String ALT_REGISTRATION = "https://bar.com";
- private static final Uri REGISTRATION_URI_1 = Uri.parse("https://foo.com");
- private static final Uri REGISTRATION_URI_2 = Uri.parse("https://foo2.com");
- private static final WebTriggerParams TRIGGER_REGISTRATION_1 =
- new WebTriggerParams.Builder(REGISTRATION_URI_1).setDebugKeyAllowed(true).build();
- private static final WebTriggerParams TRIGGER_REGISTRATION_2 =
- new WebTriggerParams.Builder(REGISTRATION_URI_2).setDebugKeyAllowed(false).build();
- private static final Context CONTEXT =
- InstrumentationRegistry.getInstrumentation().getContext();
-
- TriggerFetcher mFetcher;
- @Mock HttpsURLConnection mUrlConnection;
- @Mock HttpsURLConnection mUrlConnection1;
- @Mock HttpsURLConnection mUrlConnection2;
- @Mock EnrollmentDao mEnrollmentDao;
- @Mock Flags mFlags;
- @Mock AdServicesLogger mLogger;
-
- @Before
- public void setup() {
- mFetcher = spy(new TriggerFetcher(mEnrollmentDao, mFlags, mLogger));
- // For convenience, return the same enrollment-ID since we're using many arbitrary
- // registration URIs and not yet enforcing uniqueness of enrollment.
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(ENROLLMENT);
- }
-
- @Test
- public void testBasicTriggerRequest() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- MeasurementRegistrationResponseStats expectedStats =
- new MeasurementRegistrationResponseStats.Builder(
- AD_SERVICES_MEASUREMENT_REGISTRATIONS,
- AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
- 221)
- .setAdTechDomain(null)
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- doReturn(5000L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mLogger).logMeasurementRegistrationsResponseSize(eq(expectedStats));
- }
-
- @Test
- public void testTriggerRequest_eventTriggerData_tooManyEntries() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder tooManyEntries = new StringBuilder("[");
- for (int i = 0; i < MAX_ATTRIBUTION_EVENT_TRIGGER_DATA + 1; i++) {
- tooManyEntries.append("{\"trigger_data\": \"2\",\"priority\": \"101\"}");
- }
- tooManyEntries.append("]");
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + tooManyEntries + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequest_failsWhenNotEnrolled() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(any())).thenReturn(null);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mFetcher, never()).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequestWithDebugKey() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersRequest = new HashMap<>();
- headersRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'debug_key': '"
- + DEBUG_KEY
- + "'"
- + "}"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
-
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- assertEquals(DEBUG_KEY, result.get(0).getDebugKey());
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBasicTriggerRequestWithoutAdIdPermission() throws Exception {
- RegistrationRequest request = buildRequestWithoutAdIdPermission(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersRequest = new HashMap<>();
- headersRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'debug_key': '"
- + DEBUG_KEY
- + "'"
- + "}"));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersRequest);
-
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- assertNull(result.get(0).getDebugKey());
-
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testBadTriggerUrl() throws Exception {
- RegistrationRequest request =
- buildRequest("bad-schema://foo.com", TOP_ORIGIN);
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- }
-
- @Test
- public void testBadTriggerConnection() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doThrow(new IOException("Bad internet things"))
- .when(mFetcher).openUrl(new URL(TRIGGER_URI));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, never()).setRequestMethod("POST");
- }
-
- @Test
- public void testBadRequestReturnFailure() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(400);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequestMinimumFields() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data': " + "[{}]" + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals("[{}]", result.get(0).getEventTriggers());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testNotOverHttps() throws Exception {
- RegistrationRequest request = buildRequest("http://foo.com", TOP_ORIGIN);
- // Non-https should fail.
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- }
-
- @Test
- public void testFirst200Next500_ignoreFailureReturnSuccess() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200).thenReturn(500);
-
- Map<String, List<String>> headersFirstRequest = new HashMap<>();
- headersFirstRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}"));
- headersFirstRequest.put("Attribution-Reporting-Redirect", List.of(DEFAULT_REDIRECT));
-
- when(mUrlConnection.getHeaderFields()).thenReturn(headersFirstRequest);
-
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- verify(mUrlConnection, times(2)).setRequestMethod("POST");
- }
-
- @Test
- public void testMissingHeaderButWithRedirect() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(any(URL.class));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(Map.of("Attribution-Reporting-Redirect", List.of(DEFAULT_REDIRECT)))
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequestWithAggregateTriggerData() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(
- new JSONArray(aggregatable_trigger_data).toString(),
- result.get(0).getAggregateTriggerData());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_invalidJson() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_tooManyEntries() throws Exception {
- StringBuilder tooManyEntries = new StringBuilder("[");
- for (int i = 0; i < MAX_AGGREGATABLE_TRIGGER_DATA + 1; i++) {
- tooManyEntries.append(String.format(
- "{\"key_piece\": \"0x15%1$s\",\"source_keys\":[\"campaign-%1$s\"]}", i));
- }
- tooManyEntries.append("]");
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + tooManyEntries
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_invalidKeyPiece_missingPrefix()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_invalidKeyPiece_tooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"" + LONG_AGGREGATE_KEY_PIECE
- + "\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_sourceKeys_notAnArray()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":{\"campaignCounts\": true},"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_sourceKeys_tooManyKeys()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder tooManyKeys = new StringBuilder("[");
- tooManyKeys.append(IntStream.range(0, MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1)
- .mapToObj(i -> "aggregate-key-" + i)
- .collect(Collectors.joining(",")));
- tooManyKeys.append("]");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\": " + tooManyKeys + ","
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_sourceKeys_invalidKeyId()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\", \""
- + LONG_AGGREGATE_KEY_ID + "\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_filters_tooManyFilters()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{");
- filters.append(IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
- .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
- .collect(Collectors.joining(",")));
- filters.append("}");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\": " + filters + ","
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_filters_keyTooLong() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String filters =
- "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\": " + filters + ","
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_filters_tooManyValues()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{"
- + "\"filter-string-1\": [\"filter-value-1\"],"
- + "\"filter-string-2\": [");
- filters.append(IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
- .mapToObj(i -> "\"filter-value-" + i + "\"")
- .collect(Collectors.joining(",")));
- filters.append("]}");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\": " + filters + ","
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_filters_valueTooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String filters =
- "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\": " + filters + ","
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_notFilters_tooManyFilters()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{");
- filters.append(IntStream.range(0, MAX_ATTRIBUTION_FILTERS + 1)
- .mapToObj(i -> "\"filter-string-" + i + "\": [\"filter-value\"]")
- .collect(Collectors.joining(",")));
- filters.append("}");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\": " + filters + ","
- + "\"filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_notFilters_keyTooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String filters =
- "{\"product\":[\"1234\",\"2345\"], \"" + LONG_FILTER_STRING + "\":[\"id\"]}";
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\": " + filters + ","
- + "\"filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_notFilters_tooManyValues()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- StringBuilder filters = new StringBuilder("{"
- + "\"filter-string-1\": [\"filter-value-1\"],"
- + "\"filter-string-2\": [");
- filters.append(IntStream.range(0, MAX_VALUES_PER_ATTRIBUTION_FILTER + 1)
- .mapToObj(i -> "\"filter-value-" + i + "\"")
- .collect(Collectors.joining(",")));
- filters.append("]}");
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\": " + filters + ","
- + "\"filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregateTriggerData_notFilters_valueTooLong()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String filters =
- "{\"product\":[\"1234\",\"" + LONG_FILTER_STRING + "\"], \"ctid\":[\"id\"]}";
- String aggregatable_trigger_data =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\": " + filters + ","
- + "\"filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_trigger_data': "
- + aggregatable_trigger_data
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testBasicTriggerRequestWithAggregatableValues() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_values = "{\"campaignCounts\":32768,\"geoValue\":1644}";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_values': "
- + aggregatable_values
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(
- new JSONObject(aggregatable_values).toString(), result.get(0).getAggregateValues());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void testTriggerRequestWithAggregatableValues_invalidJson() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_values = "{\"campaignCounts\":32768\"geoValue\":1644}";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_values': "
- + aggregatable_values
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregatableValues_tooManyKeys() throws Exception {
- StringBuilder tooManyKeys = new StringBuilder("{");
- tooManyKeys.append(IntStream.range(0, MAX_AGGREGATE_KEYS_PER_REGISTRATION + 1)
- .mapToObj(i -> String.format("\"key-%s\": 12345,", i))
- .collect(Collectors.joining(",")));
- tooManyKeys.append("}");
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'aggregatable_values': " + tooManyKeys + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void testTriggerRequestWithAggregatableValues_invalidKeyId() throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- String aggregatable_values = "{\"campaignCounts\":32768, \"" + LONG_AGGREGATE_KEY_ID
- + "\":1644}";
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'aggregatable_values': "
- + aggregatable_values
- + "}")));
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertFalse(fetch.isPresent());
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void fetchTrigger_withReportingFilters_success() throws IOException, JSONException {
- // Setup
- String filters =
- "{\n"
- + " \"key_1\": [\"value_1\", \"value_2\"],\n"
- + " \"key_2\": [\"value_1\", \"value_2\"]\n"
- + "}";
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'filters': " + filters + "}")));
-
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals("https://baz.com", result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONObject(filters).toString(), result.get(0).getFilters());
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebTriggers_basic_success() throws IOException, JSONException {
- // Setup
- TriggerRegistration expectedResult1 =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_1).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .setDebugKey(DEBUG_KEY)
- .build();
- TriggerRegistration expectedResult2 =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_2).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- WebTriggerRegistrationRequest request =
- buildWebTriggerRegistrationRequest(
- Arrays.asList(TRIGGER_REGISTRATION_1, TRIGGER_REGISTRATION_2), TOP_ORIGIN);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersRequest = new HashMap<>();
- headersRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'debug_key': '"
- + DEBUG_KEY
- + "'"
- + "}"));
- when(mUrlConnection1.getHeaderFields()).thenReturn(headersRequest);
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data': " + EVENT_TRIGGERS_2 + "}")));
-
- // Execution
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchWebTriggers(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(
- new HashSet<>(Arrays.asList(expectedResult1, expectedResult2)),
- new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebTriggerSuccessWithoutAdIdPermission() throws IOException, JSONException {
- // Setup
- TriggerRegistration expectedResult1 =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_1).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
- TriggerRegistration expectedResult2 =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_2).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- WebTriggerRegistrationRequest request =
- buildWebTriggerRegistrationRequest(
- Arrays.asList(TRIGGER_REGISTRATION_1, TRIGGER_REGISTRATION_2), TOP_ORIGIN);
- doReturn(mUrlConnection1).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- doReturn(mUrlConnection2).when(mFetcher).openUrl(new URL(REGISTRATION_URI_2.toString()));
- when(mUrlConnection1.getResponseCode()).thenReturn(200);
- when(mUrlConnection2.getResponseCode()).thenReturn(200);
-
- Map<String, List<String>> headersRequest = new HashMap<>();
- headersRequest.put(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'debug_key': '"
- + DEBUG_KEY
- + "'"
- + "}"));
- when(mUrlConnection1.getHeaderFields()).thenReturn(headersRequest);
- when(mUrlConnection2.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data': " + EVENT_TRIGGERS_2 + "}")));
-
- // Execution
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchWebTriggers(request, false);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(2, result.size());
- assertEquals(
- new HashSet<>(Arrays.asList(expectedResult1, expectedResult2)),
- new HashSet<>(result));
- verify(mUrlConnection1).setRequestMethod("POST");
- verify(mUrlConnection2).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebTriggers_withExtendedHeaders_success() throws IOException, JSONException {
- // Setup
- WebTriggerRegistrationRequest request =
- buildWebTriggerRegistrationRequest(
- Collections.singletonList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- String aggregatableValues = "{\"campaignCounts\":32768,\"geoValue\":1644}";
- String filters =
- "{\n"
- + " \"key_1\": [\"value_1\", \"value_2\"],\n"
- + " \"key_2\": [\"value_1\", \"value_2\"]\n"
- + "}";
- String aggregatableTriggerData =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"filters\":"
- + "{\"conversion_subdomain\":[\"electronics.megastore\"]},"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of(
- "{"
- + "'event_trigger_data': "
- + EVENT_TRIGGERS_1
- + ", 'filters': "
- + filters
- + ", 'aggregatable_values': "
- + aggregatableValues
- + ", 'aggregatable_trigger_data': "
- + aggregatableTriggerData
- + "}")));
- TriggerRegistration expectedResult =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_1).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .setFilters(new JSONObject(filters).toString())
- .setAggregateTriggerData(new JSONArray(aggregatableTriggerData).toString())
- .setAggregateValues(new JSONObject(aggregatableValues).toString())
- .build();
-
- // Execution
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchWebTriggers(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedResult, result.get(0));
- verify(mUrlConnection).setRequestMethod("POST");
- }
-
- @Test
- public void fetchWebTriggers_withRedirects_ignoresRedirects()
- throws IOException, JSONException {
- // Setup
- WebTriggerRegistrationRequest request =
- buildWebTriggerRegistrationRequest(
- Collections.singletonList(TRIGGER_REGISTRATION_1), TOP_ORIGIN);
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(REGISTRATION_URI_1.toString()));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data': " + EVENT_TRIGGERS_1 + "}"),
- "Attribution-Reporting-Redirect",
- List.of(ALT_REGISTRATION)));
- TriggerRegistration expectedResult =
- new TriggerRegistration.Builder()
- .setTopOrigin(Uri.parse(TOP_ORIGIN))
- .setEventTriggers(new JSONArray(EVENT_TRIGGERS_1).toString())
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
-
- // Execution
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchWebTriggers(request, true);
-
- // Assertion
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(expectedResult, result.get(0));
- verify(mUrlConnection, times(1)).setRequestMethod("POST");
- verify(mFetcher, times(1)).openUrl(any());
- }
-
- @Test
- public void basicTriggerRequest_headersMoreThanMaxResponseSize_emitsMetricsWithAdTechDomain()
- throws Exception {
- RegistrationRequest request = buildRequest(TRIGGER_URI, TOP_ORIGIN);
- MeasurementRegistrationResponseStats expectedStats =
- new MeasurementRegistrationResponseStats.Builder(
- AD_SERVICES_MEASUREMENT_REGISTRATIONS,
- AD_SERVICES_MEASUREMENT_REGISTRATIONS__TYPE__TRIGGER,
- 221)
- .setAdTechDomain(TRIGGER_URI)
- .build();
- doReturn(mUrlConnection).when(mFetcher).openUrl(new URL(TRIGGER_URI));
- when(mUrlConnection.getResponseCode()).thenReturn(200);
- when(mUrlConnection.getHeaderFields())
- .thenReturn(
- Map.of(
- "Attribution-Reporting-Register-Trigger",
- List.of("{" + "'event_trigger_data':" + EVENT_TRIGGERS_1 + "}")));
- doReturn(5L).when(mFlags).getMaxResponseBasedRegistrationPayloadSizeBytes();
- Optional<List<TriggerRegistration>> fetch = mFetcher.fetchTrigger(request);
- assertTrue(fetch.isPresent());
- List<TriggerRegistration> result = fetch.get();
- assertEquals(1, result.size());
- assertEquals(TOP_ORIGIN, result.get(0).getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, result.get(0).getEnrollmentId());
- assertEquals(new JSONArray(EVENT_TRIGGERS_1).toString(), result.get(0).getEventTriggers());
- verify(mUrlConnection).setRequestMethod("POST");
- verify(mLogger).logMeasurementRegistrationsResponseSize(eq(expectedStats));
- }
-
- private RegistrationRequest buildRequest(String triggerUri, String topOriginUri) {
- return new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
- .setRegistrationUri(Uri.parse(triggerUri))
- .setTopOriginUri(Uri.parse(topOriginUri))
- .setPackageName(CONTEXT.getAttributionSource().getPackageName())
- .setAdIdPermissionGranted(true)
- .build();
- }
-
- private RegistrationRequest buildRequestWithoutAdIdPermission(
- String triggerUri, String topOriginUri) {
- return new RegistrationRequest.Builder()
- .setRegistrationType(RegistrationRequest.REGISTER_TRIGGER)
- .setRegistrationUri(Uri.parse(triggerUri))
- .setTopOriginUri(Uri.parse(topOriginUri))
- .setPackageName(CONTEXT.getAttributionSource().getPackageName())
- .setAdIdPermissionGranted(false)
- .build();
- }
-
- private WebTriggerRegistrationRequest buildWebTriggerRegistrationRequest(
- List<WebTriggerParams> triggerParams, String topOrigin) {
- return new WebTriggerRegistrationRequest.Builder(triggerParams, Uri.parse(topOrigin))
- .build();
- }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerRegistrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerRegistrationTest.java
deleted file mode 100644
index d42f96fc6..000000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/registration/TriggerRegistrationTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.measurement.registration;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.net.Uri;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-
-/**
- * Unit tests for {@link TriggerRegistration}
- */
-@SmallTest
-public final class TriggerRegistrationTest {
- private static final Uri TOP_ORIGIN = Uri.parse("https://foo.com");
- private static final String ENROLLMENT_ID = "enrollment-id";
- private static final String TOP_LEVEL_FILTERS_JSON_STRING =
- "{\n"
- + " \"key_1\": [\"value_1\", \"value_2\"],\n"
- + " \"key_2\": [\"value_1\", \"value_2\"]\n"
- + "}\n";
- private static final String EVENT_TRIGGERS =
- "[\n"
- + "{\n"
- + " \"trigger_data\": \"1\",\n"
- + " \"priority\": \"345678\",\n"
- + " \"deduplication_key\": \"2345678\",\n"
- + " \"filters\": {\n"
- + " \"source_type\": [\"navigation\"],\n"
- + " \"key_1\": [\"value_1\"] \n"
- + " }\n"
- + "}"
- + "]\n";
-
- private static final Long DEBUG_KEY = 23478951L;
-
- private static final String AGGREGATE_TRIGGER_DATA =
- "[{\"key_piece\":\"0x400\",\"source_keys\":[\"campaignCounts\"],"
- + "\"not_filters\":{\"product\":[\"1\"]}},"
- + "{\"key_piece\":\"0xA80\",\"source_keys\":[\"geoValue\"]}]";
-
- private TriggerRegistration createExampleResponse() {
- return new TriggerRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .setEventTriggers(EVENT_TRIGGERS)
- .setAggregateTriggerData(AGGREGATE_TRIGGER_DATA)
- .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
- .setFilters(TOP_LEVEL_FILTERS_JSON_STRING)
- .setDebugKey(DEBUG_KEY)
- .build();
- }
-
- void verifyExampleResponse(TriggerRegistration triggerRegistration) {
- assertEquals("https://foo.com", triggerRegistration.getTopOrigin().toString());
- assertEquals(ENROLLMENT_ID, triggerRegistration.getEnrollmentId());
- assertEquals(EVENT_TRIGGERS, triggerRegistration.getEventTriggers());
- assertEquals(AGGREGATE_TRIGGER_DATA, triggerRegistration.getAggregateTriggerData());
- assertEquals(
- "{\"campaignCounts\":32768,\"geoValue\":1644}",
- triggerRegistration.getAggregateValues());
- assertEquals(TOP_LEVEL_FILTERS_JSON_STRING, triggerRegistration.getFilters());
- assertEquals(DEBUG_KEY, triggerRegistration.getDebugKey());
- }
-
- @Test
- public void testCreation() throws Exception {
- verifyExampleResponse(createExampleResponse());
- }
-
- @Test
- public void testDefaults() throws Exception {
- TriggerRegistration response =
- new TriggerRegistration.Builder()
- .setTopOrigin(TOP_ORIGIN)
- .setEnrollmentId(ENROLLMENT_ID)
- .build();
- assertEquals(TOP_ORIGIN, response.getTopOrigin());
- assertEquals(ENROLLMENT_ID, response.getEnrollmentId());
- assertNull(response.getEventTriggers());
- assertNull(response.getAggregateTriggerData());
- assertNull(response.getAggregateValues());
- assertNull(response.getFilters());
- }
-
- @Test
- public void equals_success() {
- assertEquals(createExampleResponse(), createExampleResponse());
- }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java
index 8757ab94d..781224d34 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateFallbackReportingJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.reporting;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_AGGREGATE_FALLBACK_REPORTING_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,33 +36,31 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Unit test for {@link AggregateFallbackReportingJobService
*/
public class AggregateFallbackReportingJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
- private static final long WAIT_IN_MILLIS = 50L;
+ private static final long WAIT_IN_MILLIS = 1_000L;
private DatastoreManager mMockDatastoreManager;
private JobScheduler mMockJobScheduler;
@@ -77,10 +76,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
@@ -97,10 +97,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ disableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
@@ -117,11 +118,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -149,11 +150,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -181,11 +182,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -213,11 +214,11 @@ public class AggregateFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -240,11 +241,37 @@ public class AggregateFallbackReportingJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() throws Exception {
+ runWithMocks(
+ () -> {
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ ExtendedMockito.doCallRealMethod()
+ .when(
+ () ->
+ AggregateFallbackReportingJobService.schedule(
+ any(), any()));
+ AggregateFallbackReportingJobService.schedule(
+ mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ });
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
- .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(AdServicesConfig.class)
.spyStatic(AggregateFallbackReportingJobService.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -256,6 +283,14 @@ public class AggregateFallbackReportingJobServiceTest {
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(4))
+ .when(AdServicesConfig::getMeasurementAggregateMainReportingJobPeriodMs);
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(24))
+ .when(AdServicesConfig::getMeasurementAggregateFallbackReportingJobPeriodMs);
+ ExtendedMockito.doReturn("http://example.com")
+ .when(AdServicesConfig::getMeasurementAggregateEncryptionKeyCoordinatorUrl);
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing()
@@ -277,10 +312,10 @@ public class AggregateFallbackReportingJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_aggregate_fallback_reporting_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value)
+ .when(mockFlags)
+ .getMeasurementJobAggregateFallbackReportingKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java
index 3d79b2e08..d285a741a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportBodyTest.java
@@ -22,8 +22,10 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.android.adservices.HpkeJni;
+import com.android.adservices.service.measurement.aggregation.AggregateCryptoConverter;
import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONArray;
import org.json.JSONException;
@@ -50,8 +52,8 @@ public class AggregateReportBodyTest {
private static final String SCHEDULED_REPORT_TIME = "1246174158155";
private static final String VERSION = "12";
private static final String REPORT_ID = "A1";
- private static final Long SOURCE_DEBUG_KEY = 27628792L;
- private static final Long TRIGGER_DEBUG_KEY = 23443234L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(27628792L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(23443234L);
private static final String REPORTING_ORIGIN = "https://adtech.domain";
private static final String DEBUG_CLEARTEXT_PAYLOAD = "{\"operation\":\"histogram\","
+ "\"data\":[{\"bucket\":1369,\"value\":32768},{\"bucket\":3461,"
@@ -123,10 +125,8 @@ public class AggregateReportBodyTest {
assertEquals(REPORTING_ORIGIN, sharedInfoJson.get("reporting_origin"));
assertEquals(ATTRIBUTION_DESTINATION, sharedInfoJson.get("attribution_destination"));
assertEquals(SOURCE_REGISTRATION_TIME, sharedInfoJson.get("source_registration_time"));
- assertEquals(
- Long.toUnsignedString(SOURCE_DEBUG_KEY), aggregateJson.get("source_debug_key"));
- assertEquals(
- Long.toUnsignedString(TRIGGER_DEBUG_KEY), aggregateJson.get("trigger_debug_key"));
+ assertEquals(SOURCE_DEBUG_KEY.toString(), aggregateJson.get("source_debug_key"));
+ assertEquals(TRIGGER_DEBUG_KEY.toString(), aggregateJson.get("trigger_debug_key"));
}
@Test
@@ -158,8 +158,7 @@ public class AggregateReportBodyTest {
assertEquals(REPORTING_ORIGIN, sharedInfoJson.get("reporting_origin"));
assertEquals(ATTRIBUTION_DESTINATION, sharedInfoJson.get("attribution_destination"));
assertEquals(SOURCE_REGISTRATION_TIME, sharedInfoJson.get("source_registration_time"));
- assertEquals(
- Long.toUnsignedString(SOURCE_DEBUG_KEY), aggregateJson.get("source_debug_key"));
+ assertEquals(SOURCE_DEBUG_KEY.toString(), aggregateJson.get("source_debug_key"));
assertNull(aggregateJson.opt("trigger_debug_key"));
}
@@ -177,8 +176,7 @@ public class AggregateReportBodyTest {
assertEquals(ATTRIBUTION_DESTINATION, sharedInfoJson.get("attribution_destination"));
assertEquals(SOURCE_REGISTRATION_TIME, sharedInfoJson.get("source_registration_time"));
assertNull(aggregateJson.opt("source_debug_key"));
- assertEquals(
- Long.toUnsignedString(TRIGGER_DEBUG_KEY), aggregateJson.get("trigger_debug_key"));
+ assertEquals(TRIGGER_DEBUG_KEY.toString(), aggregateJson.get("trigger_debug_key"));
}
@Test
@@ -196,13 +194,50 @@ public class AggregateReportBodyTest {
assertEncryptedPayload(aggregateServicePayloads);
}
- private void assertEncodedDebugPayload(JSONObject aggregateServicePayloads) throws Exception {
- final String encodedPayloadBase64 =
- (String) aggregateServicePayloads.get("debug_cleartext_payload");
- assertNotNull(encodedPayloadBase64);
+ @Test
+ public void testAggregationServicePayloadsJsonSerializationWithDebugKey() throws Exception {
+ AggregateReportBody aggregateReport =
+ createAggregateReportBodyExampleWithSingleTriggerDebugKey();
+
+ AggregateEncryptionKey key = AggregateCryptoFixture.getKey();
+ JSONArray aggregationServicePayloadsJson =
+ aggregateReport.aggregationServicePayloadsToJson(/* sharedInfo = */ null, key);
+
+ JSONObject aggregateServicePayloads = aggregationServicePayloadsJson.getJSONObject(0);
- final byte[] cborEncodedPayload = Base64.getDecoder().decode(encodedPayloadBase64);
- assertCborEncoded(cborEncodedPayload);
+ assertEquals(key.getKeyId(), aggregateServicePayloads.get("key_id"));
+ assertEquals(
+ AggregateCryptoConverter.encode(DEBUG_CLEARTEXT_PAYLOAD),
+ aggregateServicePayloads.opt("debug_cleartext_payload"));
+ assertEncodedDebugPayload(aggregateServicePayloads);
+ assertEncryptedPayload(aggregateServicePayloads);
+ }
+
+ @Test
+ public void testAggregationServicePayloadsJsonSerializationWithoutDebugKey() throws Exception {
+ AggregateReportBody aggregateReport = createAggregateReportBodyExampleWithNullDebugKeys();
+
+ AggregateEncryptionKey key = AggregateCryptoFixture.getKey();
+ JSONArray aggregationServicePayloadsJson =
+ aggregateReport.aggregationServicePayloadsToJson(/* sharedInfo = */ null, key);
+
+ JSONObject aggregateServicePayloads = aggregationServicePayloadsJson.getJSONObject(0);
+
+ assertEquals(key.getKeyId(), aggregateServicePayloads.get("key_id"));
+ assertNull(aggregateServicePayloads.opt("debug_cleartext_payload"));
+ assertEncodedDebugPayload(aggregateServicePayloads);
+ assertEncryptedPayload(aggregateServicePayloads);
+ }
+
+ private void assertEncodedDebugPayload(JSONObject aggregateServicePayloads) throws Exception {
+ if (!aggregateServicePayloads.isNull("debug_cleartext_payload")) {
+ final String encodedPayloadBase64 =
+ (String) aggregateServicePayloads.get("debug_cleartext_payload");
+ assertNotNull(encodedPayloadBase64);
+
+ final byte[] cborEncodedPayload = Base64.getDecoder().decode(encodedPayloadBase64);
+ assertCborEncoded(cborEncodedPayload);
+ }
}
private void assertEncryptedPayload(JSONObject aggregateServicePayloads) throws Exception {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java
index 9ff8b9b54..5d0a1372b 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportSenderTest.java
@@ -23,6 +23,8 @@ import android.net.Uri;
import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.google.common.truth.Truth;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Rule;
@@ -79,7 +81,7 @@ public class AggregateReportSenderTest {
createAggregateReportBodyExample1().toJson(AggregateCryptoFixture.getKey());
Uri reportingOrigin = Uri.parse(REPORTING_ORIGIN);
- AggregateReportSender aggregateReportSender = new AggregateReportSender();
+ AggregateReportSender aggregateReportSender = new AggregateReportSender(false);
AggregateReportSender spyAggregateReportSender = Mockito.spy(aggregateReportSender);
Mockito.doReturn(httpUrlConnection).when(spyAggregateReportSender)
@@ -98,8 +100,16 @@ public class AggregateReportSenderTest {
URL spyUrl = Mockito.spy(new URL("https://foo"));
Mockito.doReturn(mockConnection).when(spyUrl).openConnection();
- AggregateReportSender aggregateReportSender = new AggregateReportSender();
+ AggregateReportSender aggregateReportSender = new AggregateReportSender(false);
HttpURLConnection connection = aggregateReportSender.createHttpUrlConnection(spyUrl);
assertEquals(mockConnection, connection);
}
+
+ @Test
+ public void testDebugReportUriPath() {
+ Truth.assertThat(new AggregateReportSender(false).getReportUriPath())
+ .isEqualTo(AggregateReportSender.AGGREGATE_ATTRIBUTION_REPORT_URI_PATH);
+ Truth.assertThat(new AggregateReportSender(true).getReportUriPath())
+ .isEqualTo(AggregateReportSender.DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java
index 09e4a71b5..f0d3f8220 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerIntegrationTest.java
@@ -19,11 +19,12 @@ package com.android.adservices.service.measurement.reporting;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.AbstractDbIntegrationTest;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
import com.android.adservices.data.measurement.DbState;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
@@ -93,7 +94,8 @@ public class AggregateReportingJobHandlerIntegrationTest extends AbstractDbInteg
});
Mockito.doReturn(isEnrolled ? ENROLLMENT : null)
.when(mEnrollmentDao).getEnrollmentData(Mockito.any());
- DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
AggregateReportingJobHandler spyReportingService =
Mockito.spy(new AggregateReportingJobHandler(
mEnrollmentDao, datastoreManager, mockKeyManager));
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java
index 01a9da6da..d77bc6d2f 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandlerTest.java
@@ -42,6 +42,7 @@ import com.android.adservices.service.measurement.aggregation.AggregateCryptoFix
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKey;
import com.android.adservices.service.measurement.aggregation.AggregateEncryptionKeyManager;
import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONException;
import org.json.JSONObject;
@@ -72,8 +73,8 @@ public class AggregateReportingJobHandlerTest {
private static final String CLEARTEXT_PAYLOAD =
"{\"operation\":\"histogram\",\"data\":[{\"bucket\":1,\"value\":2}]}";
- private static final Long SOURCE_DEBUG_KEY = 237865L;
- private static final Long TRIGGER_DEBUG_KEY = 928762L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(237865L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(928762L);
protected static final Context sContext = ApplicationProvider.getApplicationContext();
DatastoreManager mDatastoreManager;
@@ -86,6 +87,7 @@ public class AggregateReportingJobHandlerTest {
AggregateReportingJobHandler mAggregateReportingJobHandler;
AggregateReportingJobHandler mSpyAggregateReportingJobHandler;
+ AggregateReportingJobHandler mSpyDebugAggregateReportingJobHandler;
class FakeDatasoreManager extends DatastoreManager {
@@ -118,6 +120,11 @@ public class AggregateReportingJobHandlerTest {
mAggregateReportingJobHandler = new AggregateReportingJobHandler(
mEnrollmentDao, mDatastoreManager, mockKeyManager);
mSpyAggregateReportingJobHandler = Mockito.spy(mAggregateReportingJobHandler);
+ mSpyDebugAggregateReportingJobHandler =
+ Mockito.spy(
+ new AggregateReportingJobHandler(
+ mEnrollmentDao, mDatastoreManager, mockKeyManager)
+ .setDebugReport(true));
}
@Test
@@ -147,13 +154,57 @@ public class AggregateReportingJobHandlerTest {
.when(mSpyAggregateReportingJobHandler)
.createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(aggregateReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, times(1)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markAggregateReportStatus(any(), anyInt());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void testSendReportForPendingDebugReportSuccess()
+ throws DatastoreException, IOException, JSONException {
+ AggregateReport aggregateReport =
+ new AggregateReport.Builder()
+ .setId("aggregateReportId")
+ .setStatus(AggregateReport.Status.PENDING)
+ .setDebugReportStatus(AggregateReport.DebugReportStatus.PENDING)
+ .setEnrollmentId(ENROLLMENT_ID)
+ .setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .build();
+ JSONObject aggregateReportBody =
+ new AggregateReportBody.Builder()
+ .setReportId(aggregateReport.getId())
+ .setDebugCleartextPayload(CLEARTEXT_PAYLOAD)
+ .build()
+ .toJson(AggregateCryptoFixture.getKey());
+
+ when(mMeasurementDao.getAggregateReport(aggregateReport.getId()))
+ .thenReturn(aggregateReport);
+ doReturn(HttpURLConnection.HTTP_OK)
+ .when(mSpyDebugAggregateReportingJobHandler)
+ .makeHttpPostRequest(Mockito.any(), Mockito.any());
+ doReturn(aggregateReportBody)
+ .when(mSpyDebugAggregateReportingJobHandler)
+ .createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
+
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateDebugReportDelivered(aggregateReport.getId());
+ Assert.assertEquals(
+ AdServicesStatusUtils.STATUS_SUCCESS,
+ mSpyDebugAggregateReportingJobHandler.performReport(
+ aggregateReport.getId(), AggregateCryptoFixture.getKey()));
+
+ verify(mMeasurementDao, times(1)).markAggregateDebugReportDelivered(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -184,13 +235,16 @@ public class AggregateReportingJobHandlerTest {
.when(mSpyAggregateReportingJobHandler)
.createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(aggregateReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, times(1)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -221,13 +275,16 @@ public class AggregateReportingJobHandlerTest {
.when(mSpyAggregateReportingJobHandler)
.createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(aggregateReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, times(1)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -259,13 +316,16 @@ public class AggregateReportingJobHandlerTest {
.when(mSpyAggregateReportingJobHandler)
.createReportJsonPayload(Mockito.any(), Mockito.any(), Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(aggregateReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(
+ aggregateReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, times(1)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -300,7 +360,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, never()).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, never()).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
@@ -322,7 +382,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, never()).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, never()).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
@@ -379,7 +439,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performScheduledPendingReportsInWindow(
1000, 1100));
- verify(mMeasurementDao, times(2)).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, times(2)).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(5)).begin();
verify(mTransaction, times(5)).end();
}
@@ -424,7 +484,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performScheduledPendingReportsInWindow(
1000, 1100));
- verify(mMeasurementDao, never()).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, never()).markAggregateReportStatus(any(), anyInt());
}
@Test
@@ -445,7 +505,7 @@ public class AggregateReportingJobHandlerTest {
mSpyAggregateReportingJobHandler.performReport(
aggregateReport.getId(), AggregateCryptoFixture.getKey()));
- verify(mMeasurementDao, never()).markAggregateReportDelivered(any());
+ verify(mMeasurementDao, never()).markAggregateReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java
index 9e1748915..d092a50f1 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/AggregateReportingJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.reporting;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_AGGREGATE_MAIN_REPORTING_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,33 +36,31 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Unit test for {@link AggregateReportingJobService
*/
public class AggregateReportingJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
- private static final long WAIT_IN_MILLIS = 50L;
+ private static final long WAIT_IN_MILLIS = 1_000L;
private DatastoreManager mMockDatastoreManager;
private JobScheduler mMockJobScheduler;
@@ -77,10 +76,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
@@ -97,10 +97,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ disableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(mock(JobParameters.class));
@@ -117,11 +118,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -148,11 +149,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -179,11 +180,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -210,11 +211,11 @@ public class AggregateReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -236,11 +237,34 @@ public class AggregateReportingJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ ExtendedMockito.doCallRealMethod()
+ .when(() -> AggregateReportingJobService.schedule(any(), any()));
+ AggregateReportingJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ });
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
- .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(AdServicesConfig.class)
.spyStatic(AggregateReportingJobService.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -252,6 +276,12 @@ public class AggregateReportingJobServiceTest {
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(4))
+ .when(AdServicesConfig::getMeasurementAggregateMainReportingJobPeriodMs);
+ ExtendedMockito.doReturn("http://example.com")
+ .when(AdServicesConfig::getMeasurementAggregateEncryptionKeyCoordinatorUrl);
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing()
@@ -273,10 +303,10 @@ public class AggregateReportingJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_aggregate_reporting_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value)
+ .when(mockFlags)
+ .getMeasurementJobAggregateReportingKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/DebugReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/DebugReportingJobServiceTest.java
new file mode 100644
index 000000000..c18f231b1
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/DebugReportingJobServiceTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.measurement.reporting;
+
+import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_DEBUG_REPORT_JOB_ID;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.Context;
+
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Unit test for {@link DebugReportingJobService
+ */
+public class DebugReportingJobServiceTest {
+
+ private static final long WAIT_IN_MILLIS = 1000L;
+
+ private DatastoreManager mMockDatastoreManager;
+ private JobScheduler mMockJobScheduler;
+
+ private DebugReportingJobService mSpyService;
+
+ @Before
+ public void setUp() {
+ mSpyService = spy(new DebugReportingJobService());
+ mMockDatastoreManager = mock(DatastoreManager.class);
+ mMockJobScheduler = mock(JobScheduler.class);
+ }
+
+ @Test
+ public void onStartJob_killSwitchOn() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ // Execute
+ boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
+
+ // Validate
+ assertFalse(result);
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mMockDatastoreManager, never()).runInTransactionWithResult(any());
+ verify(mSpyService, times(1)).jobFinished(any(), eq(false));
+ verify(mMockJobScheduler, times(1)).cancel(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void onStartJob_killSwitchOff() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ ExtendedMockito.doNothing()
+ .when(
+ () ->
+ DebugReportingJobService.scheduleIfNeeded(
+ any(), anyBoolean()));
+
+ // Execute
+ boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
+
+ // Validate
+ assertTrue(result);
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ verify(mMockDatastoreManager, times(2)).runInTransactionWithResult(any());
+ verify(mSpyService, times(1)).jobFinished(any(), anyBoolean());
+ verify(mMockJobScheduler, never()).cancel(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ enableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+
+ // Execute
+ DebugReportingJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DebugReportingJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, never())
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+
+ // Execute
+ DebugReportingJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DebugReportingJobService.schedule(any(), any()), never());
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ final JobInfo mockJobInfo = mock(JobInfo.class);
+ doReturn(mockJobInfo)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+
+ // Execute
+ DebugReportingJobService.scheduleIfNeeded(
+ mockContext, /* forceSchedule = */ true);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DebugReportingJobService.schedule(any(), any()), times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ @Test
+ public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
+ throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ disableKillSwitch();
+
+ final Context mockContext = mock(Context.class);
+ doReturn(mMockJobScheduler)
+ .when(mockContext)
+ .getSystemService(JobScheduler.class);
+ doReturn(/* noJobInfo = */ null)
+ .when(mMockJobScheduler)
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+
+ // Execute
+ DebugReportingJobService.scheduleIfNeeded(mockContext, false);
+
+ // Validate
+ // Allow background thread to execute
+ Thread.sleep(WAIT_IN_MILLIS);
+ ExtendedMockito.verify(
+ () -> DebugReportingJobService.schedule(any(), any()), times(1));
+ verify(mMockJobScheduler, times(1))
+ .getPendingJob(eq(MEASUREMENT_DEBUG_REPORT_JOB_ID));
+ });
+ }
+
+ private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(AdServicesConfig.class)
+ .spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
+ .spyStatic(DebugReportingJobService.class)
+ .spyStatic(FlagsFactory.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ try {
+ // Setup mock everything in job
+ mMockDatastoreManager = mock(DatastoreManager.class);
+ doReturn(Optional.empty())
+ .when(mMockDatastoreManager)
+ .runInTransactionWithResult(any());
+ doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
+ doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
+ doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
+ ExtendedMockito.doReturn(mMockDatastoreManager)
+ .when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
+ ExtendedMockito.doNothing().when(() -> DebugReportingJobService.schedule(any(), any()));
+
+ // Execute
+ execute.run();
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ private void enableKillSwitch() {
+ toggleKillSwitch(true);
+ }
+
+ private void disableKillSwitch() {
+ toggleKillSwitch(false);
+ }
+
+ private void toggleKillSwitch(boolean value) {
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getMeasurementJobDebugReportingKillSwitch();
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java
index 0fb5730cc..84f2bbfa7 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventFallbackReportingJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.reporting;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_EVENT_FALLBACK_REPORTING_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,33 +36,31 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Unit test for {@link EventFallbackReportingJobService
*/
public class EventFallbackReportingJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
- private static final long WAIT_IN_MILLIS = 50L;
+ private static final long WAIT_IN_MILLIS = 1_000L;
private DatastoreManager mMockDatastoreManager;
private JobScheduler mMockJobScheduler;
@@ -77,10 +76,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -97,11 +97,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
ExtendedMockito.doNothing()
.when(
() ->
@@ -124,11 +124,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -155,11 +155,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -186,11 +186,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -218,11 +218,11 @@ public class EventFallbackReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -245,11 +245,34 @@ public class EventFallbackReportingJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ ExtendedMockito.doCallRealMethod()
+ .when(() -> EventFallbackReportingJobService.schedule(any(), any()));
+ EventFallbackReportingJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ });
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
+ .spyStatic(AdServicesConfig.class)
.spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
.spyStatic(EventFallbackReportingJobService.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -261,6 +284,12 @@ public class EventFallbackReportingJobServiceTest {
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(4))
+ .when(AdServicesConfig::getMeasurementEventMainReportingJobPeriodMs);
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(24))
+ .when(AdServicesConfig::getMeasurementEventFallbackReportingJobPeriodMs);
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing()
@@ -282,10 +311,10 @@ public class EventFallbackReportingJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_event_fallback_reporting_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value)
+ .when(mockFlags)
+ .getMeasurementJobEventFallbackReportingKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
index cd590525d..c12ee6ea6 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
@@ -19,6 +19,8 @@ package com.android.adservices.service.measurement.reporting;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
@@ -26,94 +28,72 @@ import org.junit.Test;
public class EventReportPayloadTest {
private static final String ATTRIBUTION_DESTINATION = "https://toasters.example";
- private static final String SOURCE_EVENT_ID = "12345";
- private static final String TRIGGER_DATA = "2";
+ private static final UnsignedLong SOURCE_EVENT_ID = new UnsignedLong(12345L);
+ private static final UnsignedLong TRIGGER_DATA = new UnsignedLong(2L);
private static final String REPORT_ID = "678";
private static final String SOURCE_TYPE = "event";
private static final double RANDOMIZED_TRIGGER_RATE = 0.0024;
- private static final Long SOURCE_DEBUG_KEY = 3894783L;
- private static final Long TRIGGER_DEBUG_KEY = 2387222L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(3894783L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(2387222L);
- private EventReportPayload createEventReportPayloadExample1() {
+ private static EventReportPayload createEventReportPayload(UnsignedLong triggerData,
+ UnsignedLong sourceDebugKey, UnsignedLong triggerDebugKey) {
return new EventReportPayload.Builder()
.setAttributionDestination(ATTRIBUTION_DESTINATION)
.setSourceEventId(SOURCE_EVENT_ID)
- .setTriggerData(TRIGGER_DATA)
+ .setTriggerData(triggerData)
.setReportId(REPORT_ID)
.setSourceType(SOURCE_TYPE)
.setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
- .setSourceDebugKey(SOURCE_DEBUG_KEY)
- .setTriggerDebugKey(TRIGGER_DEBUG_KEY)
- .build();
- }
-
- private EventReportPayload createEventReportPayloadWithNullDebugKeys() {
- return new EventReportPayload.Builder()
- .setAttributionDestination(ATTRIBUTION_DESTINATION)
- .setSourceEventId(SOURCE_EVENT_ID)
- .setTriggerData(TRIGGER_DATA)
- .setReportId(REPORT_ID)
- .setSourceType(SOURCE_TYPE)
- .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
- .setSourceDebugKey(null)
- .setTriggerDebugKey(null)
- .build();
- }
-
- private EventReportPayload createEventReportPayloadWithSingleTriggerDebugKeys() {
- return new EventReportPayload.Builder()
- .setAttributionDestination(ATTRIBUTION_DESTINATION)
- .setSourceEventId(SOURCE_EVENT_ID)
- .setTriggerData(TRIGGER_DATA)
- .setReportId(REPORT_ID)
- .setSourceType(SOURCE_TYPE)
- .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
- .setTriggerDebugKey(TRIGGER_DEBUG_KEY)
- .build();
- }
-
- private EventReportPayload createEventReportPayloadWithSingleSourceDebugKeys() {
- return new EventReportPayload.Builder()
- .setAttributionDestination(ATTRIBUTION_DESTINATION)
- .setSourceEventId(SOURCE_EVENT_ID)
- .setTriggerData(TRIGGER_DATA)
- .setReportId(REPORT_ID)
- .setSourceType(SOURCE_TYPE)
- .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
- .setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setSourceDebugKey(sourceDebugKey)
+ .setTriggerDebugKey(triggerDebugKey)
.build();
}
@Test
public void testEventPayloadJsonSerialization() throws JSONException {
- EventReportPayload eventReport = createEventReportPayloadExample1();
+ EventReportPayload eventReport =
+ createEventReportPayload(TRIGGER_DATA, SOURCE_DEBUG_KEY, TRIGGER_DEBUG_KEY);
JSONObject eventPayloadReportJson = eventReport.toJson();
assertEquals(ATTRIBUTION_DESTINATION,
eventPayloadReportJson.get("attribution_destination"));
- assertEquals(SOURCE_EVENT_ID, eventPayloadReportJson.get("source_event_id"));
- assertEquals(TRIGGER_DATA, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(TRIGGER_DATA.toString(), eventPayloadReportJson.get("trigger_data"));
assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(RANDOMIZED_TRIGGER_RATE,
eventPayloadReportJson.get("randomized_trigger_rate"));
+ assertEquals(SOURCE_DEBUG_KEY.toString(), eventPayloadReportJson.get("source_debug_key"));
+ assertEquals(TRIGGER_DEBUG_KEY.toString(), eventPayloadReportJson.get("trigger_debug_key"));
+ }
+
+ @Test
+ public void testEventPayloadJsonSerializationWithNullDebugKeys() throws JSONException {
+ EventReportPayload eventReport = createEventReportPayload(TRIGGER_DATA, null, null);
+ JSONObject eventPayloadReportJson = eventReport.toJson();
+
assertEquals(
- Long.toUnsignedString(SOURCE_DEBUG_KEY),
- eventPayloadReportJson.get("source_debug_key"));
+ ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(TRIGGER_DATA.toString(), eventPayloadReportJson.get("trigger_data"));
+ assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
+ assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(
- Long.toUnsignedString(TRIGGER_DEBUG_KEY),
- eventPayloadReportJson.get("trigger_debug_key"));
+ RANDOMIZED_TRIGGER_RATE, eventPayloadReportJson.get("randomized_trigger_rate"));
+ assertNull(eventPayloadReportJson.opt("source_debug_key"));
+ assertNull(eventPayloadReportJson.opt("trigger_debug_key"));
}
@Test
- public void testEventPayloadJsonSerializationWithNullDebugKeys() throws JSONException {
- EventReportPayload eventReport = createEventReportPayloadWithNullDebugKeys();
+ public void testEventPayloadJsonSerializationWithNullTriggerData() throws JSONException {
+ EventReportPayload eventReport = createEventReportPayload(null, null, null);
JSONObject eventPayloadReportJson = eventReport.toJson();
assertEquals(
ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
- assertEquals(SOURCE_EVENT_ID, eventPayloadReportJson.get("source_event_id"));
- assertEquals(TRIGGER_DATA, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(new UnsignedLong(0L).toString(), eventPayloadReportJson.get("trigger_data"));
assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(
@@ -124,39 +104,66 @@ public class EventReportPayloadTest {
@Test
public void testEventPayloadJsonSerializationWithSingleTriggerDebugKeys() throws JSONException {
- EventReportPayload eventReport = createEventReportPayloadWithSingleTriggerDebugKeys();
+ EventReportPayload eventReport =
+ createEventReportPayload(TRIGGER_DATA, null, TRIGGER_DEBUG_KEY);
JSONObject eventPayloadReportJson = eventReport.toJson();
assertEquals(
ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
- assertEquals(SOURCE_EVENT_ID, eventPayloadReportJson.get("source_event_id"));
- assertEquals(TRIGGER_DATA, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(TRIGGER_DATA.toString(), eventPayloadReportJson.get("trigger_data"));
assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(
RANDOMIZED_TRIGGER_RATE, eventPayloadReportJson.get("randomized_trigger_rate"));
assertNull(eventPayloadReportJson.opt("source_debug_key"));
+ assertEquals(TRIGGER_DEBUG_KEY.toString(), eventPayloadReportJson.get("trigger_debug_key"));
+ }
+
+ @Test
+ public void testEventPayloadJsonSerialization_debugKeysSourceEventIdAndTriggerDataUse64thBit()
+ throws JSONException {
+ String unsigned64BitIntString = "18446744073709551615";
+ UnsignedLong signed64BitInt = new UnsignedLong(-1L);
+ EventReportPayload eventReport = new EventReportPayload.Builder()
+ .setAttributionDestination(ATTRIBUTION_DESTINATION)
+ .setSourceEventId(signed64BitInt)
+ .setTriggerData(signed64BitInt)
+ .setReportId(REPORT_ID)
+ .setSourceType(SOURCE_TYPE)
+ .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
+ .setSourceDebugKey(signed64BitInt)
+ .setTriggerDebugKey(signed64BitInt)
+ .build();
+ JSONObject eventPayloadReportJson = eventReport.toJson();
+
assertEquals(
- Long.toUnsignedString(TRIGGER_DEBUG_KEY),
- eventPayloadReportJson.get("trigger_debug_key"));
+ ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
+ assertEquals(unsigned64BitIntString, eventPayloadReportJson.get("source_event_id"));
+ assertEquals(unsigned64BitIntString, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
+ assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
+ assertEquals(
+ RANDOMIZED_TRIGGER_RATE, eventPayloadReportJson.get("randomized_trigger_rate"));
+ assertEquals(unsigned64BitIntString, eventPayloadReportJson.opt("source_debug_key"));
+ assertEquals(unsigned64BitIntString, eventPayloadReportJson.get("trigger_debug_key"));
}
@Test
public void testEventPayloadJsonSerializationWithSingleSourceDebugKeys() throws JSONException {
- EventReportPayload eventReport = createEventReportPayloadWithSingleSourceDebugKeys();
+ EventReportPayload eventReport =
+ createEventReportPayload(TRIGGER_DATA, SOURCE_DEBUG_KEY, null);
JSONObject eventPayloadReportJson = eventReport.toJson();
assertEquals(
ATTRIBUTION_DESTINATION, eventPayloadReportJson.get("attribution_destination"));
- assertEquals(SOURCE_EVENT_ID, eventPayloadReportJson.get("source_event_id"));
- assertEquals(TRIGGER_DATA, eventPayloadReportJson.get("trigger_data"));
+ assertEquals(SOURCE_EVENT_ID.toString(), eventPayloadReportJson.get("source_event_id"));
+ assertEquals(TRIGGER_DATA.toString(), eventPayloadReportJson.get("trigger_data"));
assertEquals(REPORT_ID, eventPayloadReportJson.get("report_id"));
assertEquals(SOURCE_TYPE, eventPayloadReportJson.get("source_type"));
assertEquals(
RANDOMIZED_TRIGGER_RATE, eventPayloadReportJson.get("randomized_trigger_rate"));
assertNull(eventPayloadReportJson.opt("trigger_debug_key"));
- assertEquals(
- Long.toUnsignedString(SOURCE_DEBUG_KEY),
- eventPayloadReportJson.get("source_debug_key"));
+ assertEquals(SOURCE_DEBUG_KEY.toString(), eventPayloadReportJson.get("source_debug_key"));
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java
index 6d96c9a95..0fd9d1828 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportSenderTest.java
@@ -16,10 +16,14 @@
package com.android.adservices.service.measurement.reporting;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import android.net.Uri;
+import com.android.adservices.service.measurement.util.UnsignedLong;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
@@ -33,8 +37,8 @@ import java.net.HttpURLConnection;
public class EventReportSenderTest {
private static final String ATTRIBUTION_DESTINATION = "https://toasters.example";
- private static final String SOURCE_EVENT_ID = "12345";
- private static final String TRIGGER_DATA = "2";
+ private static final UnsignedLong SOURCE_EVENT_ID = new UnsignedLong(12345L);
+ private static final UnsignedLong TRIGGER_DATA = new UnsignedLong(2L);
private static final String REPORT_ID = "678";
private static final String SOURCE_TYPE = "event";
private static final double RANDOMIZED_TRIGGER_RATE = 0.0024;
@@ -68,7 +72,7 @@ public class EventReportSenderTest {
Uri reportingOrigin = Uri.parse("https://ad-tech.example");
JSONObject eventReportJson = createEventReportPayloadExample1().toJson();
- EventReportSender eventReportSender = new EventReportSender();
+ EventReportSender eventReportSender = new EventReportSender(false);
EventReportSender spyEventReportSender = Mockito.spy(eventReportSender);
Mockito.doReturn(httpUrlConnection).when(spyEventReportSender)
@@ -80,4 +84,12 @@ public class EventReportSenderTest {
assertEquals(outputStream.toString(), eventReportJson.toString());
assertEquals(responseCode, 200);
}
+
+ @Test
+ public void testDebugReportUriPath() {
+ assertThat(new EventReportSender(false).getReportUriPath())
+ .isEqualTo(EventReportSender.EVENT_ATTRIBUTION_REPORT_URI_PATH);
+ assertThat(new EventReportSender(true).getReportUriPath())
+ .isEqualTo(EventReportSender.DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java
index 76075c291..18d25f0fe 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerIntegrationTest.java
@@ -16,11 +16,12 @@
package com.android.adservices.service.measurement.reporting;
+import com.android.adservices.data.DbTestUtil;
import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.AbstractDbIntegrationTest;
import com.android.adservices.data.measurement.DatastoreManager;
-import com.android.adservices.data.measurement.DatastoreManagerFactory;
import com.android.adservices.data.measurement.DbState;
+import com.android.adservices.data.measurement.SQLDatastoreManager;
import com.android.adservices.service.enrollment.EnrollmentData;
import org.json.JSONException;
@@ -39,6 +40,7 @@ import java.util.Objects;
/** Integration tests for {@link EventReportingJobHandler} */
@RunWith(Parameterized.class)
public class EventReportingJobHandlerIntegrationTest extends AbstractDbIntegrationTest {
+
private static final EnrollmentData ENROLLMENT = new EnrollmentData.Builder()
.setAttributionReportingUrl(List.of("https://ad-tech.com"))
.build();
@@ -74,7 +76,8 @@ public class EventReportingJobHandlerIntegrationTest extends AbstractDbIntegrati
Mockito.doReturn(isEnrolled ? ENROLLMENT : null)
.when(mEnrollmentDao).getEnrollmentData(Mockito.any());
- DatastoreManager datastoreManager = DatastoreManagerFactory.getDatastoreManager(sContext);
+ DatastoreManager datastoreManager =
+ new SQLDatastoreManager(DbTestUtil.getDbHelperForTest());
EventReportingJobHandler spyReportingService =
Mockito.spy(new EventReportingJobHandler(mEnrollmentDao, datastoreManager));
try {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java
index 8f8fecdaa..e4a3bc95b 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobHandlerTest.java
@@ -17,6 +17,7 @@
package com.android.adservices.service.measurement.reporting;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -36,6 +37,8 @@ import com.android.adservices.data.measurement.IMeasurementDao;
import com.android.adservices.data.measurement.ITransaction;
import com.android.adservices.service.enrollment.EnrollmentData;
import com.android.adservices.service.measurement.EventReport;
+import com.android.adservices.service.measurement.aggregation.AggregateReport;
+import com.android.adservices.service.measurement.util.UnsignedLong;
import org.json.JSONException;
import org.json.JSONObject;
@@ -54,8 +57,8 @@ import java.util.List;
/** Unit test for {@link EventReportingJobHandler} */
@RunWith(MockitoJUnitRunner.class)
public class EventReportingJobHandlerTest {
- private static final Long SOURCE_DEBUG_KEY = 237865L;
- private static final Long TRIGGER_DEBUG_KEY = 928762L;
+ private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(237865L);
+ private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(928762L);
private static final EnrollmentData ENROLLMENT = new EnrollmentData.Builder()
.setAttributionReportingUrl(List.of("https://ad-tech.com"))
@@ -72,6 +75,7 @@ public class EventReportingJobHandlerTest {
EventReportingJobHandler mEventReportingJobHandler;
EventReportingJobHandler mSpyEventReportingJobHandler;
+ EventReportingJobHandler mSpyDebugEventReportingJobHandler;
class FakeDatasoreManager extends DatastoreManager {
@Override
@@ -91,6 +95,10 @@ public class EventReportingJobHandlerTest {
when(mEnrollmentDao.getEnrollmentData(any())).thenReturn(ENROLLMENT);
mEventReportingJobHandler = new EventReportingJobHandler(mEnrollmentDao, mDatastoreManager);
mSpyEventReportingJobHandler = Mockito.spy(mEventReportingJobHandler);
+ mSpyDebugEventReportingJobHandler =
+ Mockito.spy(
+ new EventReportingJobHandler(mEnrollmentDao, mDatastoreManager)
+ .setDebugReport(true));
}
@Test
@@ -99,12 +107,17 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setSourceDebugKey(SOURCE_DEBUG_KEY)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_OK)
@@ -114,13 +127,53 @@ public class EventReportingJobHandlerTest {
.when(mSpyEventReportingJobHandler)
.createReportJsonPayload(Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(eventReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(eventReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, times(1)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markEventReportStatus(any(), anyInt());
+ verify(mTransaction, times(2)).begin();
+ verify(mTransaction, times(2)).end();
+ }
+
+ @Test
+ public void testSendReportForPendingDebugReportSuccess()
+ throws DatastoreException, IOException, JSONException {
+ EventReport eventReport =
+ new EventReport.Builder()
+ .setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
+ .setStatus(EventReport.Status.PENDING)
+ .setDebugReportStatus(EventReport.DebugReportStatus.PENDING)
+ .setSourceDebugKey(SOURCE_DEBUG_KEY)
+ .setTriggerDebugKey(TRIGGER_DEBUG_KEY)
+ .build();
+ JSONObject eventReportPayload =
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
+
+ when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
+ doReturn(HttpURLConnection.HTTP_OK)
+ .when(mSpyDebugEventReportingJobHandler)
+ .makeHttpPostRequest(Mockito.any(), Mockito.any());
+ doReturn(eventReportPayload)
+ .when(mSpyDebugEventReportingJobHandler)
+ .createReportJsonPayload(Mockito.any());
+
+ doNothing().when(mMeasurementDao).markEventDebugReportDelivered(eventReport.getId());
+
+ Assert.assertEquals(
+ AdServicesStatusUtils.STATUS_SUCCESS,
+ mSpyDebugEventReportingJobHandler.performReport(eventReport.getId()));
+
+ verify(mMeasurementDao, times(1)).markEventDebugReportDelivered(any());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -131,11 +184,16 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setTriggerDebugKey(TRIGGER_DEBUG_KEY)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_OK)
@@ -145,13 +203,15 @@ public class EventReportingJobHandlerTest {
.when(mSpyEventReportingJobHandler)
.createReportJsonPayload(Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(eventReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(eventReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, times(1)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -162,11 +222,16 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setSourceDebugKey(SOURCE_DEBUG_KEY)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_OK)
@@ -176,13 +241,15 @@ public class EventReportingJobHandlerTest {
.when(mSpyEventReportingJobHandler)
.createReportJsonPayload(Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(eventReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(eventReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, times(1)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -193,12 +260,17 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setSourceDebugKey(null)
.setTriggerDebugKey(null)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_OK)
@@ -208,12 +280,14 @@ public class EventReportingJobHandlerTest {
.when(mSpyEventReportingJobHandler)
.createReportJsonPayload(Mockito.any());
- doNothing().when(mMeasurementDao).markAggregateReportDelivered(eventReport.getId());
+ doNothing()
+ .when(mMeasurementDao)
+ .markAggregateReportStatus(eventReport.getId(), AggregateReport.Status.DELIVERED);
Assert.assertEquals(
AdServicesStatusUtils.STATUS_SUCCESS,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, times(1)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(1)).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(2)).begin();
verify(mTransaction, times(2)).end();
}
@@ -224,10 +298,15 @@ public class EventReportingJobHandlerTest {
EventReport eventReport =
new EventReport.Builder()
.setId("eventReportId")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.build();
JSONObject eventReportPayload =
- new EventReportPayload.Builder().setReportId(eventReport.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport.getId())
+ .setSourceEventId(eventReport.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getEventReport(eventReport.getId())).thenReturn(eventReport);
doReturn(HttpURLConnection.HTTP_BAD_REQUEST)
@@ -241,7 +320,7 @@ public class EventReportingJobHandlerTest {
AdServicesStatusUtils.STATUS_IO_ERROR,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, never()).markEventReportDelivered(any());
+ verify(mMeasurementDao, never()).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
@@ -259,7 +338,7 @@ public class EventReportingJobHandlerTest {
AdServicesStatusUtils.STATUS_INVALID_ARGUMENT,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, never()).markEventReportDelivered(any());
+ verify(mMeasurementDao, never()).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
@@ -270,19 +349,29 @@ public class EventReportingJobHandlerTest {
EventReport eventReport1 =
new EventReport.Builder()
.setId("eventReport1")
+ .setSourceEventId(new UnsignedLong(1234L))
.setStatus(EventReport.Status.PENDING)
.setReportTime(1000L)
.build();
JSONObject eventReportPayload1 =
- new EventReportPayload.Builder().setReportId(eventReport1.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport1.getId())
+ .setSourceEventId(eventReport1.getSourceEventId())
+ .build()
+ .toJson();
EventReport eventReport2 =
new EventReport.Builder()
.setId("eventReport2")
+ .setSourceEventId(new UnsignedLong(12345L))
.setStatus(EventReport.Status.PENDING)
.setReportTime(1100L)
.build();
JSONObject eventReportPayload2 =
- new EventReportPayload.Builder().setReportId(eventReport2.getId()).build().toJson();
+ new EventReportPayload.Builder()
+ .setReportId(eventReport2.getId())
+ .setSourceEventId(eventReport2.getSourceEventId())
+ .build()
+ .toJson();
when(mMeasurementDao.getPendingEventReportIdsInWindow(1000, 1100))
.thenReturn(List.of(eventReport1.getId(), eventReport2.getId()));
@@ -301,7 +390,7 @@ public class EventReportingJobHandlerTest {
Assert.assertTrue(
mSpyEventReportingJobHandler.performScheduledPendingReportsInWindow(1000, 1100));
- verify(mMeasurementDao, times(2)).markEventReportDelivered(any());
+ verify(mMeasurementDao, times(2)).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(5)).begin();
verify(mTransaction, times(5)).end();
}
@@ -320,7 +409,7 @@ public class EventReportingJobHandlerTest {
AdServicesStatusUtils.STATUS_INTERNAL_ERROR,
mSpyEventReportingJobHandler.performReport(eventReport.getId()));
- verify(mMeasurementDao, never()).markEventReportDelivered(any());
+ verify(mMeasurementDao, never()).markEventReportStatus(any(), anyInt());
verify(mTransaction, times(1)).begin();
verify(mTransaction, times(1)).end();
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java
index bfae948a6..4cee88d06 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportingJobServiceTest.java
@@ -19,6 +19,7 @@ package com.android.adservices.service.measurement.reporting;
import static com.android.adservices.service.AdServicesConfig.MEASUREMENT_EVENT_MAIN_REPORTING_JOB_ID;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,32 +36,30 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.Context;
-import android.provider.DeviceConfig;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.measurement.DatastoreManager;
import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.service.AdServicesConfig;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.compatibility.common.util.TestUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Unit test for {@link EventReportingJobService
*/
public class EventReportingJobServiceTest {
- // This rule is used for configuring P/H flags
- @Rule
- public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
- new TestableDeviceConfig.TestableDeviceConfigRule();
-
private static final long WAIT_IN_MILLIS = 50L;
private DatastoreManager mMockDatastoreManager;
@@ -77,10 +76,11 @@ public class EventReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOn() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
+ // Setup
+ enableKillSwitch();
+
// Execute
boolean result = mSpyService.onStartJob(Mockito.mock(JobParameters.class));
@@ -97,11 +97,11 @@ public class EventReportingJobServiceTest {
@Test
public void onStartJob_killSwitchOff() throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
ExtendedMockito.doNothing()
.when(
() ->
@@ -124,11 +124,11 @@ public class EventReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOn_dontSchedule() throws Exception {
- enableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ enableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -155,11 +155,11 @@ public class EventReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_dontForceSchedule_dontSchedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -186,11 +186,11 @@ public class EventReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyExecuted_forceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -217,11 +217,11 @@ public class EventReportingJobServiceTest {
@Test
public void scheduleIfNeeded_killSwitchOff_previouslyNotExecuted_dontForceSchedule_schedule()
throws Exception {
- disableKillSwitch();
-
runWithMocks(
() -> {
// Setup
+ disableKillSwitch();
+
final Context mockContext = mock(Context.class);
doReturn(mMockJobScheduler)
.when(mockContext)
@@ -243,11 +243,34 @@ public class EventReportingJobServiceTest {
});
}
+ @Test
+ public void testSchedule_jobInfoIsPersisted() throws Exception {
+ runWithMocks(
+ () -> {
+ // Setup
+ final JobScheduler jobScheduler = mock(JobScheduler.class);
+ final ArgumentCaptor<JobInfo> captor = ArgumentCaptor.forClass(JobInfo.class);
+
+ // Execute
+ ExtendedMockito.doCallRealMethod()
+ .when(() -> EventReportingJobService.schedule(any(), any()));
+ EventReportingJobService.schedule(mock(Context.class), jobScheduler);
+
+ // Validate
+ verify(jobScheduler, times(1)).schedule(captor.capture());
+ assertNotNull(captor.getValue());
+ assertTrue(captor.getValue().isPersisted());
+ });
+ }
+
private void runWithMocks(TestUtils.RunnableWithThrow execute) throws Exception {
MockitoSession session =
ExtendedMockito.mockitoSession()
+ .spyStatic(AdServicesConfig.class)
.spyStatic(DatastoreManagerFactory.class)
+ .spyStatic(EnrollmentDao.class)
.spyStatic(EventReportingJobService.class)
+ .spyStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.startMocking();
try {
@@ -259,6 +282,10 @@ public class EventReportingJobServiceTest {
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
doReturn(mMockJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doReturn(Mockito.mock(Context.class)).when(mSpyService).getApplicationContext();
+ ExtendedMockito.doReturn(TimeUnit.HOURS.toMillis(4))
+ .when(AdServicesConfig::getMeasurementEventMainReportingJobPeriodMs);
+ ExtendedMockito.doReturn(mock(EnrollmentDao.class))
+ .when(() -> EnrollmentDao.getInstance(any()));
ExtendedMockito.doReturn(mMockDatastoreManager)
.when(() -> DatastoreManagerFactory.getDatastoreManager(any()));
ExtendedMockito.doNothing().when(() -> EventReportingJobService.schedule(any(), any()));
@@ -279,10 +306,8 @@ public class EventReportingJobServiceTest {
}
private void toggleKillSwitch(boolean value) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_ADSERVICES,
- "measurement_job_event_reporting_kill_switch",
- Boolean.toString(value),
- /* makeDefault */ false);
+ Flags mockFlags = Mockito.mock(Flags.class);
+ ExtendedMockito.doReturn(mockFlags).when(FlagsFactory::getFlags);
+ ExtendedMockito.doReturn(value).when(mockFlags).getMeasurementJobEventReportingKillSwitch();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java
index ffe8b06f0..72e21704e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/EnrollmentTest.java
@@ -52,7 +52,7 @@ public final class EnrollmentTest {
@Test
public void testMaybeGetEnrollmentId_enrollmentDataNull() {
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(REGISTRATION_URI.toString())))
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(REGISTRATION_URI)))
.thenReturn(null);
assertEquals(
Optional.empty(),
@@ -61,7 +61,7 @@ public final class EnrollmentTest {
@Test
public void testMaybeGetEnrollmentId_enrollmentDataNonNull() {
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(REGISTRATION_URI.toString())))
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(REGISTRATION_URI)))
.thenReturn(ENROLLMENT);
assertEquals(
Optional.of(ENROLLMENT.getEnrollmentId()),
@@ -87,7 +87,7 @@ public final class EnrollmentTest {
@Test
public void testMaybeGetReportingOrigin_attributionReportingUrlEmpty() {
EnrollmentData enrollment = new EnrollmentData.Builder().build();
- when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(ENROLLMENT_ID)))
+ when(mEnrollmentDao.getEnrollmentDataFromMeasurementUrl(eq(Uri.parse(ENROLLMENT_ID))))
.thenReturn(enrollment);
assertEquals(
Optional.empty(),
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java
index cf07d1361..a5e7094a5 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/FilterTest.java
@@ -19,7 +19,7 @@ package com.android.adservices.service.measurement.util;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.android.adservices.service.measurement.aggregation.AggregateFilterData;
+import com.android.adservices.service.measurement.FilterData;
import org.junit.Test;
@@ -31,83 +31,170 @@ import java.util.Map;
public class FilterTest {
@Test
- public void testIsFilterMatchReturnTrue() {
+ public void testIsFilterMatch_nonEmptyValues_returnTrue() {
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
Map<String, List<String>> triggerFilterMap = new HashMap<>();
triggerFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
triggerFilterMap.put("product", Arrays.asList("1234", "2345"));
triggerFilterMap.put("id", Arrays.asList("1", "2"));
- AggregateFilterData triggerFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
assertTrue(Filter.isFilterMatch(sourceFilter, triggerFilter, true));
}
@Test
- public void testIsFilterMatchReturnFalse() {
+ public void testIsFilterMatch_nonEmptyValues_returnFalse() {
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
Map<String, List<String>> triggerFilterMap = new HashMap<>();
triggerFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
- triggerFilterMap.put("product", Arrays.asList("1", "2")); // doesn't match.
+ // Doesn't match
+ triggerFilterMap.put("product", Arrays.asList("1", "2"));
triggerFilterMap.put("id", Arrays.asList("1", "2"));
- AggregateFilterData triggerFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
assertFalse(Filter.isFilterMatch(sourceFilter, triggerFilter, true));
}
@Test
- public void testIsNotFilterMatchReturnTrue() {
+ public void testIsFilterMatch_withEmptyValues_returnTrue() {
+ Map<String, List<String>> sourceFilterMap = new HashMap<>();
+ sourceFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ sourceFilterMap.put("product", Collections.emptyList());
+ sourceFilterMap.put("ctid", Collections.singletonList("id"));
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+
+ Map<String, List<String>> triggerFilterMap = new HashMap<>();
+ triggerFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ triggerFilterMap.put("product", Collections.emptyList());
+ triggerFilterMap.put("id", Arrays.asList("1", "2"));
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+
+ assertTrue(Filter.isFilterMatch(sourceFilter, triggerFilter, true));
+ }
+
+ @Test
+ public void testIsFilterMatch_withEmptyValues_returnFalse() {
+ Map<String, List<String>> sourceFilterMap = new HashMap<>();
+ sourceFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ sourceFilterMap.put("product", Arrays.asList("1234", "234"));
+ sourceFilterMap.put("ctid", Collections.singletonList("id"));
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+
+ Map<String, List<String>> triggerFilterMap = new HashMap<>();
+ triggerFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ // Doesn't match
+ triggerFilterMap.put("product", Collections.emptyList());
+ triggerFilterMap.put("id", Arrays.asList("1", "2"));
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+
+ assertFalse(Filter.isFilterMatch(sourceFilter, triggerFilter, true));
+ }
+
+ @Test
+ public void testIsFilterMatch_withNegation_nonEmptyValues_returnTrue() {
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
Map<String, List<String>> triggerFilterMap = new HashMap<>();
triggerFilterMap.put("conversion_subdomain", Collections.singletonList("electronics"));
- triggerFilterMap.put("product", Arrays.asList("1", "2")); // doesn't match.
+ // Doesn't match
+ triggerFilterMap.put("product", Arrays.asList("1", "2"));
triggerFilterMap.put("id", Arrays.asList("1", "2"));
- AggregateFilterData triggerFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
assertTrue(Filter.isFilterMatch(sourceFilter, triggerFilter, false));
}
@Test
- public void testIsNotFilterMatchReturnFalse() {
+ public void testIsFilterMatch_withNegation_nonEmptyValues_returnFalse() {
Map<String, List<String>> sourceFilterMap = new HashMap<>();
sourceFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
sourceFilterMap.put("product", Arrays.asList("1234", "234"));
sourceFilterMap.put("ctid", Collections.singletonList("id"));
- AggregateFilterData sourceFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
Map<String, List<String>> triggerFilterMap = new HashMap<>();
triggerFilterMap.put(
"conversion_subdomain", Collections.singletonList("electronics.megastore"));
triggerFilterMap.put("product", Arrays.asList("1234", "2345"));
triggerFilterMap.put("id", Arrays.asList("1", "2"));
- AggregateFilterData triggerFilter =
- new AggregateFilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+
+ assertFalse(Filter.isFilterMatch(sourceFilter, triggerFilter, false));
+ }
+
+ @Test
+ public void testIsFilterMatch_withNegation_withEmptyValues_returnTrue() {
+ Map<String, List<String>> sourceFilterMap = new HashMap<>();
+ sourceFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ sourceFilterMap.put("product", Arrays.asList("1234", "234"));
+ sourceFilterMap.put("ctid", Collections.singletonList("id"));
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+
+ Map<String, List<String>> triggerFilterMap = new HashMap<>();
+ triggerFilterMap.put("conversion_subdomain", Collections.singletonList("electronics"));
+ // Matches when negated
+ triggerFilterMap.put("product", Collections.emptyList());
+ triggerFilterMap.put("id", Arrays.asList("1", "2"));
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
+ assertTrue(Filter.isFilterMatch(sourceFilter, triggerFilter, false));
+ }
+
+ @Test
+ public void testIsFilterMatch_withNegation_withEmptyValues_returnFalse() {
+ Map<String, List<String>> sourceFilterMap = new HashMap<>();
+ sourceFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ sourceFilterMap.put("product", Collections.emptyList());
+ sourceFilterMap.put("ctid", Collections.singletonList("id"));
+ FilterData sourceFilter =
+ new FilterData.Builder().setAttributionFilterMap(sourceFilterMap).build();
+
+ Map<String, List<String>> triggerFilterMap = new HashMap<>();
+ triggerFilterMap.put(
+ "conversion_subdomain", Collections.singletonList("electronics.megastore"));
+ // Doesn't match when negated
+ triggerFilterMap.put("product", Collections.emptyList());
+ triggerFilterMap.put("id", Arrays.asList("1", "2"));
+ FilterData triggerFilter =
+ new FilterData.Builder().setAttributionFilterMap(triggerFilterMap).build();
assertFalse(Filter.isFilterMatch(sourceFilter, triggerFilter, false));
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/UnsignedLongTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/UnsignedLongTest.java
new file mode 100644
index 000000000..170b00680
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/UnsignedLongTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.measurement.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class UnsignedLongTest {
+ private static final UnsignedLong ULONG_LONG = new UnsignedLong(2L);
+ private static final UnsignedLong ULONG_STR = new UnsignedLong("2");
+ private static final UnsignedLong LARGE = new UnsignedLong("9223372036854775808");
+ private static final UnsignedLong LARGER = new UnsignedLong("9223372036854775809");
+
+ @Test
+ public void testEqualsPass() {
+ assertEquals(ULONG_STR, ULONG_STR);
+ assertEquals(ULONG_STR, ULONG_LONG);
+ assertEquals(ULONG_LONG, ULONG_LONG);
+ }
+
+ @Test
+ public void testEqualsFail() {
+ assertNotEquals(ULONG_LONG, new UnsignedLong(3L));
+ assertNotEquals(ULONG_STR, new UnsignedLong(3L));
+ }
+
+ @Test
+ public void testCompareTo_equal() {
+ assertTrue(ULONG_LONG.compareTo(ULONG_STR) == 0);
+ assertTrue(LARGE.compareTo(new UnsignedLong("9223372036854775808")) == 0);
+ }
+
+ @Test
+ public void testCompareTo_smaller() {
+ assertTrue(ULONG_LONG.compareTo(new UnsignedLong(3L)) < 0);
+ assertTrue(ULONG_LONG.compareTo(LARGE) < 0);
+ assertTrue(LARGE.compareTo(LARGER) < 0);
+ }
+
+ @Test
+ public void testCompareTo_larger() {
+ assertTrue(ULONG_LONG.compareTo(new UnsignedLong(1L)) > 0);
+ assertTrue(LARGE.compareTo(ULONG_LONG) > 0);
+ assertTrue(LARGER.compareTo(LARGE) > 0);
+ }
+
+ @Test
+ public void testHashCode_equals() {
+ assertEquals(ULONG_STR.hashCode(), ULONG_STR.hashCode());
+ assertEquals(ULONG_STR.hashCode(), ULONG_LONG.hashCode());
+ assertEquals(ULONG_LONG.hashCode(), ULONG_LONG.hashCode());
+ }
+
+ @Test
+ public void testHashCode_notEquals() {
+ assertNotEquals(ULONG_LONG.hashCode(), new UnsignedLong(3L).hashCode());
+ assertNotEquals(ULONG_STR.hashCode(), new UnsignedLong(3L).hashCode());
+ }
+
+ @Test
+ public void testValidateConstructorArgument_long() {
+ Long arg = null;
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new UnsignedLong(arg));
+ }
+
+ @Test
+ public void testValidateConstructorArgument_string() {
+ String arg = null;
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new UnsignedLong(arg));
+ }
+
+ @Test
+ public void testMod() {
+ // uint64 18446744073709501613 = long -50003
+ // 18446744073709501613 % 8 = 5
+ assertEquals(new UnsignedLong(5L), new UnsignedLong(-50003L).mod(8));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("18446744073709551615", new UnsignedLong(-1L).toString());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java
index 2b2bc00b7..e16c1d8c3 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/util/WebTest.java
@@ -36,13 +36,15 @@ public class WebTest {
private static final String HTTPS_SCHEME = "https";
private static final String HTTP_SCHEME = "http";
private static final String INVALID_URL = "invalid url";
+ private static final String PATH = "path";
+ private static final String SECOND_PATH = "second_path";
@Test
public void testTopPrivateDomainAndScheme_ValidPublicDomainAndHttpsScheme() {
String inputUrl = String.format("%s://%s.%s",
HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN);
Uri expectedUri = Uri.parse(inputUrl);
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -52,7 +54,7 @@ public class WebTest {
String inputUrl = String.format("%s://%s.%s",
HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PRIVATE_DOMAIN);
Uri expectedUri = Uri.parse(inputUrl);
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -63,7 +65,7 @@ public class WebTest {
HTTPS_SCHEME, SUBDOMAIN, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN);
Uri expectedUri = Uri.parse(String.format("%s://%s.%s",
HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN));
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -74,7 +76,7 @@ public class WebTest {
HTTPS_SCHEME, SUBDOMAIN, TOP_PRIVATE_DOMAIN, VALID_PRIVATE_DOMAIN);
Uri expectedUri = Uri.parse(String.format("%s://%s.%s",
HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PRIVATE_DOMAIN));
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -84,7 +86,7 @@ public class WebTest {
String inputUrl = String.format("%s://%s.%s",
HTTP_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN);
Uri expectedUri = Uri.parse(inputUrl);
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertTrue(output.isPresent());
assertEquals(expectedUri, output.get());
}
@@ -92,13 +94,68 @@ public class WebTest {
@Test
public void testTopPrivateDomainAndScheme_InvalidTldAndHttpsScheme() {
String inputUrl = String.format("%s://%s.%s", HTTP_SCHEME, TOP_PRIVATE_DOMAIN, INVALID_TLD);
- Optional output = Web.topPrivateDomainAndScheme(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(inputUrl);
assertFalse(output.isPresent());
}
@Test
public void testTopPrivateDomainAndScheme_InvalidUrl() {
- Optional output = Web.topPrivateDomainAndScheme(INVALID_URL);
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(INVALID_URL);
+ assertFalse(output.isPresent());
+ }
+
+ @Test
+ public void topPrivateDomainAndPath_ForDomainAndPath_ReturnsDomainAndPath() {
+ String inputUrl =
+ String.format(
+ "%s://%s.%s/%s",
+ HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN, PATH);
+ Uri expectedUri = Uri.parse(inputUrl);
+ Optional<Uri> output = Web.topPrivateDomainSchemeAndPath(Uri.parse(inputUrl));
+ assertTrue(output.isPresent());
+ assertEquals(expectedUri, output.get());
+ }
+
+ @Test
+ public void topPrivateDomainAndPath_ForSubdomain_DoesNotReturnSubdomain() {
+ String inputUrl =
+ String.format(
+ "%s://%s.%s.%s/%s",
+ HTTPS_SCHEME, SUBDOMAIN, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN, PATH);
+ Uri uri = Uri.parse(inputUrl);
+ String expectedUri =
+ String.format(
+ "%s://%s.%s/%s",
+ HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN, PATH);
+ Optional<Uri> output = Web.topPrivateDomainSchemeAndPath(uri);
+ assertTrue(output.isPresent());
+ assertEquals(expectedUri, output.get().toString());
+ }
+
+ @Test
+ public void topPrivateDomainAndPath_ForMultiplePaths_ReturnsMultipleTokens() {
+ String inputUrl =
+ String.format(
+ "%s://%s.%s.%s/%s/%s",
+ HTTPS_SCHEME,
+ SUBDOMAIN,
+ TOP_PRIVATE_DOMAIN,
+ VALID_PUBLIC_DOMAIN,
+ PATH,
+ SECOND_PATH);
+ Uri uri = Uri.parse(inputUrl);
+ String expectedUri =
+ String.format(
+ "%s://%s.%s/%s/%s",
+ HTTPS_SCHEME, TOP_PRIVATE_DOMAIN, VALID_PUBLIC_DOMAIN, PATH, SECOND_PATH);
+ Optional<Uri> output = Web.topPrivateDomainSchemeAndPath(uri);
+ assertTrue(output.isPresent());
+ assertEquals(expectedUri, output.get().toString());
+ }
+
+ @Test
+ public void topPrivateDomainAndPath_ForInvalidUri_ReturnsEmptyOptional() {
+ Optional<Uri> output = Web.topPrivateDomainAndScheme(INVALID_URL);
assertFalse(output.isPresent());
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/AdServicesLoggerImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/AdServicesLoggerImplTest.java
new file mode 100644
index 000000000..7717ddd60
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/AdServicesLoggerImplTest.java
@@ -0,0 +1,469 @@
+/*
+ * 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.stats;
+
+import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
+
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__TARGETING;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REPORTS_UPLOADED__TYPE__EVENT;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MESUREMENT_REPORTS_UPLOADED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__OPT_OUT_SELECTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
+import static com.android.adservices.service.stats.BackgroundFetchProcessReportedStatsTest.LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.BackgroundFetchProcessReportedStatsTest.NUM_OF_ELIGIBLE_TO_UPDATE_CAS;
+import static com.android.adservices.service.stats.BackgroundFetchProcessReportedStatsTest.RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.BUYER_DECISION_LOGIC_SCRIPT_TYPE;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.FETCHED_BUYER_DECISION_LOGIC_SCRIPT_SIZE_IN_BYTES;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.FETCHED_TRUSTED_BIDDING_SIGNALS_DATA_SIZE_IN_BYTES;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.GENERATE_BIDS_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.GET_BUYER_DECISION_LOGIC_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.GET_BUYER_DECISION_LOGIC_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.GET_TRUSTED_BIDDING_SIGNALS_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.GET_TRUSTED_BIDDING_SIGNALS_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.NUM_OF_ADS_FOR_BIDDING;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.NUM_OF_KEYS_OF_TRUSTED_BIDDING_SIGNALS;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.RUN_AD_BIDDING_PER_CA_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.RUN_AD_BIDDING_PER_CA_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.RUN_BIDDING_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdBiddingPerCAProcessReportedStatsTest.RUN_BIDDING_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.GET_BUYERS_CUSTOM_AUDIENCE_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.GET_BUYERS_CUSTOM_AUDIENCE_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.NUM_BUYERS_FETCHED;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.NUM_BUYERS_REQUESTED;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.NUM_OF_ADS_ENTERING_BIDDING;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.NUM_OF_CAS_ENTERING_BIDDING;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.NUM_OF_CAS_POSTING_BIDDING;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.RATIO_OF_CAS_SELECTING_RMKT_ADS;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.RUN_AD_BIDDING_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.RUN_AD_BIDDING_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdBiddingProcessReportedStatsTest.TOTAL_AD_BIDDING_STAGE_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.FETCHED_AD_SELECTION_LOGIC_SCRIPT_SIZE_IN_BYTES;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.FETCHED_TRUSTED_SCORING_SIGNALS_DATA_SIZE_IN_BYTES;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.GET_AD_SCORES_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.GET_AD_SCORES_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.GET_AD_SELECTION_LOGIC_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.GET_AD_SELECTION_LOGIC_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.GET_AD_SELECTION_LOGIC_SCRIPT_TYPE;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.GET_TRUSTED_SCORING_SIGNALS_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.GET_TRUSTED_SCORING_SIGNALS_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.NUM_OF_CAS_ENTERING_SCORING;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.NUM_OF_CONTEXTUAL_ADS_ENTERING_SCORING;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.NUM_OF_REMARKETING_ADS_ENTERING_SCORING;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.RUN_AD_SCORING_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.RUN_AD_SCORING_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdScoringProcessReportedStatsTest.SCORE_ADS_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdSelectionProcessReportedStatsTest.DB_AD_SELECTION_SIZE_IN_BYTES;
+import static com.android.adservices.service.stats.RunAdSelectionProcessReportedStatsTest.IS_RMKT_ADS_WON;
+import static com.android.adservices.service.stats.RunAdSelectionProcessReportedStatsTest.PERSIST_AD_SELECTION_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdSelectionProcessReportedStatsTest.PERSIST_AD_SELECTION_RESULT_CODE;
+import static com.android.adservices.service.stats.RunAdSelectionProcessReportedStatsTest.RUN_AD_SELECTION_LATENCY_IN_MILLIS;
+import static com.android.adservices.service.stats.RunAdSelectionProcessReportedStatsTest.RUN_AD_SELECTION_RESULT_CODE;
+import static com.android.adservices.service.stats.UpdateCustomAudienceProcessReportedStatsTest.DATA_SIZE_OF_ADS_IN_BYTES;
+import static com.android.adservices.service.stats.UpdateCustomAudienceProcessReportedStatsTest.NUM_OF_ADS;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link AdServicesLoggerImpl}. */
+public class AdServicesLoggerImplTest {
+ @Mock StatsdAdServicesLogger mStatsdLoggerMock;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testLogFledgeApiCallStats() {
+ final int latencyMs = 10;
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logFledgeApiCallStats(
+ AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, STATUS_SUCCESS, latencyMs);
+ verify(mStatsdLoggerMock)
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS),
+ eq(STATUS_SUCCESS),
+ eq(latencyMs));
+ }
+
+ @Test
+ public void testLogRunAdSelectionProcessReportedStats() {
+ RunAdSelectionProcessReportedStats stats =
+ RunAdSelectionProcessReportedStats.builder()
+ .setIsRemarketingAdsWon(IS_RMKT_ADS_WON)
+ .setDBAdSelectionSizeInBytes(DB_AD_SELECTION_SIZE_IN_BYTES)
+ .setPersistAdSelectionLatencyInMillis(
+ PERSIST_AD_SELECTION_LATENCY_IN_MILLIS)
+ .setPersistAdSelectionResultCode(PERSIST_AD_SELECTION_RESULT_CODE)
+ .setRunAdSelectionLatencyInMillis(RUN_AD_SELECTION_LATENCY_IN_MILLIS)
+ .setRunAdSelectionResultCode(RUN_AD_SELECTION_RESULT_CODE)
+ .build();
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logRunAdSelectionProcessReportedStats(stats);
+ ArgumentCaptor<RunAdSelectionProcessReportedStats> argumentCaptor =
+ ArgumentCaptor.forClass(RunAdSelectionProcessReportedStats.class);
+ verify(mStatsdLoggerMock).logRunAdSelectionProcessReportedStats(argumentCaptor.capture());
+ assertEquals(argumentCaptor.getValue().getIsRemarketingAdsWon(), IS_RMKT_ADS_WON);
+ assertEquals(
+ argumentCaptor.getValue().getDBAdSelectionSizeInBytes(),
+ DB_AD_SELECTION_SIZE_IN_BYTES);
+ assertEquals(
+ argumentCaptor.getValue().getPersistAdSelectionLatencyInMillis(),
+ PERSIST_AD_SELECTION_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdSelectionResultCode(),
+ PERSIST_AD_SELECTION_RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdSelectionLatencyInMillis(),
+ RUN_AD_SELECTION_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdSelectionResultCode(),
+ RUN_AD_SELECTION_RESULT_CODE);
+ }
+
+ @Test
+ public void testLogRunAdScoringProcessReportedStats() {
+ RunAdScoringProcessReportedStats stats =
+ RunAdScoringProcessReportedStats.builder()
+ .setGetAdSelectionLogicLatencyInMillis(
+ GET_AD_SELECTION_LOGIC_LATENCY_IN_MILLIS)
+ .setGetAdSelectionLogicResultCode(GET_AD_SELECTION_LOGIC_RESULT_CODE)
+ .setGetAdSelectionLogicScriptType(GET_AD_SELECTION_LOGIC_SCRIPT_TYPE)
+ .setFetchedAdSelectionLogicScriptSizeInBytes(
+ FETCHED_AD_SELECTION_LOGIC_SCRIPT_SIZE_IN_BYTES)
+ .setGetTrustedScoringSignalsLatencyInMillis(
+ GET_TRUSTED_SCORING_SIGNALS_LATENCY_IN_MILLIS)
+ .setGetTrustedScoringSignalsResultCode(
+ GET_TRUSTED_SCORING_SIGNALS_RESULT_CODE)
+ .setFetchedTrustedScoringSignalsDataSizeInBytes(
+ FETCHED_TRUSTED_SCORING_SIGNALS_DATA_SIZE_IN_BYTES)
+ .setScoreAdsLatencyInMillis(SCORE_ADS_LATENCY_IN_MILLIS)
+ .setGetAdScoresLatencyInMillis(GET_AD_SCORES_LATENCY_IN_MILLIS)
+ .setGetAdScoresResultCode(GET_AD_SCORES_RESULT_CODE)
+ .setNumOfCasEnteringScoring(NUM_OF_CAS_ENTERING_SCORING)
+ .setNumOfRemarketingAdsEnteringScoring(
+ NUM_OF_REMARKETING_ADS_ENTERING_SCORING)
+ .setNumOfContextualAdsEnteringScoring(
+ NUM_OF_CONTEXTUAL_ADS_ENTERING_SCORING)
+ .setRunAdScoringLatencyInMillis(RUN_AD_SCORING_LATENCY_IN_MILLIS)
+ .setRunAdScoringResultCode(RUN_AD_SCORING_RESULT_CODE)
+ .build();
+
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logRunAdScoringProcessReportedStats(stats);
+ ArgumentCaptor<RunAdScoringProcessReportedStats> argumentCaptor =
+ ArgumentCaptor.forClass(RunAdScoringProcessReportedStats.class);
+ verify(mStatsdLoggerMock).logRunAdScoringProcessReportedStats(argumentCaptor.capture());
+ assertEquals(
+ argumentCaptor.getValue().getGetAdSelectionLogicLatencyInMillis(),
+ GET_AD_SELECTION_LOGIC_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getGetAdSelectionLogicResultCode(),
+ GET_AD_SELECTION_LOGIC_RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getGetAdSelectionLogicScriptType(),
+ GET_AD_SELECTION_LOGIC_SCRIPT_TYPE);
+ assertEquals(
+ argumentCaptor.getValue().getFetchedAdSelectionLogicScriptSizeInBytes(),
+ FETCHED_AD_SELECTION_LOGIC_SCRIPT_SIZE_IN_BYTES);
+ assertEquals(
+ argumentCaptor.getValue().getGetTrustedScoringSignalsLatencyInMillis(),
+ GET_TRUSTED_SCORING_SIGNALS_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getGetTrustedScoringSignalsResultCode(),
+ GET_TRUSTED_SCORING_SIGNALS_RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getFetchedTrustedScoringSignalsDataSizeInBytes(),
+ FETCHED_TRUSTED_SCORING_SIGNALS_DATA_SIZE_IN_BYTES);
+ assertEquals(
+ argumentCaptor.getValue().getScoreAdsLatencyInMillis(),
+ SCORE_ADS_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getGetAdScoresLatencyInMillis(),
+ GET_AD_SCORES_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getGetAdScoresResultCode(), GET_AD_SCORES_RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getNumOfCasEnteringScoring(),
+ NUM_OF_CAS_ENTERING_SCORING);
+ assertEquals(
+ argumentCaptor.getValue().getNumOfRemarketingAdsEnteringScoring(),
+ NUM_OF_REMARKETING_ADS_ENTERING_SCORING);
+ assertEquals(
+ argumentCaptor.getValue().getNumOfContextualAdsEnteringScoring(),
+ NUM_OF_CONTEXTUAL_ADS_ENTERING_SCORING);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdScoringLatencyInMillis(),
+ RUN_AD_SCORING_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdScoringResultCode(), RUN_AD_SCORING_RESULT_CODE);
+ }
+
+ @Test
+ public void testLogRunAdBiddingProcessReportedStats() {
+ RunAdBiddingProcessReportedStats stats =
+ RunAdBiddingProcessReportedStats.builder()
+ .setGetBuyersCustomAudienceLatencyInMills(
+ GET_BUYERS_CUSTOM_AUDIENCE_LATENCY_IN_MILLIS)
+ .setGetBuyersCustomAudienceResultCode(
+ GET_BUYERS_CUSTOM_AUDIENCE_RESULT_CODE)
+ .setNumBuyersRequested(NUM_BUYERS_REQUESTED)
+ .setNumBuyersFetched(NUM_BUYERS_FETCHED)
+ .setNumOfAdsEnteringBidding(NUM_OF_ADS_ENTERING_BIDDING)
+ .setNumOfCasEnteringBidding(NUM_OF_CAS_ENTERING_BIDDING)
+ .setNumOfCasPostBidding(NUM_OF_CAS_POSTING_BIDDING)
+ .setRatioOfCasSelectingRmktAds(RATIO_OF_CAS_SELECTING_RMKT_ADS)
+ .setRunAdBiddingLatencyInMillis(RUN_AD_BIDDING_LATENCY_IN_MILLIS)
+ .setRunAdBiddingResultCode(RUN_AD_BIDDING_RESULT_CODE)
+ .setTotalAdBiddingStageLatencyInMillis(
+ TOTAL_AD_BIDDING_STAGE_LATENCY_IN_MILLIS)
+ .build();
+
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logRunAdBiddingProcessReportedStats(stats);
+ ArgumentCaptor<RunAdBiddingProcessReportedStats> argumentCaptor =
+ ArgumentCaptor.forClass(RunAdBiddingProcessReportedStats.class);
+ verify(mStatsdLoggerMock).logRunAdBiddingProcessReportedStats(argumentCaptor.capture());
+ assertEquals(
+ argumentCaptor.getValue().getGetBuyersCustomAudienceLatencyInMills(),
+ GET_BUYERS_CUSTOM_AUDIENCE_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getGetBuyersCustomAudienceResultCode(),
+ GET_BUYERS_CUSTOM_AUDIENCE_RESULT_CODE);
+ assertEquals(argumentCaptor.getValue().getNumBuyersRequested(), NUM_BUYERS_REQUESTED);
+ assertEquals(argumentCaptor.getValue().getNumBuyersFetched(), NUM_BUYERS_FETCHED);
+ assertEquals(
+ argumentCaptor.getValue().getNumOfAdsEnteringBidding(),
+ NUM_OF_ADS_ENTERING_BIDDING);
+ assertEquals(
+ argumentCaptor.getValue().getNumOfCasEnteringBidding(),
+ NUM_OF_CAS_ENTERING_BIDDING);
+ assertEquals(
+ argumentCaptor.getValue().getNumOfCasPostBidding(), NUM_OF_CAS_POSTING_BIDDING);
+ assertEquals(
+ argumentCaptor.getValue().getRatioOfCasSelectingRmktAds(),
+ RATIO_OF_CAS_SELECTING_RMKT_ADS,
+ 0.0f);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdBiddingLatencyInMillis(),
+ RUN_AD_BIDDING_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdBiddingResultCode(), RUN_AD_BIDDING_RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getTotalAdBiddingStageLatencyInMillis(),
+ TOTAL_AD_BIDDING_STAGE_LATENCY_IN_MILLIS);
+ }
+
+ @Test
+ public void testLogRunAdBiddingPerCAProcessReportedStats() {
+ RunAdBiddingPerCAProcessReportedStats stats =
+ RunAdBiddingPerCAProcessReportedStats.builder()
+ .setNumOfAdsForBidding(NUM_OF_ADS_FOR_BIDDING)
+ .setRunAdBiddingPerCaLatencyInMillis(
+ RUN_AD_BIDDING_PER_CA_LATENCY_IN_MILLIS)
+ .setRunAdBiddingPerCaResultCode(RUN_AD_BIDDING_PER_CA_RESULT_CODE)
+ .setGetBuyerDecisionLogicLatencyInMillis(
+ GET_BUYER_DECISION_LOGIC_LATENCY_IN_MILLIS)
+ .setGetBuyerDecisionLogicResultCode(GET_BUYER_DECISION_LOGIC_RESULT_CODE)
+ .setBuyerDecisionLogicScriptType(BUYER_DECISION_LOGIC_SCRIPT_TYPE)
+ .setFetchedBuyerDecisionLogicScriptSizeInBytes(
+ FETCHED_BUYER_DECISION_LOGIC_SCRIPT_SIZE_IN_BYTES)
+ .setNumOfKeysOfTrustedBiddingSignals(NUM_OF_KEYS_OF_TRUSTED_BIDDING_SIGNALS)
+ .setFetchedTrustedBiddingSignalsDataSizeInBytes(
+ FETCHED_TRUSTED_BIDDING_SIGNALS_DATA_SIZE_IN_BYTES)
+ .setGetTrustedBiddingSignalsLatencyInMillis(
+ GET_TRUSTED_BIDDING_SIGNALS_LATENCY_IN_MILLIS)
+ .setGetTrustedBiddingSignalsResultCode(
+ GET_TRUSTED_BIDDING_SIGNALS_RESULT_CODE)
+ .setGenerateBidsLatencyInMillis(GENERATE_BIDS_LATENCY_IN_MILLIS)
+ .setRunBiddingLatencyInMillis(RUN_BIDDING_LATENCY_IN_MILLIS)
+ .setRunBiddingResultCode(RUN_BIDDING_RESULT_CODE)
+ .build();
+
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logRunAdBiddingPerCAProcessReportedStats(stats);
+ ArgumentCaptor<RunAdBiddingPerCAProcessReportedStats> argumentCaptor =
+ ArgumentCaptor.forClass(RunAdBiddingPerCAProcessReportedStats.class);
+ verify(mStatsdLoggerMock)
+ .logRunAdBiddingPerCAProcessReportedStats(argumentCaptor.capture());
+ assertEquals(argumentCaptor.getValue().getNumOfAdsForBidding(), NUM_OF_ADS_FOR_BIDDING);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdBiddingPerCaLatencyInMillis(),
+ RUN_AD_BIDDING_PER_CA_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getRunAdBiddingPerCaResultCode(),
+ RUN_AD_BIDDING_PER_CA_RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getGetBuyerDecisionLogicLatencyInMillis(),
+ GET_BUYER_DECISION_LOGIC_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getGetBuyerDecisionLogicResultCode(),
+ GET_BUYER_DECISION_LOGIC_RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getBuyerDecisionLogicScriptType(),
+ BUYER_DECISION_LOGIC_SCRIPT_TYPE);
+ assertEquals(
+ argumentCaptor.getValue().getFetchedBuyerDecisionLogicScriptSizeInBytes(),
+ FETCHED_BUYER_DECISION_LOGIC_SCRIPT_SIZE_IN_BYTES);
+ assertEquals(
+ argumentCaptor.getValue().getNumOfKeysOfTrustedBiddingSignals(),
+ NUM_OF_KEYS_OF_TRUSTED_BIDDING_SIGNALS);
+ assertEquals(
+ argumentCaptor.getValue().getFetchedTrustedBiddingSignalsDataSizeInBytes(),
+ FETCHED_TRUSTED_BIDDING_SIGNALS_DATA_SIZE_IN_BYTES);
+ assertEquals(
+ argumentCaptor.getValue().getGetTrustedBiddingSignalsLatencyInMillis(),
+ GET_TRUSTED_BIDDING_SIGNALS_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getGetTrustedBiddingSignalsResultCode(),
+ GET_TRUSTED_BIDDING_SIGNALS_RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getGenerateBidsLatencyInMillis(),
+ GENERATE_BIDS_LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getRunBiddingLatencyInMillis(),
+ RUN_BIDDING_LATENCY_IN_MILLIS);
+ assertEquals(argumentCaptor.getValue().getRunBiddingResultCode(), RUN_BIDDING_RESULT_CODE);
+ }
+
+ @Test
+ public void testLogBackgroundFetchProcessReportedStats() {
+ BackgroundFetchProcessReportedStats stats =
+ BackgroundFetchProcessReportedStats.builder()
+ .setLatencyInMillis(LATENCY_IN_MILLIS)
+ .setNumOfEligibleToUpdateCas(NUM_OF_ELIGIBLE_TO_UPDATE_CAS)
+ .setResultCode(RESULT_CODE)
+ .build();
+
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logBackgroundFetchProcessReportedStats(stats);
+ ArgumentCaptor<BackgroundFetchProcessReportedStats> argumentCaptor =
+ ArgumentCaptor.forClass(BackgroundFetchProcessReportedStats.class);
+ verify(mStatsdLoggerMock).logBackgroundFetchProcessReportedStats(argumentCaptor.capture());
+ assertEquals(argumentCaptor.getValue().getLatencyInMillis(), LATENCY_IN_MILLIS);
+ assertEquals(
+ argumentCaptor.getValue().getNumOfEligibleToUpdateCas(),
+ NUM_OF_ELIGIBLE_TO_UPDATE_CAS);
+ assertEquals(argumentCaptor.getValue().getResultCode(), RESULT_CODE);
+ }
+
+ @Test
+ public void testLogUpdateCustomAudienceProcessReportedStats() {
+ UpdateCustomAudienceProcessReportedStats stats =
+ UpdateCustomAudienceProcessReportedStats.builder()
+ .setLatencyInMills(LATENCY_IN_MILLIS)
+ .setResultCode(RESULT_CODE)
+ .setDataSizeOfAdsInBytes(DATA_SIZE_OF_ADS_IN_BYTES)
+ .setNumOfAds(NUM_OF_ADS)
+ .build();
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logUpdateCustomAudienceProcessReportedStats(stats);
+ ArgumentCaptor<UpdateCustomAudienceProcessReportedStats> argumentCaptor =
+ ArgumentCaptor.forClass(UpdateCustomAudienceProcessReportedStats.class);
+ verify(mStatsdLoggerMock)
+ .logUpdateCustomAudienceProcessReportedStats(argumentCaptor.capture());
+ assertEquals(argumentCaptor.getValue().getLatencyInMills(), LATENCY_IN_MILLIS);
+ assertEquals(argumentCaptor.getValue().getResultCode(), RESULT_CODE);
+ assertEquals(
+ argumentCaptor.getValue().getDataSizeOfAdsInBytes(), DATA_SIZE_OF_ADS_IN_BYTES);
+ assertEquals(argumentCaptor.getValue().getNumOfAds(), NUM_OF_ADS);
+ }
+
+ @Test
+ public void testLogMeasurementReportReports() {
+ MeasurementReportsStats stats =
+ new MeasurementReportsStats.Builder()
+ .setCode(AD_SERVICES_MESUREMENT_REPORTS_UPLOADED)
+ .setType(AD_SERVICES_MEASUREMENT_REPORTS_UPLOADED__TYPE__EVENT)
+ .setResultCode(STATUS_SUCCESS)
+ .build();
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logMeasurementReports(stats);
+ ArgumentCaptor<MeasurementReportsStats> argumentCaptor =
+ ArgumentCaptor.forClass(MeasurementReportsStats.class);
+ verify(mStatsdLoggerMock).logMeasurementReports(argumentCaptor.capture());
+ assertEquals(argumentCaptor.getValue().getCode(), AD_SERVICES_MESUREMENT_REPORTS_UPLOADED);
+ assertEquals(
+ argumentCaptor.getValue().getType(),
+ AD_SERVICES_MEASUREMENT_REPORTS_UPLOADED__TYPE__EVENT);
+ assertEquals(argumentCaptor.getValue().getResultCode(), STATUS_SUCCESS);
+ }
+
+ @Test
+ public void testLogApiCallStats() {
+ final String packageName = "com.android.test";
+ final String sdkName = "com.android.container";
+ final int latency = 100;
+ ApiCallStats stats =
+ new ApiCallStats.Builder()
+ .setCode(AD_SERVICES_API_CALLED)
+ .setApiClass(AD_SERVICES_API_CALLED__API_CLASS__TARGETING)
+ .setApiName(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS)
+ .setAppPackageName(packageName)
+ .setSdkPackageName(sdkName)
+ .setLatencyMillisecond(latency)
+ .setResultCode(STATUS_SUCCESS)
+ .build();
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logApiCallStats(stats);
+ ArgumentCaptor<ApiCallStats> argumentCaptor = ArgumentCaptor.forClass(ApiCallStats.class);
+ verify(mStatsdLoggerMock).logApiCallStats(argumentCaptor.capture());
+ assertEquals(argumentCaptor.getValue().getCode(), AD_SERVICES_API_CALLED);
+ assertEquals(
+ argumentCaptor.getValue().getApiClass(),
+ AD_SERVICES_API_CALLED__API_CLASS__TARGETING);
+ assertEquals(
+ argumentCaptor.getValue().getApiName(),
+ AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS);
+ assertEquals(argumentCaptor.getValue().getAppPackageName(), packageName);
+ assertEquals(argumentCaptor.getValue().getSdkPackageName(), sdkName);
+ assertEquals(argumentCaptor.getValue().getLatencyMillisecond(), latency);
+ assertEquals(argumentCaptor.getValue().getResultCode(), STATUS_SUCCESS);
+ }
+
+ @Test
+ public void testLogUIStats() {
+ UIStats stats =
+ new UIStats.Builder()
+ .setCode(AD_SERVICES_SETTINGS_USAGE_REPORTED)
+ .setRegion(AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW)
+ .setAction(AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__OPT_OUT_SELECTED)
+ .build();
+ AdServicesLoggerImpl adServicesLogger = new AdServicesLoggerImpl(mStatsdLoggerMock);
+ adServicesLogger.logUIStats(stats);
+ ArgumentCaptor<UIStats> argumentCaptor = ArgumentCaptor.forClass(UIStats.class);
+ verify(mStatsdLoggerMock).logUIStats(argumentCaptor.capture());
+ assertEquals(argumentCaptor.getValue().getCode(), AD_SERVICES_SETTINGS_USAGE_REPORTED);
+ assertEquals(
+ argumentCaptor.getValue().getRegion(),
+ AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW);
+ assertEquals(
+ argumentCaptor.getValue().getAction(),
+ AD_SERVICES_SETTINGS_USAGE_REPORTED__ACTION__OPT_OUT_SELECTED);
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/ApiServiceLatencyCalculatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/ApiServiceLatencyCalculatorTest.java
new file mode 100644
index 000000000..88a7ae8ec
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/ApiServiceLatencyCalculatorTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.adservices.common.CallerMetadata;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ApiServiceLatencyCalculatorTest {
+ private static final long BINDER_ELAPSED_TIMESTAMP = 100L;
+ private static final CallerMetadata sCallerMetadata =
+ new CallerMetadata.Builder()
+ .setBinderElapsedTimestamp(BINDER_ELAPSED_TIMESTAMP)
+ .build();
+ private static final long START_ELAPSED_TIMESTAMP = 105L;
+ private static final long CURRENT_ELAPSED_TIMESTAMP = 107L;
+ private static final long STOP_ELAPSED_TIMESTAMP = 110L;
+ private static final int BINDER_LATENCY_MS =
+ (int) (2 * (START_ELAPSED_TIMESTAMP - BINDER_ELAPSED_TIMESTAMP));
+ private static final int INTERNAL_LATENCY_MS =
+ (int) (STOP_ELAPSED_TIMESTAMP - START_ELAPSED_TIMESTAMP);
+ private static final int OVERALL_LATENCY_MS = BINDER_LATENCY_MS + INTERNAL_LATENCY_MS;
+
+ @Mock private Clock mMockClock;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testApiServiceLatencyCalculator_currentElapsedTimeLatency() {
+ when(mMockClock.elapsedRealtime())
+ .thenReturn(START_ELAPSED_TIMESTAMP, CURRENT_ELAPSED_TIMESTAMP);
+ ApiServiceLatencyCalculator apiServiceLatencyCalculator =
+ new ApiServiceLatencyCalculator(sCallerMetadata, mMockClock);
+ verify(mMockClock).elapsedRealtime();
+ assertThat(apiServiceLatencyCalculator.getApiServiceElapsedLatencyMs())
+ .isEqualTo((int) (CURRENT_ELAPSED_TIMESTAMP - START_ELAPSED_TIMESTAMP));
+ }
+
+ @Test
+ public void testApiServiceLatencyCalculatorStop_calculateOverallLatencyOnce()
+ throws InterruptedException {
+ when(mMockClock.elapsedRealtime())
+ .thenReturn(START_ELAPSED_TIMESTAMP, STOP_ELAPSED_TIMESTAMP);
+ ApiServiceLatencyCalculator apiServiceLatencyCalculator =
+ new ApiServiceLatencyCalculator(sCallerMetadata, mMockClock);
+
+ int overallLatencyMs1 = apiServiceLatencyCalculator.getApiServiceOverallLatencyMs();
+ assertThat(overallLatencyMs1).isEqualTo(OVERALL_LATENCY_MS);
+ // Make thread to fall sleep for 5 milliseconds to verify that the overall latencies no
+ // longer change once the api service latency calculator was stopped by first time calling
+ // {@link ApiServiceLatencyCalculator#.getApiServiceOverallLatencyMs()} or
+ // {@link ApiServiceLatencyCalculator#.getApiServiceInternalFinalLatencyMs()}
+ Thread.sleep(5L);
+ int overallLatencyMs2 = apiServiceLatencyCalculator.getApiServiceOverallLatencyMs();
+ assertThat(overallLatencyMs1).isEqualTo(overallLatencyMs2);
+ }
+
+ @Test
+ public void testApiServiceLatencyCalculatorStop_calculateInternalLatencyOnce()
+ throws InterruptedException {
+ when(mMockClock.elapsedRealtime())
+ .thenReturn(START_ELAPSED_TIMESTAMP, STOP_ELAPSED_TIMESTAMP);
+ ApiServiceLatencyCalculator apiServiceLatencyCalculator =
+ new ApiServiceLatencyCalculator(sCallerMetadata, mMockClock);
+
+ int internalLatencyMs1 = apiServiceLatencyCalculator.getApiServiceInternalFinalLatencyMs();
+ assertThat(internalLatencyMs1).isEqualTo(INTERNAL_LATENCY_MS);
+ // Make thread to fall sleep for 5 milliseconds to verify that the overall latencies no
+ // longer change once the api service latency calculator was stopped by first time calling
+ // {@link ApiServiceLatencyCalculator#.getApiServiceOverallLatencyMs()} or
+ // {@link ApiServiceLatencyCalculator#.getApiServiceInternalFinalLatencyMs()}
+ Thread.sleep(5L);
+ int internalLatencyMs2 = apiServiceLatencyCalculator.getApiServiceInternalFinalLatencyMs();
+ assertThat(internalLatencyMs1).isEqualTo(internalLatencyMs2);
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/BackgroundFetchProcessReportedStatsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/BackgroundFetchProcessReportedStatsTest.java
new file mode 100644
index 000000000..8c4a6f642
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/BackgroundFetchProcessReportedStatsTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.stats;
+
+import static org.junit.Assert.assertEquals;
+
+import android.adservices.common.AdServicesStatusUtils;
+
+import org.junit.Test;
+
+/** Unit tests for {@link BackgroundFetchProcessReportedStats}. */
+public class BackgroundFetchProcessReportedStatsTest {
+ static final int LATENCY_IN_MILLIS = 10;
+ static final int NUM_OF_ELIGIBLE_TO_UPDATE_CAS = 5;
+ static final int RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+
+ @Test
+ public void testBuilderCreateSuccess() {
+ BackgroundFetchProcessReportedStats stats =
+ BackgroundFetchProcessReportedStats.builder()
+ .setLatencyInMillis(LATENCY_IN_MILLIS)
+ .setNumOfEligibleToUpdateCas(NUM_OF_ELIGIBLE_TO_UPDATE_CAS)
+ .setResultCode(RESULT_CODE)
+ .build();
+ assertEquals(LATENCY_IN_MILLIS, stats.getLatencyInMillis());
+ assertEquals(NUM_OF_ELIGIBLE_TO_UPDATE_CAS, stats.getNumOfEligibleToUpdateCas());
+ assertEquals(RESULT_CODE, stats.getResultCode());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdBiddingPerCAProcessReportedStatsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdBiddingPerCAProcessReportedStatsTest.java
new file mode 100644
index 000000000..2aa0dde95
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdBiddingPerCAProcessReportedStatsTest.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.stats;
+
+import static com.android.adservices.service.stats.AdServicesStatsLog.RUN_AD_BIDDING_PER_CAPROCESS_REPORTED__BUYER_DECISION_LOGIC_SCRIPT_TYPE__JAVASCRIPT;
+
+import static org.junit.Assert.assertEquals;
+
+import android.adservices.common.AdServicesStatusUtils;
+
+import org.junit.Test;
+
+/** Unit tests for {@link RunAdBiddingPerCAProcessReportedStats}. */
+public class RunAdBiddingPerCAProcessReportedStatsTest {
+ static final int NUM_OF_ADS_FOR_BIDDING = 10;
+ static final int RUN_AD_BIDDING_PER_CA_LATENCY_IN_MILLIS = 5;
+ static final int RUN_AD_BIDDING_PER_CA_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int GET_BUYER_DECISION_LOGIC_LATENCY_IN_MILLIS = 5;
+ static final int GET_BUYER_DECISION_LOGIC_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int BUYER_DECISION_LOGIC_SCRIPT_TYPE =
+ RUN_AD_BIDDING_PER_CAPROCESS_REPORTED__BUYER_DECISION_LOGIC_SCRIPT_TYPE__JAVASCRIPT;
+ static final int FETCHED_BUYER_DECISION_LOGIC_SCRIPT_SIZE_IN_BYTES = 10;
+ static final int NUM_OF_KEYS_OF_TRUSTED_BIDDING_SIGNALS = 10;
+ static final int FETCHED_TRUSTED_BIDDING_SIGNALS_DATA_SIZE_IN_BYTES = 10;
+ static final int GET_TRUSTED_BIDDING_SIGNALS_LATENCY_IN_MILLIS = 5;
+ static final int GENERATE_BIDS_LATENCY_IN_MILLIS = 10;
+ static final int RUN_BIDDING_LATENCY_IN_MILLIS = 5;
+ static final int RUN_BIDDING_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int GET_TRUSTED_BIDDING_SIGNALS_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+
+ @Test
+ public void testBuilderCreateSuccess() {
+ RunAdBiddingPerCAProcessReportedStats stats =
+ RunAdBiddingPerCAProcessReportedStats.builder()
+ .setNumOfAdsForBidding(NUM_OF_ADS_FOR_BIDDING)
+ .setRunAdBiddingPerCaLatencyInMillis(
+ RUN_AD_BIDDING_PER_CA_LATENCY_IN_MILLIS)
+ .setRunAdBiddingPerCaResultCode(RUN_AD_BIDDING_PER_CA_RESULT_CODE)
+ .setGetBuyerDecisionLogicLatencyInMillis(
+ GET_BUYER_DECISION_LOGIC_LATENCY_IN_MILLIS)
+ .setGetBuyerDecisionLogicResultCode(GET_BUYER_DECISION_LOGIC_RESULT_CODE)
+ .setBuyerDecisionLogicScriptType(BUYER_DECISION_LOGIC_SCRIPT_TYPE)
+ .setFetchedBuyerDecisionLogicScriptSizeInBytes(
+ FETCHED_BUYER_DECISION_LOGIC_SCRIPT_SIZE_IN_BYTES)
+ .setNumOfKeysOfTrustedBiddingSignals(NUM_OF_KEYS_OF_TRUSTED_BIDDING_SIGNALS)
+ .setFetchedTrustedBiddingSignalsDataSizeInBytes(
+ FETCHED_TRUSTED_BIDDING_SIGNALS_DATA_SIZE_IN_BYTES)
+ .setGetTrustedBiddingSignalsLatencyInMillis(
+ GET_TRUSTED_BIDDING_SIGNALS_LATENCY_IN_MILLIS)
+ .setGetTrustedBiddingSignalsResultCode(
+ GET_TRUSTED_BIDDING_SIGNALS_RESULT_CODE)
+ .setGenerateBidsLatencyInMillis(GENERATE_BIDS_LATENCY_IN_MILLIS)
+ .setRunBiddingLatencyInMillis(RUN_BIDDING_LATENCY_IN_MILLIS)
+ .setRunBiddingResultCode(RUN_BIDDING_RESULT_CODE)
+ .build();
+ assertEquals(NUM_OF_ADS_FOR_BIDDING, stats.getNumOfAdsForBidding());
+ assertEquals(
+ RUN_AD_BIDDING_PER_CA_LATENCY_IN_MILLIS,
+ stats.getRunAdBiddingPerCaLatencyInMillis());
+ assertEquals(RUN_AD_BIDDING_PER_CA_RESULT_CODE, stats.getRunAdBiddingPerCaResultCode());
+ assertEquals(
+ GET_BUYER_DECISION_LOGIC_LATENCY_IN_MILLIS,
+ stats.getGetBuyerDecisionLogicLatencyInMillis());
+ assertEquals(
+ GET_BUYER_DECISION_LOGIC_RESULT_CODE, stats.getGetBuyerDecisionLogicResultCode());
+ assertEquals(BUYER_DECISION_LOGIC_SCRIPT_TYPE, stats.getBuyerDecisionLogicScriptType());
+ assertEquals(
+ FETCHED_BUYER_DECISION_LOGIC_SCRIPT_SIZE_IN_BYTES,
+ stats.getFetchedBuyerDecisionLogicScriptSizeInBytes());
+ assertEquals(
+ NUM_OF_KEYS_OF_TRUSTED_BIDDING_SIGNALS,
+ stats.getNumOfKeysOfTrustedBiddingSignals());
+ assertEquals(
+ FETCHED_TRUSTED_BIDDING_SIGNALS_DATA_SIZE_IN_BYTES,
+ stats.getFetchedTrustedBiddingSignalsDataSizeInBytes());
+ assertEquals(
+ GET_TRUSTED_BIDDING_SIGNALS_LATENCY_IN_MILLIS,
+ stats.getGetTrustedBiddingSignalsLatencyInMillis());
+ assertEquals(
+ GET_TRUSTED_BIDDING_SIGNALS_RESULT_CODE,
+ stats.getGetTrustedBiddingSignalsResultCode());
+ assertEquals(GENERATE_BIDS_LATENCY_IN_MILLIS, stats.getGenerateBidsLatencyInMillis());
+ assertEquals(RUN_BIDDING_LATENCY_IN_MILLIS, stats.getRunBiddingLatencyInMillis());
+ assertEquals(RUN_BIDDING_RESULT_CODE, stats.getRunBiddingResultCode());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdBiddingProcessReportedStatsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdBiddingProcessReportedStatsTest.java
new file mode 100644
index 000000000..af36d48ea
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdBiddingProcessReportedStatsTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.stats;
+
+import static org.junit.Assert.assertEquals;
+
+import android.adservices.common.AdServicesStatusUtils;
+
+import org.junit.Test;
+
+/** Unit tests for {@link RunAdBiddingProcessReportedStats}. */
+public class RunAdBiddingProcessReportedStatsTest {
+ static final int GET_BUYERS_CUSTOM_AUDIENCE_LATENCY_IN_MILLIS = 10;
+ static final int NUM_BUYERS_REQUESTED = 5;
+ static final int NUM_BUYERS_FETCHED = 3;
+ static final int NUM_OF_ADS_ENTERING_BIDDING = 20;
+ static final int NUM_OF_CAS_ENTERING_BIDDING = 10;
+ static final int NUM_OF_CAS_POSTING_BIDDING = 2;
+ static final float RATIO_OF_CAS_SELECTING_RMKT_ADS = 0.80f;
+ static final int RUN_AD_BIDDING_LATENCY_IN_MILLIS = 10;
+ static final int TOTAL_AD_BIDDING_STAGE_LATENCY_IN_MILLIS = 20;
+ static final int GET_BUYERS_CUSTOM_AUDIENCE_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int RUN_AD_BIDDING_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+
+ @Test
+ public void testBuilderCreateSuccess() {
+ RunAdBiddingProcessReportedStats stats =
+ RunAdBiddingProcessReportedStats.builder()
+ .setGetBuyersCustomAudienceLatencyInMills(
+ GET_BUYERS_CUSTOM_AUDIENCE_LATENCY_IN_MILLIS)
+ .setGetBuyersCustomAudienceResultCode(
+ GET_BUYERS_CUSTOM_AUDIENCE_RESULT_CODE)
+ .setNumBuyersRequested(NUM_BUYERS_REQUESTED)
+ .setNumBuyersFetched(NUM_BUYERS_FETCHED)
+ .setNumOfAdsEnteringBidding(NUM_OF_ADS_ENTERING_BIDDING)
+ .setNumOfCasEnteringBidding(NUM_OF_CAS_ENTERING_BIDDING)
+ .setNumOfCasPostBidding(NUM_OF_CAS_POSTING_BIDDING)
+ .setRatioOfCasSelectingRmktAds(RATIO_OF_CAS_SELECTING_RMKT_ADS)
+ .setRunAdBiddingLatencyInMillis(RUN_AD_BIDDING_LATENCY_IN_MILLIS)
+ .setRunAdBiddingResultCode(RUN_AD_BIDDING_RESULT_CODE)
+ .setTotalAdBiddingStageLatencyInMillis(
+ TOTAL_AD_BIDDING_STAGE_LATENCY_IN_MILLIS)
+ .build();
+ assertEquals(
+ GET_BUYERS_CUSTOM_AUDIENCE_LATENCY_IN_MILLIS,
+ stats.getGetBuyersCustomAudienceLatencyInMills());
+ assertEquals(
+ GET_BUYERS_CUSTOM_AUDIENCE_RESULT_CODE,
+ stats.getGetBuyersCustomAudienceResultCode());
+ assertEquals(NUM_BUYERS_REQUESTED, stats.getNumBuyersRequested());
+ assertEquals(NUM_BUYERS_FETCHED, stats.getNumBuyersFetched());
+ assertEquals(NUM_OF_ADS_ENTERING_BIDDING, stats.getNumOfAdsEnteringBidding());
+ assertEquals(NUM_OF_CAS_ENTERING_BIDDING, stats.getNumOfCasEnteringBidding());
+ assertEquals(NUM_OF_CAS_POSTING_BIDDING, stats.getNumOfCasPostBidding());
+ assertEquals(RATIO_OF_CAS_SELECTING_RMKT_ADS, stats.getRatioOfCasSelectingRmktAds(), 0.0f);
+ assertEquals(RUN_AD_BIDDING_LATENCY_IN_MILLIS, stats.getRunAdBiddingLatencyInMillis());
+ assertEquals(RUN_AD_BIDDING_RESULT_CODE, stats.getRunAdBiddingResultCode());
+ assertEquals(
+ TOTAL_AD_BIDDING_STAGE_LATENCY_IN_MILLIS,
+ stats.getTotalAdBiddingStageLatencyInMillis());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdScoringProcessReportedStatsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdScoringProcessReportedStatsTest.java
new file mode 100644
index 000000000..1d6521af4
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdScoringProcessReportedStatsTest.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.stats;
+
+import static org.junit.Assert.assertEquals;
+
+import android.adservices.common.AdServicesStatusUtils;
+
+import org.junit.Test;
+
+/** Unit tests for {@link RunAdScoringProcessReportedStats}. */
+public class RunAdScoringProcessReportedStatsTest {
+ static final int GET_AD_SELECTION_LOGIC_LATENCY_IN_MILLIS = 10;
+ static final int FETCHED_AD_SELECTION_LOGIC_SCRIPT_SIZE_IN_BYTES = 20;
+ static final int GET_TRUSTED_SCORING_SIGNALS_LATENCY_IN_MILLIS = 10;
+ static final int FETCHED_TRUSTED_SCORING_SIGNALS_DATA_SIZE_IN_BYTES = 10;
+ static final int SCORE_ADS_LATENCY_IN_MILLIS = 5;
+ static final int GET_AD_SCORES_LATENCY_IN_MILLIS = 5;
+ static final int GET_AD_SELECTION_LOGIC_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int GET_AD_SCORES_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int GET_TRUSTED_SCORING_SIGNALS_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int NUM_OF_CAS_ENTERING_SCORING = 5;
+ static final int NUM_OF_REMARKETING_ADS_ENTERING_SCORING = 6;
+ static final int NUM_OF_CONTEXTUAL_ADS_ENTERING_SCORING = 0;
+ static final int RUN_AD_SCORING_LATENCY_IN_MILLIS = 10;
+ static final int RUN_AD_SCORING_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int GET_AD_SELECTION_LOGIC_SCRIPT_TYPE = AdServicesStatusUtils.STATUS_SUCCESS;
+
+ @Test
+ public void testBuilderCreateSuccess() {
+ RunAdScoringProcessReportedStats stats =
+ RunAdScoringProcessReportedStats.builder()
+ .setGetAdSelectionLogicLatencyInMillis(
+ GET_AD_SELECTION_LOGIC_LATENCY_IN_MILLIS)
+ .setGetAdSelectionLogicResultCode(GET_AD_SELECTION_LOGIC_RESULT_CODE)
+ .setGetAdSelectionLogicScriptType(GET_AD_SELECTION_LOGIC_SCRIPT_TYPE)
+ .setFetchedAdSelectionLogicScriptSizeInBytes(
+ FETCHED_AD_SELECTION_LOGIC_SCRIPT_SIZE_IN_BYTES)
+ .setGetTrustedScoringSignalsLatencyInMillis(
+ GET_TRUSTED_SCORING_SIGNALS_LATENCY_IN_MILLIS)
+ .setGetTrustedScoringSignalsResultCode(
+ GET_TRUSTED_SCORING_SIGNALS_RESULT_CODE)
+ .setFetchedTrustedScoringSignalsDataSizeInBytes(
+ FETCHED_TRUSTED_SCORING_SIGNALS_DATA_SIZE_IN_BYTES)
+ .setScoreAdsLatencyInMillis(SCORE_ADS_LATENCY_IN_MILLIS)
+ .setGetAdScoresLatencyInMillis(GET_AD_SCORES_LATENCY_IN_MILLIS)
+ .setGetAdScoresResultCode(GET_AD_SCORES_RESULT_CODE)
+ .setNumOfCasEnteringScoring(NUM_OF_CAS_ENTERING_SCORING)
+ .setNumOfRemarketingAdsEnteringScoring(
+ NUM_OF_REMARKETING_ADS_ENTERING_SCORING)
+ .setNumOfContextualAdsEnteringScoring(
+ NUM_OF_CONTEXTUAL_ADS_ENTERING_SCORING)
+ .setRunAdScoringLatencyInMillis(RUN_AD_SCORING_LATENCY_IN_MILLIS)
+ .setRunAdScoringResultCode(RUN_AD_SCORING_RESULT_CODE)
+ .build();
+ assertEquals(
+ GET_AD_SELECTION_LOGIC_LATENCY_IN_MILLIS,
+ stats.getGetAdSelectionLogicLatencyInMillis());
+ assertEquals(GET_AD_SELECTION_LOGIC_RESULT_CODE, stats.getGetAdSelectionLogicResultCode());
+ assertEquals(GET_AD_SELECTION_LOGIC_SCRIPT_TYPE, stats.getGetAdSelectionLogicScriptType());
+ assertEquals(
+ FETCHED_AD_SELECTION_LOGIC_SCRIPT_SIZE_IN_BYTES,
+ stats.getFetchedAdSelectionLogicScriptSizeInBytes());
+ assertEquals(
+ GET_TRUSTED_SCORING_SIGNALS_LATENCY_IN_MILLIS,
+ stats.getGetTrustedScoringSignalsLatencyInMillis());
+ assertEquals(
+ GET_TRUSTED_SCORING_SIGNALS_RESULT_CODE,
+ stats.getGetTrustedScoringSignalsResultCode());
+ assertEquals(
+ FETCHED_TRUSTED_SCORING_SIGNALS_DATA_SIZE_IN_BYTES,
+ stats.getFetchedTrustedScoringSignalsDataSizeInBytes());
+ assertEquals(SCORE_ADS_LATENCY_IN_MILLIS, stats.getScoreAdsLatencyInMillis());
+ assertEquals(GET_AD_SCORES_LATENCY_IN_MILLIS, stats.getGetAdScoresLatencyInMillis());
+ assertEquals(GET_AD_SCORES_RESULT_CODE, stats.getGetAdScoresResultCode());
+ assertEquals(NUM_OF_CAS_ENTERING_SCORING, stats.getNumOfCasEnteringScoring());
+ assertEquals(
+ NUM_OF_REMARKETING_ADS_ENTERING_SCORING,
+ stats.getNumOfRemarketingAdsEnteringScoring());
+ assertEquals(
+ NUM_OF_CONTEXTUAL_ADS_ENTERING_SCORING,
+ stats.getNumOfContextualAdsEnteringScoring());
+ assertEquals(RUN_AD_SCORING_LATENCY_IN_MILLIS, stats.getRunAdScoringLatencyInMillis());
+ assertEquals(RUN_AD_SCORING_RESULT_CODE, stats.getRunAdScoringResultCode());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdSelectionProcessReportedStatsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdSelectionProcessReportedStatsTest.java
new file mode 100644
index 000000000..9362547db
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/RunAdSelectionProcessReportedStatsTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.stats;
+
+import static org.junit.Assert.assertEquals;
+
+import android.adservices.common.AdServicesStatusUtils;
+
+import org.junit.Test;
+
+/** Unit tests for {@link RunAdSelectionProcessReportedStats}. */
+public class RunAdSelectionProcessReportedStatsTest {
+ static final boolean IS_RMKT_ADS_WON = true;
+ static final int DB_AD_SELECTION_SIZE_IN_BYTES = 100;
+ static final int PERSIST_AD_SELECTION_LATENCY_IN_MILLIS = 10;
+ static final int RUN_AD_SELECTION_LATENCY_IN_MILLIS = 10;
+ static final int PERSIST_AD_SELECTION_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int RUN_AD_SELECTION_RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+
+ @Test
+ public void testBuilderCreateSuccess() {
+ RunAdSelectionProcessReportedStats stats =
+ RunAdSelectionProcessReportedStats.builder()
+ .setIsRemarketingAdsWon(IS_RMKT_ADS_WON)
+ .setDBAdSelectionSizeInBytes(DB_AD_SELECTION_SIZE_IN_BYTES)
+ .setPersistAdSelectionLatencyInMillis(
+ PERSIST_AD_SELECTION_LATENCY_IN_MILLIS)
+ .setPersistAdSelectionResultCode(PERSIST_AD_SELECTION_RESULT_CODE)
+ .setRunAdSelectionLatencyInMillis(RUN_AD_SELECTION_LATENCY_IN_MILLIS)
+ .setRunAdSelectionResultCode(RUN_AD_SELECTION_RESULT_CODE)
+ .build();
+ assertEquals(IS_RMKT_ADS_WON, stats.getIsRemarketingAdsWon());
+ assertEquals(DB_AD_SELECTION_SIZE_IN_BYTES, stats.getDBAdSelectionSizeInBytes());
+ assertEquals(
+ PERSIST_AD_SELECTION_LATENCY_IN_MILLIS,
+ stats.getPersistAdSelectionLatencyInMillis());
+ assertEquals(PERSIST_AD_SELECTION_RESULT_CODE, stats.getPersistAdSelectionResultCode());
+ assertEquals(RUN_AD_SELECTION_LATENCY_IN_MILLIS, stats.getRunAdSelectionLatencyInMillis());
+ assertEquals(RUN_AD_SELECTION_RESULT_CODE, stats.getRunAdSelectionResultCode());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/UpdateCustomAudienceProcessReportedStatsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/UpdateCustomAudienceProcessReportedStatsTest.java
new file mode 100644
index 000000000..57f867632
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/UpdateCustomAudienceProcessReportedStatsTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.stats;
+
+import static org.junit.Assert.assertEquals;
+
+import android.adservices.common.AdServicesStatusUtils;
+
+import org.junit.Test;
+
+/** Unit tests for {@link UpdateCustomAudienceProcessReportedStats}. */
+public class UpdateCustomAudienceProcessReportedStatsTest {
+ static final int LATENCY_IN_MILLIS = 10;
+ static final int RESULT_CODE = AdServicesStatusUtils.STATUS_SUCCESS;
+ static final int DATA_SIZE_OF_ADS_IN_BYTES = 10;
+ static final int NUM_OF_ADS = 5;
+
+ @Test
+ public void testBuilderCreateSuccess() {
+ UpdateCustomAudienceProcessReportedStats stats =
+ UpdateCustomAudienceProcessReportedStats.builder()
+ .setLatencyInMills(LATENCY_IN_MILLIS)
+ .setResultCode(RESULT_CODE)
+ .setDataSizeOfAdsInBytes(DATA_SIZE_OF_ADS_IN_BYTES)
+ .setNumOfAds(NUM_OF_ADS)
+ .build();
+ assertEquals(LATENCY_IN_MILLIS, stats.getLatencyInMills());
+ assertEquals(RESULT_CODE, stats.getResultCode());
+ assertEquals(DATA_SIZE_OF_ADS_IN_BYTES, stats.getDataSizeOfAdsInBytes());
+ assertEquals(NUM_OF_ADS, stats.getNumOfAds());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/AppUpdateManagerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/AppUpdateManagerTest.java
index 392dc77a5..6e3028e92 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/AppUpdateManagerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/AppUpdateManagerTest.java
@@ -16,14 +16,22 @@
package com.android.adservices.service.topics;
+import static com.android.adservices.service.topics.EpochManager.PADDED_TOP_TOPICS_STRING;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,15 +65,18 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
-import java.util.stream.Collectors;
/** Unit tests for {@link com.android.adservices.service.topics.AppUpdateManager} */
public class AppUpdateManagerTest {
@SuppressWarnings({"unused"})
private static final String TAG = "AppInstallationInfoManagerTest";
+ private static final String EMPTY_SDK = "";
+ private static final long TAXONOMY_VERSION = 1L;
+ private static final long MODEL_VERSION = 1L;
+
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
- private final DbHelper mDbHelper = DbTestUtil.getDbHelperForTest();
+ private final DbHelper mDbHelper = spy(DbTestUtil.getDbHelperForTest());
private AppUpdateManager mAppUpdateManager;
private TopicsDao mTopicsDao;
@@ -89,8 +100,9 @@ public class AppUpdateManagerTest {
DbTestUtil.deleteTable(TopicsTables.ReturnedTopicContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.UsageHistoryContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.AppUsageHistoryContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.TopicContributorsContract.TABLE);
- mAppUpdateManager = new AppUpdateManager(mTopicsDao, new Random(), mMockFlags);
+ mAppUpdateManager = new AppUpdateManager(mDbHelper, mTopicsDao, new Random(), mMockFlags);
}
@Test
@@ -110,13 +122,11 @@ public class AppUpdateManagerTest {
// Begin to persist data into database
// Handle AppClassificationTopicsContract
- final long taxonomyVersion = 1L;
- final long modelVersion = 1L;
final long epochId1 = 1L;
final int topicId1 = 1;
final int numberOfLookBackEpochs = 1;
- Topic topic1 = Topic.create(topicId1, taxonomyVersion, modelVersion);
+ Topic topic1 = Topic.create(topicId1, TAXONOMY_VERSION, MODEL_VERSION);
Map<String, List<Topic>> appClassificationTopicsMap1 = new HashMap<>();
appClassificationTopicsMap1.put(app1, Collections.singletonList(topic1));
@@ -130,9 +140,9 @@ public class AppUpdateManagerTest {
// Handle UsageHistoryContract
final String sdk1 = "sdk1";
- mTopicsDao.recordUsageHistory(epochId1, app1, "");
+ mTopicsDao.recordUsageHistory(epochId1, app1, EMPTY_SDK);
mTopicsDao.recordUsageHistory(epochId1, app1, sdk1);
- mTopicsDao.recordUsageHistory(epochId1, app2, "");
+ mTopicsDao.recordUsageHistory(epochId1, app2, EMPTY_SDK);
mTopicsDao.recordUsageHistory(epochId1, app2, sdk1);
// Verify UsageHistoryContract has both apps
@@ -160,16 +170,16 @@ public class AppUpdateManagerTest {
// Handle ReturnedTopicContract
Map<Pair<String, String>, Topic> returnedAppSdkTopics = new HashMap<>();
- returnedAppSdkTopics.put(Pair.create(app1, /* sdk */ ""), topic1);
+ returnedAppSdkTopics.put(Pair.create(app1, EMPTY_SDK), topic1);
returnedAppSdkTopics.put(Pair.create(app1, sdk1), topic1);
- returnedAppSdkTopics.put(Pair.create(app2, /* sdk */ ""), topic1);
+ returnedAppSdkTopics.put(Pair.create(app2, EMPTY_SDK), topic1);
returnedAppSdkTopics.put(Pair.create(app2, sdk1), topic1);
mTopicsDao.persistReturnedAppTopicsMap(epochId1, returnedAppSdkTopics);
Map<Pair<String, String>, Topic> expectedReturnedTopics = new HashMap<>();
- expectedReturnedTopics.put(Pair.create(app1, /* sdk */ ""), topic1);
+ expectedReturnedTopics.put(Pair.create(app1, EMPTY_SDK), topic1);
expectedReturnedTopics.put(Pair.create(app1, sdk1), topic1);
- expectedReturnedTopics.put(Pair.create(app2, /* sdk */ ""), topic1);
+ expectedReturnedTopics.put(Pair.create(app2, EMPTY_SDK), topic1);
expectedReturnedTopics.put(Pair.create(app2, sdk1), topic1);
// Verify ReturnedTopicContract has both apps
@@ -180,7 +190,7 @@ public class AppUpdateManagerTest {
.isEqualTo(expectedReturnedTopics);
// Reconcile uninstalled applications
- mAppUpdateManager.reconcileUninstalledApps(mContext);
+ mAppUpdateManager.reconcileUninstalledApps(mContext, epochId1);
verify(mContext).getPackageManager();
verify(mMockPackageManager).getInstalledApplications(Mockito.any());
@@ -200,7 +210,7 @@ public class AppUpdateManagerTest {
.doesNotContain(app2);
// Returned Topics Map contains only App1 paris
Map<Pair<String, String>, Topic> expectedReturnedTopicsAfterWiping = new HashMap<>();
- expectedReturnedTopicsAfterWiping.put(Pair.create(app1, /* sdk */ ""), topic1);
+ expectedReturnedTopicsAfterWiping.put(Pair.create(app1, EMPTY_SDK), topic1);
expectedReturnedTopicsAfterWiping.put(Pair.create(app1, sdk1), topic1);
assertThat(
mTopicsDao
@@ -210,6 +220,183 @@ public class AppUpdateManagerTest {
}
@Test
+ public void testReconcileUninstalledApps_handleTopicsWithoutContributor() {
+ // Test Setup:
+ // * Both app1 and app2 have usages in database. app2 won't be current installed app list
+ // that is returned by mocked Package Manager, so it'll be regarded as an unhandled
+ // uninstalled app.
+ // * In Epoch1, app1 is classified to topic1, topic2. app2 is classified to topic1, topic3.
+ // Both app1 and app2 have topic3 as returned topic as they both call Topics API via sdk.
+ // * In Epoch2, both app1 and app2 are classified to topic1, topic3. (verify epoch basis)
+ // * In Epoch3, both app2 and app3 are classified to topic1. app4 learns topic1 from sdk and
+ // also returns topic1. After app2 and app4 are uninstalled, topic1 should be removed for
+ // epoch3 and app3 should have no returned topic. (verify consecutive deletion on a topic)
+ // * In Epoch4, app2 is uninstalled. topic3 will be removed in Epoch1 as it has app2 as the
+ // only contributor, while topic3 will stay in Epoch2 as app2 contributes to it.
+ final String app1 = "app1";
+ final String app2 = "app2";
+ final String sdk = "sdk";
+ final long epoch1 = 1L;
+ final long epoch2 = 2L;
+ final long epoch4 = 4L;
+ final int numberOfLookBackEpochs = 3;
+
+ Topic topic1 = Topic.create(1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(3, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic4 = Topic.create(4, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic5 = Topic.create(5, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic6 = Topic.create(6, TAXONOMY_VERSION, MODEL_VERSION);
+
+ // Mock Package Manager for installed applications
+ ApplicationInfo appInfo1 = new ApplicationInfo();
+ appInfo1.packageName = app1;
+
+ when(mMockPackageManager.getInstalledApplications(Mockito.any()))
+ .thenReturn(List.of(appInfo1));
+
+ // Persist to AppClassificationTopics table
+ mTopicsDao.persistAppClassificationTopics(
+ epoch1, Map.of(app1, List.of(topic1, topic2), app2, List.of(topic1, topic3)));
+ mTopicsDao.persistAppClassificationTopics(
+ epoch2, Map.of(app1, List.of(topic1, topic3), app2, List.of(topic1, topic3)));
+
+ // Persist to TopTopics table
+ mTopicsDao.persistTopTopics(
+ epoch1, List.of(topic1, topic2, topic3, topic4, topic5, topic6));
+ mTopicsDao.persistTopTopics(
+ epoch2, List.of(topic1, topic2, topic3, topic4, topic5, topic6));
+
+ // Persist to TopicContributors table
+ mTopicsDao.persistTopicContributors(epoch1, Map.of(topic1.getTopic(), Set.of(app1, app2)));
+ mTopicsDao.persistTopicContributors(epoch1, Map.of(topic2.getTopic(), Set.of(app1)));
+ mTopicsDao.persistTopicContributors(epoch1, Map.of(topic3.getTopic(), Set.of(app2)));
+ mTopicsDao.persistTopicContributors(epoch2, Map.of(topic1.getTopic(), Set.of(app1, app2)));
+ mTopicsDao.persistTopicContributors(epoch2, Map.of(topic3.getTopic(), Set.of(app1, app2)));
+
+ // Persist to ReturnedTopics table
+ mTopicsDao.persistReturnedAppTopicsMap(epoch1, Map.of(Pair.create(app1, sdk), topic3));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch1, Map.of(Pair.create(app2, sdk), topic3));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch2, Map.of(Pair.create(app1, sdk), topic3));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch2, Map.of(Pair.create(app2, sdk), topic3));
+
+ // Mock flag value to remove dependency of actual flag value
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
+ // Enable the feature
+ when(mDbHelper.supportsTopicContributorsTable()).thenReturn(true);
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+
+ // Execute reconciliation to handle app2
+ mAppUpdateManager.reconcileUninstalledApps(mContext, epoch4);
+
+ // Verify Returned Topics in [1, 3]. app2 should have no returnedTopics as it's uninstalled.
+ // app1 only has returned topic at Epoch2 as topic3 is removed from Epoch1.
+ Map<Long, Map<Pair<String, String>, Topic>> expectedReturnedTopicsMap =
+ Map.of(epoch2, Map.of(Pair.create(app1, sdk), topic3));
+ assertThat(mTopicsDao.retrieveReturnedTopics(epoch4 - 1, numberOfLookBackEpochs))
+ .isEqualTo(expectedReturnedTopicsMap);
+
+ // Verify TopicContributors Map is updated: app1 should be removed after the uninstallation.
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch1))
+ .isEqualTo(
+ Map.of(topic1.getTopic(), Set.of(app1), topic2.getTopic(), Set.of(app1)));
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch2))
+ .isEqualTo(
+ Map.of(topic1.getTopic(), Set.of(app1), topic3.getTopic(), Set.of(app1)));
+ }
+
+ @Test
+ public void testReconcileUninstalledApps_contributorDeletionsToSameTopic() {
+ // Test Setup:
+ // * app1 has usages in database. Both app2 and app3 won't be current installed app list
+ // that is returned by mocked Package Manager, so they'll be regarded as an unhandled
+ // uninstalled apps.
+ // * Both app2 and app3 are contributors to topic1 and return topic1. app1 is not the
+ // contributor but also returns topic1, learnt via same SDK.
+ final String app1 = "app1";
+ final String app2 = "app2";
+ final String app3 = "app3";
+ final String sdk = "sdk";
+ final long epoch1 = 1L;
+ final long epoch2 = 2L;
+ final int numberOfLookBackEpochs = 3;
+
+ Topic topic1 = Topic.create(1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(3, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic4 = Topic.create(4, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic5 = Topic.create(5, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic6 = Topic.create(6, TAXONOMY_VERSION, MODEL_VERSION);
+
+ // Mock Package Manager for installed applications
+ ApplicationInfo appInfo1 = new ApplicationInfo();
+ appInfo1.packageName = app1;
+
+ when(mMockPackageManager.getInstalledApplications(Mockito.any()))
+ .thenReturn(List.of(appInfo1));
+
+ // Persist to AppClassificationTopics table
+ mTopicsDao.persistAppClassificationTopics(
+ epoch1, Map.of(app2, List.of(topic1), app3, List.of(topic1)));
+
+ // Persist to TopTopics table
+ mTopicsDao.persistTopTopics(
+ epoch1, List.of(topic1, topic2, topic3, topic4, topic5, topic6));
+
+ // Persist to TopicContributors table
+ mTopicsDao.persistTopicContributors(epoch1, Map.of(topic1.getTopic(), Set.of(app2, app3)));
+
+ // Persist to ReturnedTopics table
+ mTopicsDao.persistReturnedAppTopicsMap(epoch1, Map.of(Pair.create(app1, sdk), topic1));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch1, Map.of(Pair.create(app2, sdk), topic1));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch1, Map.of(Pair.create(app3, sdk), topic1));
+
+ // Mock flag value to remove dependency of actual flag value
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
+ // Enable the feature
+ doReturn(true).when(mDbHelper).supportsTopicContributorsTable();
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+
+ // Execute reconciliation to handle app2 and app3
+ mAppUpdateManager.reconcileUninstalledApps(mContext, epoch2);
+
+ // Verify Returned Topics in epoch 1. app2 and app3 are uninstalled, so they definitely
+ // don't have a returned topic. As topic1 has no contributors after uninstallations of app2
+ // and app3, it's removed from database. Therefore, app1 should have no returned topics as
+ // well.
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch1)).isEmpty();
+ assertThat(mTopicsDao.retrieveReturnedTopics(epoch1, numberOfLookBackEpochs)).isEmpty();
+ }
+
+ @Test
+ public void testReconcileUninstalledApps_disableTopicContributorsCheck() {
+ AppUpdateManager appUpdateManager =
+ spy(new AppUpdateManager(mDbHelper, mTopicsDao, new Random(), mMockFlags));
+
+ // Do not check actual usage of related methods.
+ doNothing().when(appUpdateManager).handleTopTopicsWithoutContributors(anyLong(), any());
+ doNothing().when(appUpdateManager).deleteAppDataFromTableByApps(any());
+ doReturn(Set.of()).when(appUpdateManager).getCurrentInstalledApps(any());
+ doReturn(Set.of("anyValue")).when(appUpdateManager).getUnhandledUninstalledApps(any());
+ doReturn(3).when(mMockFlags).getTopicsNumberOfLookBackEpochs();
+
+ // verify feature is enabled
+ doReturn(false).when(appUpdateManager).supportsTopicContributorFeature();
+ appUpdateManager.reconcileUninstalledApps(mContext, /* any positive long */ 1L);
+ // handleTopTopicsWithoutContributors() is not invoked.
+ verify(appUpdateManager, never()).handleTopTopicsWithoutContributors(anyLong(), any());
+ verify(appUpdateManager).deleteAppDataFromTableByApps(any());
+
+ // verify feature is disabled
+ doReturn(true).when(appUpdateManager).supportsTopicContributorFeature();
+ appUpdateManager.reconcileUninstalledApps(mContext, /* any positive long */ 1L);
+ // handleTopTopicsWithoutContributors() is invoked.
+ verify(appUpdateManager, atLeastOnce())
+ .handleTopTopicsWithoutContributors(anyLong(), any());
+ verify(appUpdateManager, times(2)).deleteAppDataFromTableByApps(any());
+ }
+
+ @Test
public void testGetUnhandledUninstalledApps() {
final long epochId = 1L;
Set<String> currentInstalledApps = Set.of("app1", "app2", "app5");
@@ -222,10 +409,10 @@ public class AppUpdateManagerTest {
mTopicsDao.persistReturnedAppTopicsMap(
epochId,
Map.of(
- Pair.create("app2", ""),
+ Pair.create("app2", EMPTY_SDK),
Topic.create(
/* topic ID */ 1, /* taxonomyVersion */ 1L, /* model version */ 1L),
- Pair.create("app4", ""),
+ Pair.create("app4", EMPTY_SDK),
Topic.create(
/* topic ID */ 1, /* taxonomyVersion */
1L, /* model version */
@@ -252,10 +439,10 @@ public class AppUpdateManagerTest {
mTopicsDao.persistReturnedAppTopicsMap(
epochId,
Map.of(
- Pair.create("app2", ""),
+ Pair.create("app2", EMPTY_SDK),
Topic.create(
/* topic ID */ 1, /* taxonomyVersion */ 1L, /* model version */ 1L),
- Pair.create("app6", ""),
+ Pair.create("app6", EMPTY_SDK),
Topic.create(
/* topic ID */ 1, /* taxonomyVersion */
1L, /* model version */
@@ -280,13 +467,11 @@ public class AppUpdateManagerTest {
// Therefore, database will only contain app1's data.
// Handle AppClassificationTopicsContract
- final long taxonomyVersion = 1L;
- final long modelVersion = 1L;
final long epochId1 = 1L;
final int topicId1 = 1;
final int numberOfLookBackEpochs = 1;
- Topic topic1 = Topic.create(topicId1, taxonomyVersion, modelVersion);
+ Topic topic1 = Topic.create(topicId1, TAXONOMY_VERSION, MODEL_VERSION);
mTopicsDao.persistAppClassificationTopics(epochId1, Map.of(app1, List.of(topic1)));
mTopicsDao.persistAppClassificationTopics(epochId1, Map.of(app2, List.of(topic1)));
@@ -298,11 +483,11 @@ public class AppUpdateManagerTest {
// Handle UsageHistoryContract
final String sdk1 = "sdk1";
- mTopicsDao.recordUsageHistory(epochId1, app1, "");
+ mTopicsDao.recordUsageHistory(epochId1, app1, EMPTY_SDK);
mTopicsDao.recordUsageHistory(epochId1, app1, sdk1);
- mTopicsDao.recordUsageHistory(epochId1, app2, "");
+ mTopicsDao.recordUsageHistory(epochId1, app2, EMPTY_SDK);
mTopicsDao.recordUsageHistory(epochId1, app2, sdk1);
- mTopicsDao.recordUsageHistory(epochId1, app3, "");
+ mTopicsDao.recordUsageHistory(epochId1, app3, EMPTY_SDK);
mTopicsDao.recordUsageHistory(epochId1, app3, sdk1);
// Verify UsageHistoryContract has both apps
@@ -332,11 +517,11 @@ public class AppUpdateManagerTest {
// Handle ReturnedTopicContract
Map<Pair<String, String>, Topic> returnedAppSdkTopics = new HashMap<>();
- returnedAppSdkTopics.put(Pair.create(app1, /* sdk */ ""), topic1);
+ returnedAppSdkTopics.put(Pair.create(app1, EMPTY_SDK), topic1);
returnedAppSdkTopics.put(Pair.create(app1, sdk1), topic1);
- returnedAppSdkTopics.put(Pair.create(app2, /* sdk */ ""), topic1);
+ returnedAppSdkTopics.put(Pair.create(app2, EMPTY_SDK), topic1);
returnedAppSdkTopics.put(Pair.create(app2, sdk1), topic1);
- returnedAppSdkTopics.put(Pair.create(app3, /* sdk */ ""), topic1);
+ returnedAppSdkTopics.put(Pair.create(app3, EMPTY_SDK), topic1);
returnedAppSdkTopics.put(Pair.create(app3, sdk1), topic1);
mTopicsDao.persistReturnedAppTopicsMap(epochId1, returnedAppSdkTopics);
@@ -366,7 +551,7 @@ public class AppUpdateManagerTest {
.isEqualTo(Set.of(app1, sdk1));
// Returned Topics Map contains only App1 paris
Map<Pair<String, String>, Topic> expectedReturnedTopicsAfterWiping = new HashMap<>();
- expectedReturnedTopicsAfterWiping.put(Pair.create(app1, /* sdk */ ""), topic1);
+ expectedReturnedTopicsAfterWiping.put(Pair.create(app1, EMPTY_SDK), topic1);
expectedReturnedTopicsAfterWiping.put(Pair.create(app1, sdk1), topic1);
assertThat(
mTopicsDao
@@ -376,47 +561,43 @@ public class AppUpdateManagerTest {
}
@Test
- public void testDeleteAppDataFromTableByApp_nullUninstalledAppName() {
+ public void testDeleteAppDataFromTableByApps_nullUninstalledAppName() {
assertThrows(
NullPointerException.class,
() -> mAppUpdateManager.deleteAppDataFromTableByApps(null));
}
@Test
- public void testDeleteAppDataFromTableByApp_nonExistingUninstalledAppName() {
+ public void testDeleteAppDataFromTableByApps_nonExistingUninstalledAppName() {
// To test it won't throw by calling the method with non-existing application name
mAppUpdateManager.deleteAppDataFromTableByApps(List.of("app"));
}
@Test
- public void testDeleteAppDataByUri() {
- // Mock AppUpdateManager to check the invocation of deleteAppDataByUri() because
- // the functionality has already been tested in testDeleteAppDataFromTableByApp
- AppUpdateManager appUpdateManager =
- Mockito.spy(new AppUpdateManager(mTopicsDao, new Random(), mMockFlags));
+ public void testDeleteAppDataFromTableByApps_topicContributorsTable() {
+ final long epoch1 = 1L;
- final String appName = "app";
- Uri packageUri = Uri.parse(appName);
-
- // Only verify the invocation of deleteAppDataFromTableByApp()
- doNothing().when(appUpdateManager).deleteAppDataFromTableByApps(eq(List.of(appName)));
-
- appUpdateManager.deleteAppDataByUri(packageUri);
-
- verify(appUpdateManager).deleteAppDataByUri(eq(packageUri));
- verify(appUpdateManager).deleteAppDataFromTableByApps(eq(List.of(appName)));
- }
-
- @Test
- public void testDeleteAppDataByUri_nullUri() {
- assertThrows(NullPointerException.class, () -> mAppUpdateManager.deleteAppDataByUri(null));
- }
-
- @Test
- public void testDeleteAppDataByUri_nonExistingUninstalledAppName() {
- // To test it won't throw by calling the method with Uri containing
- // non-existing application name.
- mAppUpdateManager.deleteAppDataByUri(Uri.parse("app"));
+ final String app = "app";
+ final int topicId = 1;
+
+ Map<Integer, Set<String>> topicContributorsMap = Map.of(topicId, Set.of(app));
+ mTopicsDao.persistTopicContributors(epoch1, topicContributorsMap);
+
+ // Enable Database Version 3
+ when(mDbHelper.supportsTopicContributorsTable()).thenReturn(true);
+
+ // Feature flag is Off
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(false);
+ mAppUpdateManager.deleteAppDataFromTableByApps(List.of(app));
+ // Table should not be cleared
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch1))
+ .isEqualTo(topicContributorsMap);
+
+ // Feature flag is On
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+ mAppUpdateManager.deleteAppDataFromTableByApps(List.of(app));
+ // Table should be cleared
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch1)).isEmpty();
}
@Test
@@ -424,11 +605,8 @@ public class AppUpdateManagerTest {
final String app1 = "app1";
final String app2 = "app2";
final long currentEpochId = 4L;
- final long taxonomyVersion = 1L;
- final long modelVersion = 1L;
final int numOfLookBackEpochs = 3;
final int topicsNumberOfTopTopics = 5;
- final int topicsNumberOfRandomTopics = 1;
final int topicsPercentageForRandomTopic = 5;
// As selectAssignedTopicFromTopTopics() randomly assigns a top topic, pass in a Mocked
@@ -445,14 +623,13 @@ public class AppUpdateManagerTest {
topicsPercentageForRandomTopic, // Will select a regular topic
1, // Index of second topic
0, // Will select a random topic
- topicsNumberOfRandomTopics - 1 // Select the last random topic
+ 0, // Select the first random topic
});
AppUpdateManager appUpdateManager =
- new AppUpdateManager(mTopicsDao, mockRandom, mMockFlags);
+ new AppUpdateManager(mDbHelper, mTopicsDao, mockRandom, mMockFlags);
// Mock Flags to get an independent result
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numOfLookBackEpochs);
when(mMockFlags.getTopicsNumberOfTopTopics()).thenReturn(topicsNumberOfTopTopics);
- when(mMockFlags.getTopicsNumberOfRandomTopics()).thenReturn(topicsNumberOfRandomTopics);
when(mMockFlags.getTopicsPercentageForRandomTopic())
.thenReturn(topicsPercentageForRandomTopic);
@@ -465,12 +642,12 @@ public class AppUpdateManagerTest {
when(mMockPackageManager.getInstalledApplications(Mockito.any()))
.thenReturn(List.of(appInfo1, appInfo2));
- Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
- Topic topic2 = Topic.create(/* topic */ 2, taxonomyVersion, modelVersion);
- Topic topic3 = Topic.create(/* topic */ 3, taxonomyVersion, modelVersion);
- Topic topic4 = Topic.create(/* topic */ 4, taxonomyVersion, modelVersion);
- Topic topic5 = Topic.create(/* topic */ 5, taxonomyVersion, modelVersion);
- Topic topic6 = Topic.create(/* topic */ 6, taxonomyVersion, modelVersion);
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(/* topic */ 3, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic4 = Topic.create(/* topic */ 4, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic5 = Topic.create(/* topic */ 5, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic6 = Topic.create(/* topic */ 6, TAXONOMY_VERSION, MODEL_VERSION);
List<Topic> topTopics = List.of(topic1, topic2, topic3, topic4, topic5, topic6);
// Begin to persist data into database
@@ -479,13 +656,18 @@ public class AppUpdateManagerTest {
// installed app.
mTopicsDao.recordAppUsageHistory(currentEpochId - 1, app1);
// Unused but to mimic what happens in reality
- mTopicsDao.recordUsageHistory(currentEpochId - 1, app1, /* sdk */ "sdk");
+ mTopicsDao.recordUsageHistory(currentEpochId - 1, app1, "sdk");
// Persist top topics into database for last 3 epochs
for (long epochId = currentEpochId - 1;
epochId >= currentEpochId - numOfLookBackEpochs;
epochId--) {
mTopicsDao.persistTopTopics(epochId, topTopics);
+ // Persist topics to TopicContributors Table avoid being filtered out
+ for (Topic topic : topTopics) {
+ mTopicsDao.persistTopicContributors(
+ epochId, Map.of(topic.getTopic(), Set.of(app1, app2)));
+ }
}
// Assign topics to past epochs
@@ -493,27 +675,22 @@ public class AppUpdateManagerTest {
Map<Long, Map<Pair<String, String>, Topic>> expectedReturnedTopics = new HashMap<>();
expectedReturnedTopics.put(
- currentEpochId - 1, Map.of(Pair.create(app2, /* sdk */ ""), topic1));
+ currentEpochId - 1, Map.of(Pair.create(app2, EMPTY_SDK), topic1));
expectedReturnedTopics.put(
- currentEpochId - 2, Map.of(Pair.create(app2, /* sdk */ ""), topic2));
+ currentEpochId - 2, Map.of(Pair.create(app2, EMPTY_SDK), topic2));
expectedReturnedTopics.put(
- currentEpochId - 3, Map.of(Pair.create(app2, /* sdk */ ""), topic6));
+ currentEpochId - 3, Map.of(Pair.create(app2, EMPTY_SDK), topic6));
assertThat(mTopicsDao.retrieveReturnedTopics(currentEpochId - 1, numOfLookBackEpochs))
.isEqualTo(expectedReturnedTopics);
verify(mMockFlags).getTopicsNumberOfLookBackEpochs();
verify(mMockFlags).getTopicsNumberOfTopTopics();
- verify(mMockFlags).getTopicsNumberOfRandomTopics();
verify(mMockFlags).getTopicsPercentageForRandomTopic();
}
@Test
public void testSelectAssignedTopicFromTopTopics() {
- final long taxonomyVersion = 1L;
- final long modelVersion = 1L;
- final int topicsNumberOfTopTopics = 5;
- final int topicsNumberOfRandomTopics = 1;
final int topicsPercentageForRandomTopic = 5;
// Test the randomness with pre-defined values
@@ -521,75 +698,42 @@ public class AppUpdateManagerTest {
new MockRandom(
new long[] {
0, // Will select a random topic
- topicsNumberOfRandomTopics - 1, // Select the last random topic
+ 0, // Select the first random topic
topicsPercentageForRandomTopic, // Will select a regular topic
0 // Select the first regular topic
});
AppUpdateManager appUpdateManager =
- new AppUpdateManager(mTopicsDao, mockRandom, mMockFlags);
+ new AppUpdateManager(mDbHelper, mTopicsDao, mockRandom, mMockFlags);
- Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
- Topic topic2 = Topic.create(/* topic */ 2, taxonomyVersion, modelVersion);
- Topic topic3 = Topic.create(/* topic */ 3, taxonomyVersion, modelVersion);
- Topic topic4 = Topic.create(/* topic */ 4, taxonomyVersion, modelVersion);
- Topic topic5 = Topic.create(/* topic */ 5, taxonomyVersion, modelVersion);
- Topic topic6 = Topic.create(/* topic */ 6, taxonomyVersion, modelVersion);
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(/* topic */ 3, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic6 = Topic.create(/* topic */ 6, TAXONOMY_VERSION, MODEL_VERSION);
- List<Topic> topTopics = Arrays.asList(topic1, topic2, topic3, topic4, topic5, topic6);
+ List<Topic> regularTopics = List.of(topic1, topic2, topic3);
+ List<Topic> randomTopics = List.of(topic6);
- // In the first invocation, mockRandom returns a 0 that indicates a regular top topic will
- // be returned, and following by another 0 to select the first regular top topic.
- Topic regularTopTopic =
- appUpdateManager.selectAssignedTopicFromTopTopics(
- topTopics,
- topicsNumberOfTopTopics,
- topicsNumberOfRandomTopics,
- topicsPercentageForRandomTopic);
- assertThat(regularTopTopic).isEqualTo(topic6);
-
- // In the second invocation, mockRandom returns a 5 that indicates a random top topic will
- // be returned, and following by a 0 to select the first(only) random top topic.
+ // In the first invocation, mockRandom returns a 0 that indicates a random top topic will
+ // be returned, and followed by another 0 to select the first(only) random top topic.
Topic randomTopTopic =
appUpdateManager.selectAssignedTopicFromTopTopics(
- topTopics,
- topicsNumberOfTopTopics,
- topicsNumberOfRandomTopics,
- topicsPercentageForRandomTopic);
- assertThat(randomTopTopic).isEqualTo(topic1);
- }
+ regularTopics, randomTopics, topicsPercentageForRandomTopic);
+ assertThat(randomTopTopic).isEqualTo(topic6);
- @Test
- public void testSelectAssignedTopicFromTopTopics_invalidSize() {
- List<Integer> intTopics = Arrays.asList(1, 2, 3, 4, 5, 6);
- List<Topic> topTopics =
- intTopics.stream()
- .map(
- intTopic ->
- Topic.create(
- intTopic,
- /* Taxonomy Version */ 1L, /* Model Version */
- 1L))
- .collect(Collectors.toList());
- assertThrows(
- IllegalArgumentException.class,
- () ->
- mAppUpdateManager.selectAssignedTopicFromTopTopics(
- topTopics,
- /* topicsNumberOfTopTopics */ 4,
- /* topicsNumberOfRandomTopics */ 1,
- /* topicsPercentageForRandomTopic */ 5));
+ // In the second invocation, mockRandom returns a 5 that indicates a regular top topic will
+ // be returned, and following by a 0 to select the first regular top topic.
+ Topic regularTopTopic =
+ appUpdateManager.selectAssignedTopicFromTopTopics(
+ regularTopics, randomTopics, topicsPercentageForRandomTopic);
+ assertThat(regularTopTopic).isEqualTo(topic1);
}
@Test
public void testAssignTopicsToNewlyInstalledApps() {
final String appName = "app";
- Uri packageUri = Uri.parse(appName);
final long currentEpochId = 4L;
- final long taxonomyVersion = 1L;
- final long modelVersion = 1L;
final int numOfLookBackEpochs = 3;
final int topicsNumberOfTopTopics = 5;
- final int topicsNumberOfRandomTopics = 1;
final int topicsPercentageForRandomTopic = 5;
// As selectAssignedTopicFromTopTopics() randomly assigns a top topic, pass in a Mocked
@@ -606,26 +750,28 @@ public class AppUpdateManagerTest {
topicsPercentageForRandomTopic, // Will select a regular topic
1, // Index of second topic
0, // Will select a random topic
- topicsNumberOfRandomTopics - 1 // Select the last random topic
+ 0, // Select the first random topic
});
// Spy an instance of AppUpdateManager in order to mock selectAssignedTopicFromTopTopics()
// to avoid randomness.
AppUpdateManager appUpdateManager =
- new AppUpdateManager(mTopicsDao, mockRandom, mMockFlags);
+ new AppUpdateManager(mDbHelper, mTopicsDao, mockRandom, mMockFlags);
// Mock Flags to get an independent result
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numOfLookBackEpochs);
when(mMockFlags.getTopicsNumberOfTopTopics()).thenReturn(topicsNumberOfTopTopics);
- when(mMockFlags.getTopicsNumberOfRandomTopics()).thenReturn(topicsNumberOfRandomTopics);
when(mMockFlags.getTopicsPercentageForRandomTopic())
.thenReturn(topicsPercentageForRandomTopic);
-
- Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
- Topic topic2 = Topic.create(/* topic */ 2, taxonomyVersion, modelVersion);
- Topic topic3 = Topic.create(/* topic */ 3, taxonomyVersion, modelVersion);
- Topic topic4 = Topic.create(/* topic */ 4, taxonomyVersion, modelVersion);
- Topic topic5 = Topic.create(/* topic */ 5, taxonomyVersion, modelVersion);
- Topic topic6 = Topic.create(/* topic */ 6, taxonomyVersion, modelVersion);
+ // Enable TopContributors check
+ when(mDbHelper.supportsTopicContributorsTable()).thenReturn(true);
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(/* topic */ 3, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic4 = Topic.create(/* topic */ 4, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic5 = Topic.create(/* topic */ 5, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic6 = Topic.create(/* topic */ 6, TAXONOMY_VERSION, MODEL_VERSION);
List<Topic> topTopics = List.of(topic1, topic2, topic3, topic4, topic5, topic6);
// Persist top topics into database for last 3 epochs
@@ -633,29 +779,79 @@ public class AppUpdateManagerTest {
epochId >= currentEpochId - numOfLookBackEpochs;
epochId--) {
mTopicsDao.persistTopTopics(epochId, topTopics);
+
+ // Persist topics to TopicContributors Table avoid being filtered out
+ for (Topic topic : topTopics) {
+ mTopicsDao.persistTopicContributors(
+ epochId, Map.of(topic.getTopic(), Set.of(appName)));
+ }
}
// Assign topics to past epochs
- appUpdateManager.assignTopicsToNewlyInstalledApps(packageUri, currentEpochId);
+ appUpdateManager.assignTopicsToNewlyInstalledApps(appName, currentEpochId);
Map<Long, Map<Pair<String, String>, Topic>> expectedReturnedTopics = new HashMap<>();
expectedReturnedTopics.put(
- currentEpochId - 1, Map.of(Pair.create(appName, /* sdk */ ""), topic1));
+ currentEpochId - 1, Map.of(Pair.create(appName, EMPTY_SDK), topic1));
expectedReturnedTopics.put(
- currentEpochId - 2, Map.of(Pair.create(appName, /* sdk */ ""), topic2));
+ currentEpochId - 2, Map.of(Pair.create(appName, EMPTY_SDK), topic2));
expectedReturnedTopics.put(
- currentEpochId - 3, Map.of(Pair.create(appName, /* sdk */ ""), topic6));
+ currentEpochId - 3, Map.of(Pair.create(appName, EMPTY_SDK), topic6));
assertThat(mTopicsDao.retrieveReturnedTopics(currentEpochId - 1, numOfLookBackEpochs))
.isEqualTo(expectedReturnedTopics);
verify(mMockFlags).getTopicsNumberOfLookBackEpochs();
verify(mMockFlags).getTopicsNumberOfTopTopics();
- verify(mMockFlags).getTopicsNumberOfRandomTopics();
verify(mMockFlags).getTopicsPercentageForRandomTopic();
}
@Test
+ public void testAssignTopicsToNewlyInstalledApps_disableTopicContributorsCheck() {
+ AppUpdateManager appUpdateManager =
+ spy(new AppUpdateManager(mDbHelper, mTopicsDao, new Random(), mMockFlags));
+
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(/* topic */ 3, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic4 = Topic.create(/* topic */ 4, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic5 = Topic.create(/* topic */ 5, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic6 = Topic.create(/* topic */ 6, TAXONOMY_VERSION, MODEL_VERSION);
+ List<Topic> topTopics = List.of(topic1, topic2, topic3, topic4, topic5, topic6);
+ final long epochId = 1L;
+ final int numberOfLookBackEpochs = 3;
+ final int numberOfTopTopics = 5;
+ final int percentageForRandomTopic = 5;
+
+ // Do not check actual usage of related methods.
+ doReturn(List.of())
+ .when(appUpdateManager)
+ .filterRegularTopicsWithoutContributors(any(), anyLong());
+ doReturn(topic1)
+ .when(appUpdateManager)
+ .selectAssignedTopicFromTopTopics(any(), any(), eq(percentageForRandomTopic));
+ mTopicsDao.persistTopTopics(epochId, topTopics);
+ doReturn(numberOfLookBackEpochs).when(mMockFlags).getTopicsNumberOfLookBackEpochs();
+ doReturn(numberOfTopTopics).when(mMockFlags).getTopicsNumberOfTopTopics();
+ doReturn(percentageForRandomTopic).when(mMockFlags).getTopicsPercentageForRandomTopic();
+
+ // verify feature flag is off
+ doReturn(false).when(appUpdateManager).supportsTopicContributorFeature();
+ appUpdateManager.assignTopicsToNewlyInstalledApps("anyApp", epochId + 1);
+ // The filter method is not invoked.
+ verify(appUpdateManager, never()).filterRegularTopicsWithoutContributors(any(), anyLong());
+ verify(appUpdateManager, atLeastOnce())
+ .selectAssignedTopicFromTopTopics(any(), any(), eq(percentageForRandomTopic));
+
+ // verify feature flag is on
+ doReturn(true).when(appUpdateManager).supportsTopicContributorFeature();
+ appUpdateManager.assignTopicsToNewlyInstalledApps("anyApp", epochId + 1);
+ // The filter method is invoked.
+ verify(appUpdateManager, atLeastOnce())
+ .filterRegularTopicsWithoutContributors(any(), anyLong());
+ }
+
+ @Test
public void testAssignTopicsToSdkForAppInstallation() {
final String app = "app";
final String sdk = "sdk";
@@ -664,7 +860,7 @@ public class AppUpdateManagerTest {
final long taxonomyVersion = 1L;
final long modelVersion = 1L;
- Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
+ Pair<String, String> appOnlyCaller = Pair.create(app, EMPTY_SDK);
Pair<String, String> appSdkCaller = Pair.create(app, sdk);
Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
@@ -713,14 +909,12 @@ public class AppUpdateManagerTest {
final String sdk = ""; // App calls Topics API directly
final int numberOfLookBackEpochs = 3;
final long currentEpochId = 5L;
- final long taxonomyVersion = 1L;
- final long modelVersion = 1L;
- Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
+ Pair<String, String> appOnlyCaller = Pair.create(app, EMPTY_SDK);
- Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
- Topic topic2 = Topic.create(/* topic */ 2, taxonomyVersion, modelVersion);
- Topic topic3 = Topic.create(/* topic */ 3, taxonomyVersion, modelVersion);
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(/* topic */ 3, TAXONOMY_VERSION, MODEL_VERSION);
Topic[] topics = {topic1, topic2, topic3};
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
@@ -742,15 +936,13 @@ public class AppUpdateManagerTest {
public void testAssignTopicsToSdkForAppInstallation_unsatisfiedApp() {
final String app = "app";
final String sdk = "sdk";
- final long taxonomyVersion = 1L;
- final long modelVersion = 1L;
final int numberOfLookBackEpochs = 1;
Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
Pair<String, String> otherAppOnlyCaller = Pair.create("otherApp", /* sdk */ "");
Pair<String, String> appSdkCaller = Pair.create(app, sdk);
- Topic topic = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
+ Topic topic = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
@@ -790,14 +982,12 @@ public class AppUpdateManagerTest {
final String app = "app";
final String sdk = "sdk";
final String otherSDK = "otherSdk";
- final long taxonomyVersion = 1L;
- final long modelVersion = 1L;
final int numberOfLookBackEpochs = 1;
- Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
+ Pair<String, String> appOnlyCaller = Pair.create(app, EMPTY_SDK);
Pair<String, String> appSdkCaller = Pair.create(app, sdk);
- Topic topic = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
+ Topic topic = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
@@ -834,4 +1024,199 @@ public class AppUpdateManagerTest {
/* epochId */ 2L,
Map.of(appOnlyCaller, topic, appSdkCaller, topic)));
}
+
+ @Test
+ public void testSupportsTopicContributorFeature() {
+ // Both on
+ when(mDbHelper.supportsTopicContributorsTable()).thenReturn(true);
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+ assertThat(mAppUpdateManager.supportsTopicContributorFeature()).isTrue();
+
+ // On and Off
+ when(mDbHelper.supportsTopicContributorsTable()).thenReturn(true);
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(false);
+ assertThat(mAppUpdateManager.supportsTopicContributorFeature()).isFalse();
+
+ when(mDbHelper.supportsTopicContributorsTable()).thenReturn(false);
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+ assertThat(mAppUpdateManager.supportsTopicContributorFeature()).isFalse();
+
+ // Both off
+ when(mDbHelper.supportsTopicContributorsTable()).thenReturn(false);
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(false);
+ assertThat(mAppUpdateManager.supportsTopicContributorFeature()).isFalse();
+ }
+
+ @Test
+ public void testConvertUriToAppName() {
+ final String samplePackageName = "com.example.measurement.sampleapp";
+ final String packageScheme = "package:";
+
+ Uri uri = Uri.parse(packageScheme + samplePackageName);
+ assertThat(mAppUpdateManager.convertUriToAppName(uri)).isEqualTo(samplePackageName);
+ }
+
+ @Test
+ public void testHandleTopTopicsWithoutContributors() {
+ final long epochId1 = 1;
+ final long epochId2 = 2;
+ final String app1 = "app1";
+ final String app2 = "app2";
+ final String sdk = "sdk";
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(/* topic */ 3, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic4 = Topic.create(/* topic */ 4, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic5 = Topic.create(/* topic */ 5, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic6 = Topic.create(/* topic */ 6, TAXONOMY_VERSION, MODEL_VERSION);
+
+ // Both app1 and app2 have usage in the epoch and all 6 topics are top topics
+ // Both Topic1 and Topic2 have 2 contributors, app1, and app2. Topic3 has the only
+ // contributor app1.
+ // Therefore, Topic3 will be removed from ReturnedTopics if app1 is uninstalled.
+ mTopicsDao.persistTopTopics(
+ epochId1, List.of(topic1, topic2, topic3, topic4, topic5, topic6));
+ mTopicsDao.persistAppClassificationTopics(
+ epochId1,
+ Map.of(
+ app1, List.of(topic1, topic2, topic3),
+ app2, List.of(topic1, topic2)));
+ mTopicsDao.persistTopicContributors(
+ epochId1,
+ Map.of(
+ topic1.getTopic(), Set.of(app1, app2),
+ topic2.getTopic(), Set.of(app1, app2),
+ topic3.getTopic(), Set.of(app1)));
+ mTopicsDao.persistReturnedAppTopicsMap(
+ epochId1,
+ Map.of(
+ Pair.create(app1, EMPTY_SDK), topic3,
+ Pair.create(app1, sdk), topic3,
+ Pair.create(app2, EMPTY_SDK), topic2,
+ Pair.create(app2, sdk), topic1));
+
+ // Copy data of Epoch1 to Epoch2 to verify the removal is on epoch basis
+ mTopicsDao.persistTopTopics(epochId2, mTopicsDao.retrieveTopTopics(epochId1));
+ mTopicsDao.persistAppClassificationTopics(
+ epochId2, mTopicsDao.retrieveAppClassificationTopics(epochId1));
+ mTopicsDao.persistTopicContributors(
+ epochId2, mTopicsDao.retrieveTopicToContributorsMap(epochId1));
+ mTopicsDao.persistTopicContributors(
+ epochId2, mTopicsDao.retrieveTopicToContributorsMap(epochId1));
+ mTopicsDao.persistReturnedAppTopicsMap(
+ epochId2,
+ mTopicsDao
+ .retrieveReturnedTopics(epochId1, /* numberOfLookBackEpochs */ 1)
+ .get(epochId1));
+
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(1);
+ mAppUpdateManager.handleTopTopicsWithoutContributors(
+ /* only handle past epochs */ epochId2, app1);
+
+ // Only observe current epoch per the setup of this test
+ // Topic3 should be removed from returnedTopics
+ assertThat(
+ mTopicsDao
+ .retrieveReturnedTopics(epochId1, /* numberOfLookBackEpochs */ 1)
+ .get(epochId1))
+ .isEqualTo(
+ Map.of(
+ Pair.create(app2, EMPTY_SDK), topic2,
+ Pair.create(app2, sdk), topic1));
+ // Epoch2 has no changes.
+ assertThat(
+ mTopicsDao
+ .retrieveReturnedTopics(epochId2, /* numberOfLookBackEpochs */ 1)
+ .get(epochId2))
+ .isEqualTo(
+ Map.of(
+ Pair.create(app1, EMPTY_SDK), topic3,
+ Pair.create(app1, sdk), topic3,
+ Pair.create(app2, EMPTY_SDK), topic2,
+ Pair.create(app2, sdk), topic1));
+ }
+
+ @Test
+ public void testFilterRegularTopicsWithoutContributors() {
+ final long epochId = 1;
+ final String app = "app";
+
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(/* topic */ 3, TAXONOMY_VERSION, MODEL_VERSION);
+
+ List<Topic> regularTopics = List.of(topic1, topic2, topic3);
+ // topic1 has a contributor. topic2 has empty contributor set and topic3 is annotated with
+ // PADDED_TOP_TOPICS_STRING. (See EpochManager#PADDED_TOP_TOPICS_STRING for details)
+ mTopicsDao.persistTopicContributors(
+ epochId,
+ Map.of(
+ topic1.getTopic(),
+ Set.of(app),
+ topic2.getTopic(),
+ Set.of(),
+ topic3.getTopic(),
+ Set.of(PADDED_TOP_TOPICS_STRING)));
+
+ // topic2 is filtered out.
+ assertThat(mAppUpdateManager.filterRegularTopicsWithoutContributors(regularTopics, epochId))
+ .isEqualTo(List.of(topic1, topic3));
+ }
+
+ // The actual e2e logic is tested in TopicsWorkerTest "testHandleAppUninstallation" tests.
+ // Methods invoked are tested respectively.
+ @Test
+ public void testHandleAppUninstallationInRealTime_enableTopicContributors() {
+ final String app = "app";
+ final long epochId = 1L;
+
+ AppUpdateManager appUpdateManager =
+ spy(new AppUpdateManager(mDbHelper, mTopicsDao, new Random(), mMockFlags));
+
+ // Enable Topic Contributors feature
+ doReturn(true).when(mDbHelper).supportsTopicContributorsTable();
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+
+ appUpdateManager.handleAppUninstallationInRealTime(Uri.parse(app), epochId);
+
+ verify(appUpdateManager).convertUriToAppName(Uri.parse(app));
+ verify(appUpdateManager).handleTopTopicsWithoutContributors(epochId, app);
+ verify(appUpdateManager).deleteAppDataFromTableByApps(List.of(app));
+ }
+
+ // The actual e2e logic is tested in TopicsWorkerTest "testHandleAppUninstallation" tests.
+ // Methods invoked are tested respectively in this test class.
+ @Test
+ public void testHandleAppUninstallationInRealTime_disableTopicContributors() {
+ final String app = "app";
+ final long epochId = 1L;
+
+ AppUpdateManager appUpdateManager =
+ spy(new AppUpdateManager(mDbHelper, mTopicsDao, new Random(), mMockFlags));
+
+ // Disable Topic Contributors feature
+ doReturn(false).when(mDbHelper).supportsTopicContributorsTable();
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+
+ appUpdateManager.handleAppUninstallationInRealTime(Uri.parse(app), epochId);
+
+ verify(appUpdateManager).convertUriToAppName(Uri.parse(app));
+ verify(appUpdateManager, never()).handleTopTopicsWithoutContributors(epochId, app);
+ verify(appUpdateManager).deleteAppDataFromTableByApps(List.of(app));
+ }
+
+ // For test coverage only. The actual e2e logic is tested in TopicsWorkerTest. Methods invoked
+ // are tested respectively in this test class.
+ @Test
+ public void testHandleAppInstallationInRealTime() {
+ final String app = "app";
+ final long epochId = 1L;
+
+ AppUpdateManager appUpdateManager =
+ spy(new AppUpdateManager(mDbHelper, mTopicsDao, new Random(), mMockFlags));
+
+ appUpdateManager.handleAppInstallationInRealTime(Uri.parse(app), epochId);
+
+ verify(appUpdateManager).assignTopicsToNewlyInstalledApps(app, epochId);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/CacheManagerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/CacheManagerTest.java
index 0eda2af51..ef39584e4 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/CacheManagerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/CacheManagerTest.java
@@ -17,6 +17,8 @@ package com.android.adservices.service.topics;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,9 +35,13 @@ import com.android.adservices.data.topics.Topic;
import com.android.adservices.data.topics.TopicsDao;
import com.android.adservices.data.topics.TopicsTables;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.GetTopicsReportedStats;
+
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -62,6 +68,7 @@ public final class CacheManagerTest {
@Mock Flags mMockFlags;
@Mock EpochManager mMockEpochManager;
+ @Mock AdServicesLogger mLogger;
@Before
public void setup() {
@@ -76,34 +83,40 @@ public final class CacheManagerTest {
DbTestUtil.deleteTable(TopicsTables.UsageHistoryContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.AppUsageHistoryContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.BlockedTopicsContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.TopicContributorsContract.TABLE);
DbHelper dbHelper = DbTestUtil.getDbHelperForTest();
mTopicsDao = new TopicsDao(dbHelper);
-
- // Erase all existing data.
- DbTestUtil.deleteTable(TopicsTables.TaxonomyContract.TABLE);
- DbTestUtil.deleteTable(TopicsTables.AppClassificationTopicsContract.TABLE);
- DbTestUtil.deleteTable(TopicsTables.CallerCanLearnTopicsContract.TABLE);
- DbTestUtil.deleteTable(TopicsTables.TopTopicsContract.TABLE);
- DbTestUtil.deleteTable(TopicsTables.ReturnedTopicContract.TABLE);
- DbTestUtil.deleteTable(TopicsTables.UsageHistoryContract.TABLE);
- DbTestUtil.deleteTable(TopicsTables.AppUsageHistoryContract.TABLE);
}
@Test
public void testGetTopics_emptyCache() {
// The cache is empty when first created.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
List<Topic> topics = cacheManager.getTopics(/* numberOfLookBackEpochs = */ 3, "app", "sdk");
assertThat(topics).isEmpty();
+
+ // Verify GetTopicsReportedStats created for logging.
+ verify(mLogger)
+ .logGetTopicsReportedStats(
+ eq(
+ GetTopicsReportedStats.builder()
+ .setFilteredBlockedTopicCount(0)
+ .setDuplicateTopicCount(0)
+ .setTopicIdsCount(0)
+ .build()));
}
@Test
public void testGetTopics() {
// The cache is empty.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
+ ArgumentCaptor<GetTopicsReportedStats> argument =
+ ArgumentCaptor.forClass(GetTopicsReportedStats.class);
// Assume the current epochId is 4L, we will load cache for returned topics in the last 3
// epochs: epochId in {3, 2, 1}.
@@ -215,12 +228,27 @@ public final class CacheManagerTest {
assertThat(cacheManager.getTopics(/* numberOfLookBackEpochs = */ 3, "app5", "sdk1"))
.containsExactlyElementsIn(Arrays.asList(topic1, topic5));
+
+ // GetTopics is invoked 19 times.
+ verify(mLogger, times(19)).logGetTopicsReportedStats(argument.capture());
+ assertThat(argument.getAllValues()).hasSize(19);
+ // Verify log for the first call.
+ assertThat(argument.getAllValues().get(0))
+ .isEqualTo(
+ GetTopicsReportedStats.builder()
+ .setFilteredBlockedTopicCount(0)
+ .setDuplicateTopicCount(0)
+ .setTopicIdsCount(0)
+ .build());
}
@Test
public void testGetTopics_someTopicsBlocked() {
// The cache is empty.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
+ ArgumentCaptor<GetTopicsReportedStats> argument =
+ ArgumentCaptor.forClass(GetTopicsReportedStats.class);
// Assume the current epochId is 4L, we will load cache for returned topics in the last 3
// epochs: epochId in {3, 2, 1}.
@@ -339,6 +367,103 @@ public final class CacheManagerTest {
assertThat(cacheManager.getTopics(/* numberOfLookBackEpochs = */ 3, "app5", "sdk1"))
.containsExactlyElementsIn(Arrays.asList(topic1, topic5));
+
+ // GetTopics is invoked 19 times.
+ verify(mLogger, times(19)).logGetTopicsReportedStats(argument.capture());
+ assertThat(argument.getAllValues()).hasSize(19);
+ // Verify log for the first call.
+ assertThat(argument.getAllValues().get(0))
+ .isEqualTo(
+ GetTopicsReportedStats.builder()
+ .setFilteredBlockedTopicCount(0)
+ .setDuplicateTopicCount(0)
+ .setTopicIdsCount(0)
+ .build());
+ }
+
+ @Test
+ public void testGetTopics_verifyLogs() {
+ // The cache is empty.
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
+ ArgumentCaptor<GetTopicsReportedStats> argument =
+ ArgumentCaptor.forClass(GetTopicsReportedStats.class);
+
+ // Assume the current epochId is 4L, we will load cache for returned topics in the last 3
+ // epochs: epochId in {3, 2, 1}.
+ long currentEpochId = 4L;
+ when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
+ // Mock Flags to make it independent of configuration
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(3);
+
+ Topic topic1 = Topic.create(/* topic */ 1, /* taxonomyVersion */ 1L, /* modelVersion */ 1L);
+ Topic topic2 = Topic.create(/* topic */ 2, /* taxonomyVersion */ 1L, /* modelVersion */ 1L);
+ Topic topic3 = Topic.create(/* topic */ 3, /* taxonomyVersion */ 1L, /* modelVersion */ 1L);
+
+ // EpochId 1
+ Map<Pair<String, String>, Topic> returnedAppSdkTopicsMap1 = new HashMap<>();
+ returnedAppSdkTopicsMap1.put(Pair.create("app1", ""), topic1);
+ returnedAppSdkTopicsMap1.put(Pair.create("app1", "sdk1"), topic1);
+ returnedAppSdkTopicsMap1.put(Pair.create("app1", "sdk2"), topic1);
+
+ mTopicsDao.persistReturnedAppTopicsMap(/* epochId */ 1L, returnedAppSdkTopicsMap1);
+
+ // EpochId 2
+ Map<Pair<String, String>, Topic> returnedAppSdkTopicsMap2 = new HashMap<>();
+
+ returnedAppSdkTopicsMap2.put(Pair.create("app1", ""), topic2);
+ returnedAppSdkTopicsMap2.put(Pair.create("app1", "sdk1"), topic2);
+ returnedAppSdkTopicsMap2.put(Pair.create("app1", "sdk2"), topic2);
+
+ mTopicsDao.persistReturnedAppTopicsMap(/* epochId */ 2L, returnedAppSdkTopicsMap2);
+
+ // EpochId 3
+ Map<Pair<String, String>, Topic> returnedAppSdkTopicsMap3 = new HashMap<>();
+
+ returnedAppSdkTopicsMap3.put(Pair.create("app1", ""), topic3);
+ returnedAppSdkTopicsMap3.put(Pair.create("app1", "sdk1"), topic2);
+ returnedAppSdkTopicsMap3.put(Pair.create("app1", "sdk2"), topic1);
+
+ mTopicsDao.persistReturnedAppTopicsMap(/* epochId */ 3L, returnedAppSdkTopicsMap3);
+
+ // block topic 2.
+ mTopicsDao.recordBlockedTopic(topic2);
+
+ cacheManager.loadCache();
+
+ verify(mMockEpochManager).getCurrentEpochId();
+ verify(mMockFlags).getTopicsNumberOfLookBackEpochs();
+
+ // Now look at epochId in [1,..,3] by setting numberOfLookBackEpochs = 3.
+ // Should return topic1, topic2 and topic3, but topic2 is blocked - so only topic1 and
+ // topic3 are expected.
+ assertThat(cacheManager.getTopics(/* numberOfLookBackEpochs = */ 3, "app1", ""))
+ .containsExactlyElementsIn(Arrays.asList(topic1, topic3));
+ // Should return topic1 and topic2, but topic2 is blocked - so only topic1 is expected.
+ assertThat(cacheManager.getTopics(/* numberOfLookBackEpochs = */ 3, "app1", "sdk1"))
+ .containsExactlyElementsIn(Arrays.asList(topic1));
+ // Should return topic1 and topic2, but topic2 is blocked - so only topic1 is expected.
+ assertThat(cacheManager.getTopics(/* numberOfLookBackEpochs = */ 3, "app1", "sdk2"))
+ .containsExactlyElementsIn(Arrays.asList(topic1));
+
+ // GetTopics is invoked 3 times.
+ verify(mLogger, times(3)).logGetTopicsReportedStats(argument.capture());
+ assertThat(argument.getAllValues()).hasSize(3);
+ // Should return topic1, topic2 and topic3, but topic2 is blocked - so only topic1 and
+ // topic3 are expected.
+ assertThat(argument.getAllValues().get(0).getFilteredBlockedTopicCount()).isEqualTo(1);
+ assertThat(argument.getAllValues().get(0).getDuplicateTopicCount()).isEqualTo(0);
+ assertThat(argument.getAllValues().get(0).getTopicIdsCount()).isEqualTo(2);
+ // Should return topic1 and topic2, but topic2 is blocked 2 times - so only topic1 is
+ // expected.
+ assertThat(argument.getAllValues().get(1).getFilteredBlockedTopicCount()).isEqualTo(2);
+ assertThat(argument.getAllValues().get(1).getDuplicateTopicCount()).isEqualTo(0);
+ assertThat(argument.getAllValues().get(1).getTopicIdsCount()).isEqualTo(1);
+ // Should return topic1 and topic2, but topic2 is blocked - so only topic1 is expected.
+ // topic1 is deduplicated.
+ assertThat(argument.getAllValues().get(2).getFilteredBlockedTopicCount()).isEqualTo(1);
+ assertThat(argument.getAllValues().get(2).getDuplicateTopicCount()).isEqualTo(1);
+ assertThat(argument.getAllValues().get(2).getTopicIdsCount()).isEqualTo(1);
}
@Test
@@ -371,7 +496,8 @@ public final class CacheManagerTest {
});
// The cache is empty.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
// Assume the current epochId is 4L, we will load cache for returned topics in the last 3
// epochs: epochId in {3, 2, 1}.
@@ -423,7 +549,10 @@ public final class CacheManagerTest {
@Test
public void testGetTopics_duplicateTopics() {
// The cache is empty.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
+ ArgumentCaptor<GetTopicsReportedStats> argument =
+ ArgumentCaptor.forClass(GetTopicsReportedStats.class);
// Assume the current epochId is 4L, we will load cache for returned topics in the last 3
// epochs: epochId in {3, 2, 1}.
@@ -497,6 +626,18 @@ public final class CacheManagerTest {
.containsExactlyElementsIn(Arrays.asList(topic1, topic2));
assertThat(cacheManager.getTopics(/* numberOfLookBackEpochs = */ 3, "app1", "sdk2"))
.containsExactlyElementsIn(Arrays.asList(topic1, topic2, topic3));
+
+ // GetTopics is invoked 9 times.
+ verify(mLogger, times(9)).logGetTopicsReportedStats(argument.capture());
+ assertThat(argument.getAllValues()).hasSize(9);
+ // Verify log for the first call.
+ assertThat(argument.getAllValues().get(0))
+ .isEqualTo(
+ GetTopicsReportedStats.builder()
+ .setFilteredBlockedTopicCount(0)
+ .setDuplicateTopicCount(0)
+ .setTopicIdsCount(1)
+ .build());
}
// Currently SQLException is not thrown. This test needs to be uplifted after SQLException gets
@@ -505,19 +646,31 @@ public final class CacheManagerTest {
@Test
public void testGetTopics_failToLoadFromDb() {
// The cache is empty when first created.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
// Fail to load from DB will have empty cache.
List<Topic> topics = cacheManager.getTopics(/* numberOfLookBackEpochs = */ 3, "app", "sdk");
assertThat(topics).isEmpty();
+
+ // Verify GetTopicsReportedStats created for logging.
+ verify(mLogger)
+ .logGetTopicsReportedStats(
+ eq(
+ GetTopicsReportedStats.builder()
+ .setFilteredBlockedTopicCount(0)
+ .setDuplicateTopicCount(0)
+ .setTopicIdsCount(0)
+ .build()));
}
@Test
public void testDump() {
// Trigger the dump to verify no crash
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
PrintWriter printWriter = new PrintWriter(new Writer() {
@Override
@@ -542,7 +695,8 @@ public final class CacheManagerTest {
@Test
public void testGetKnownTopicsWithConsent_noBlockedTopics() {
// The cache is empty.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
// Assume the current epochId is 4L, we will load cache for returned topics in the last 3
// epochs: epochId in {3, 2, 1}.
@@ -586,9 +740,71 @@ public final class CacheManagerTest {
}
@Test
+ public void testGetTopicsInEpochRange() {
+ Topic topic1 =
+ Topic.create(/* topic */ 1, /* taxonomyVersion = */ 1L, /* modelVersion = */ 1L);
+ Topic topic2 =
+ Topic.create(/* topic */ 2, /* taxonomyVersion = */ 1L, /* modelVersion = */ 1L);
+ Topic topic3 =
+ Topic.create(/* topic */ 3, /* taxonomyVersion = */ 1L, /* modelVersion = */ 1L);
+ Topic topic4 =
+ Topic.create(/* topic */ 4, /* taxonomyVersion = */ 1L, /* modelVersion = */ 1L);
+ Topic topic5 =
+ Topic.create(/* topic */ 5, /* taxonomyVersion = */ 1L, /* modelVersion = */ 1L);
+
+ String app1 = "app1";
+ String app2 = "app2";
+ String sdk = "sdk";
+ Pair<String, String> app1Sdk = Pair.create(app1, sdk);
+ Pair<String, String> app2Sdk = Pair.create(app2, sdk);
+ long epoch1 = 1L;
+ long epoch2 = 2L;
+ long epoch3 = 3L;
+
+ long currentEpochId = 4L;
+ when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
+ // Mock Flags to make it independent of configuration
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(3);
+
+ // App1 has Topic1 in Epoch 1. Topic2 in Epoch 2, Topic3 in Epoch 3.
+ // App2 has Topic4 in Epoch 1, Topic5 in Epoch 2.
+ mTopicsDao.persistReturnedAppTopicsMap(epoch1, Map.of(app1Sdk, topic1));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch2, Map.of(app1Sdk, topic2));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch3, Map.of(app1Sdk, topic3));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch1, Map.of(app2Sdk, topic4));
+ mTopicsDao.persistReturnedAppTopicsMap(epoch2, Map.of(app2Sdk, topic5));
+
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
+ cacheManager.loadCache();
+
+ // App1 should have topic1/2/3 in epoch range [1, 3].
+ assertThat(
+ cacheManager.getTopicsInEpochRange(
+ /* epochLowerBound */ 1, /* epochUpperBound */ 3, app1, sdk))
+ .isEqualTo(List.of(topic1, topic2, topic3));
+
+ // App1 should have topic2/3 in epoch range [2, 3].
+ assertThat(
+ cacheManager.getTopicsInEpochRange(
+ /* epochLowerBound */ 2, /* epochUpperBound */ 3, app1, sdk))
+ .isEqualTo(List.of(topic2, topic3));
+
+ // App2 should have topic4/5 in epoch range [1, 3].
+ assertThat(
+ cacheManager.getTopicsInEpochRange(
+ /* epochLowerBound */ 1, /* epochUpperBound */ 3, app2, sdk))
+ .isEqualTo(List.of(topic4, topic5));
+
+ verify(mMockEpochManager).getCurrentEpochId();
+ verify(mMockFlags).getTopicsNumberOfLookBackEpochs();
+ }
+
+ @Test
public void testGetKnownTopicsWithConsent_blockSomeTopics() {
// The cache is empty.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
// Assume the current epochId is 4L, we will load cache for returned topics in the last 3
// epochs: epochId in {3, 2, 1}.
@@ -639,7 +855,8 @@ public final class CacheManagerTest {
@Test
public void testGetKnownTopicsWithConsent_blockAllTopics() {
// The cache is empty.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
// Assume the current epochId is 4L, we will load cache for returned topics in the last 3
// epochs: epochId in {3, 2, 1}.
@@ -692,7 +909,8 @@ public final class CacheManagerTest {
@Test
public void testClearAllTopicsData() {
// The cache is empty.
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
Topic topic1 =
Topic.create(/* topic */ 1, /* taxonomyVersion = */ 1L, /* modelVersion = */ 1L);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochJobServiceTest.java
index 115c2d42a..abf8ac518 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochJobServiceTest.java
@@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -45,6 +46,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
@@ -57,6 +59,8 @@ import java.util.concurrent.TimeUnit;
@SuppressWarnings("ConstantConditions")
public class EpochJobServiceTest {
private static final int BINDER_CONNECTION_TIMEOUT_MS = 5_000;
+ private static final long EPOCH_JOB_PERIOD_MS = 10_000L;
+ private static final long EPOCH_JOB_FLEX_MS = 1_000L;
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
private static final JobScheduler JOB_SCHEDULER = CONTEXT.getSystemService(JobScheduler.class);
private static final Flags TEST_FLAGS = FlagsFactory.getFlagsForTest();
@@ -72,6 +76,7 @@ public class EpochJobServiceTest {
@Mock AppUpdateManager mMockAppUpdateManager;
@Mock JobParameters mMockJobParameters;
@Mock Flags mMockFlags;
+ @Mock JobScheduler mMockJobScheduler;
@Before
public void setup() {
@@ -139,7 +144,7 @@ public class EpochJobServiceTest {
}
@Test
- public void testOnStartJob_killSwitchOn() throws InterruptedException {
+ public void testOnStartJob_killSwitchOn() {
// Killswitch is on.
doReturn(true).when(mMockFlags).getTopicsKillSwitch();
@@ -154,7 +159,8 @@ public class EpochJobServiceTest {
TOPICS_EPOCH_JOB_ID,
new ComponentName(CONTEXT, EpochJobService.class))
.setRequiresCharging(true)
- .setPeriodic(/* epochJobPeriodMs */ 10000, /* epochJobFlexMs */ 1000)
+ .setPeriodic(EPOCH_JOB_PERIOD_MS, EPOCH_JOB_FLEX_MS)
+ .setPersisted(true)
.build();
JOB_SCHEDULER.schedule(existingJobInfo);
assertNotNull(JOB_SCHEDULER.getPendingJob(TOPICS_EPOCH_JOB_ID));
@@ -268,4 +274,16 @@ public class EpochJobServiceTest {
assertThat(EpochJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isFalse();
assertThat(JOB_SCHEDULER.getPendingJob(TOPICS_EPOCH_JOB_ID)).isNull();
}
+
+ @Test
+ public void testSchedule_jobInfoIsPersisted() {
+ final ArgumentCaptor<JobInfo> argumentCaptor = ArgumentCaptor.forClass(JobInfo.class);
+
+ EpochJobService.schedule(
+ CONTEXT, mMockJobScheduler, EPOCH_JOB_PERIOD_MS, EPOCH_JOB_FLEX_MS);
+
+ verify(mMockJobScheduler, times(1)).schedule(argumentCaptor.capture());
+ assertThat(argumentCaptor.getValue()).isNotNull();
+ assertThat(argumentCaptor.getValue().isPersisted()).isTrue();
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochManagerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochManagerTest.java
index af44a9467..553173820 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochManagerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/EpochManagerTest.java
@@ -15,12 +15,16 @@
*/
package com.android.adservices.service.topics;
+import static com.android.adservices.service.topics.EpochManager.PADDED_TOP_TOPICS_STRING;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,7 +52,6 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
@@ -85,6 +88,7 @@ public final class EpochManagerTest {
@Mock Classifier mMockClassifier;
@Mock Clock mMockClock;
+ @Mock Flags mMockFlag;
@Before
public void setup() {
@@ -105,6 +109,7 @@ public final class EpochManagerTest {
DbTestUtil.deleteTable(TopicsTables.UsageHistoryContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.AppUsageHistoryContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.EpochOriginContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.TopicContributorsContract.TABLE);
}
@Test
@@ -380,6 +385,8 @@ public final class EpochManagerTest {
// Mock the flag to make test result deterministic
Flags mockedFlags = Mockito.mock(Flags.class);
when(mockedFlags.getNumberOfEpochsToKeepInHistory()).thenReturn(3);
+ when(mockedFlags.getEnableDatabaseSchemaVersion3()).thenReturn(true);
+ when(mockedFlags.getEnableTopicContributorsCheck()).thenReturn(false);
EpochManager epochManager =
new EpochManager(
@@ -433,6 +440,47 @@ public final class EpochManagerTest {
}
@Test
+ public void testGarbageCollectOutdatedEpochData_topContributorsTable() {
+ // Mock the flag to make test result deterministic
+ Flags mockedFlags = Mockito.mock(Flags.class);
+ when(mockedFlags.getNumberOfEpochsToKeepInHistory()).thenReturn(1);
+
+ EpochManager epochManager =
+ spy(
+ new EpochManager(
+ mTopicsDao,
+ mDbHelper,
+ new Random(),
+ mMockClassifier,
+ mockedFlags,
+ mMockClock));
+
+ final long epoch1 = 1L;
+ final long epoch2 = 2L;
+ final long currentEpochId = 3L;
+ final String app = "app";
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+
+ Map<Integer, Set<String>> topContributorsMap = Map.of(topic1.getTopic(), Set.of(app));
+ mTopicsDao.persistTopicContributors(epoch1, topContributorsMap);
+ mTopicsDao.persistTopicContributors(epoch2, topContributorsMap);
+
+ // Test feature flag is off
+ doReturn(false).when(epochManager).supportsTopicContributorFeature();
+ epochManager.garbageCollectOutdatedEpochData(currentEpochId);
+ // Nothing should be garbage collected.
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch1)).isEqualTo(topContributorsMap);
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch2)).isEqualTo(topContributorsMap);
+
+ // Test feature flag is on
+ doReturn(true).when(epochManager).supportsTopicContributorFeature();
+ epochManager.garbageCollectOutdatedEpochData(currentEpochId);
+ // Data of Epoch 1 should be garbage collected.
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch1)).isEmpty();
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epoch2)).isEqualTo(topContributorsMap);
+ }
+
+ @Test
public void testProcessEpoch() {
// Create a new EpochManager that we can control the random generator.
//
@@ -459,6 +507,8 @@ public final class EpochManagerTest {
// Mock EpochManager for getCurrentEpochId()
final long epochId = 1L;
doReturn(epochId).when(epochManager).getCurrentEpochId();
+ // Enable Topic Contributors feature
+ doReturn(true).when(epochManager).supportsTopicContributorFeature();
Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
@@ -559,6 +609,26 @@ public final class EpochManagerTest {
List<Topic> topTopicsFromDB = topicsDao.retrieveTopTopics(epochId);
assertThat(topTopicsFromDB).isEqualTo(topTopics);
+ // Verify TopicContributorsContract
+ // AppClassificationTopics has:
+ // app1 -> topic1, topic2, app2 -> topic2, topic3,
+ // app3 -> topic4, topic5, app4 -> topic5, topic6
+ // All app1 ~ app4 have usages and all topic1 ~ topic6 are top topics
+ // So the reverse mapping of AppClassificationTopics, which is topTopicsToContributorsMap,
+ // should be:
+ // topic1 -> app1, topic2 -> app1, app2, topic3 -> app2
+ // topic4 -> app3, topic5 -> app3, app4, topic6 -> app4
+ Map<Integer, Set<String>> expectedTopTopicsToContributorsMap =
+ Map.of(
+ topic1.getTopic(), Set.of("app1"),
+ topic2.getTopic(), Set.of("app1", "app2"),
+ topic3.getTopic(), Set.of("app2"),
+ topic4.getTopic(), Set.of("app3"),
+ topic5.getTopic(), Set.of("app3", "app4"),
+ topic6.getTopic(), Set.of("app4"));
+ assertThat(topicsDao.retrieveTopicToContributorsMap(epochId))
+ .isEqualTo(expectedTopTopicsToContributorsMap);
+
// Verify ReturnedTopicContract
// Random sequence numbers used in this test: {1, 5, 6, 7, 8, 9}.
// The order of selected topics by iterations: "random_topic", "topic1", "topic2", "topic3",
@@ -595,7 +665,75 @@ public final class EpochManagerTest {
}
@Test
- public void testDump() throws FileNotFoundException {
+ public void testProcessEpoch_disableTopicContributorsCheck() {
+ // Simplify the setup of epoch computation, to only test the effect of feature flag
+ // Mock the flag to make test result deterministic
+ Flags mockedFlags = Mockito.mock(Flags.class);
+ EpochManager epochManager =
+ Mockito.spy(
+ new EpochManager(
+ mTopicsDao,
+ mDbHelper,
+ new Random(),
+ mMockClassifier,
+ mockedFlags,
+ mMockClock));
+
+ final String app = "app";
+ final String sdk = "sdk";
+ final long epochId = 1L;
+
+ Topic topic1 = Topic.create(/* topic */ 1, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic2 = Topic.create(/* topic */ 2, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic3 = Topic.create(/* topic */ 3, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic4 = Topic.create(/* topic */ 4, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic5 = Topic.create(/* topic */ 5, TAXONOMY_VERSION, MODEL_VERSION);
+ Topic topic6 = Topic.create(/* topic */ 6, TAXONOMY_VERSION, MODEL_VERSION);
+ Map<String, List<Topic>> appClassificationTopicsMap = Map.of(app, List.of(topic1));
+ List<Topic> topTopics = List.of(topic1, topic2, topic3, topic4, topic5, topic6);
+
+ when(mockedFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(1);
+ when(mockedFlags.getTopicsNumberOfTopTopics())
+ .thenReturn(mFlags.getTopicsNumberOfTopTopics());
+ when(mockedFlags.getTopicsNumberOfRandomTopics())
+ .thenReturn(mFlags.getTopicsNumberOfRandomTopics());
+ doReturn(epochId).when(epochManager).getCurrentEpochId();
+
+ mTopicsDao.recordUsageHistory(epochId, app, sdk);
+ when(mMockClassifier.classify(any())).thenReturn(appClassificationTopicsMap);
+ when(mMockClassifier.getTopTopics(
+ appClassificationTopicsMap,
+ mFlags.getTopicsNumberOfTopTopics(),
+ mFlags.getTopicsNumberOfRandomTopics()))
+ .thenReturn(topTopics);
+
+ // Verify the feature flag is off
+ doReturn(false).when(epochManager).supportsTopicContributorFeature();
+ epochManager.processEpoch();
+
+ // TopContributors table should be empty when feature is not enabled
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId)).isEmpty();
+
+ // Verify the feature flag is on.
+ doReturn(true).when(epochManager).supportsTopicContributorFeature();
+ epochManager.processEpoch();
+
+ // TopContributors table should be non-empty when feature is not enabled
+ // topic1 is normal top topic with contributor "app"
+ // topic2 ~ topic5 are padded topics. They are annotated with PADDED_TOP_TOPICS_STRING.
+ // topic6 is a random topic which should not be handled.
+ Map<Integer, Set<String>> expectedTopicContributorsMap = new HashMap<>();
+ expectedTopicContributorsMap.put(topic1.getTopic(), Set.of(app));
+ expectedTopicContributorsMap.put(topic2.getTopic(), Set.of(PADDED_TOP_TOPICS_STRING));
+ expectedTopicContributorsMap.put(topic3.getTopic(), Set.of(PADDED_TOP_TOPICS_STRING));
+ expectedTopicContributorsMap.put(topic4.getTopic(), Set.of(PADDED_TOP_TOPICS_STRING));
+ expectedTopicContributorsMap.put(topic5.getTopic(), Set.of(PADDED_TOP_TOPICS_STRING));
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId))
+ .isEqualTo(expectedTopicContributorsMap);
+ }
+
+ @Test
+ public void testDump() {
// Trigger the dump to verify no crash
PrintWriter printWriter = new PrintWriter(new Writer() {
@Override
@@ -852,6 +990,144 @@ public final class EpochManagerTest {
verify(mMockClock, times(3)).currentTimeMillis();
}
+ @Test
+ public void testComputeTopTopicsToContributorsMap() {
+ EpochManager epochManager = createEpochManagerWithMockedFlag();
+
+ // Topic1 and Topic2 are top topics. Topic3 is not a top topic.
+ final Topic topic1 = createTopic(1);
+ final Topic topic2 = createTopic(2);
+ final Topic topic3 = createTopic(3);
+
+ final String app1 = "app1";
+ final String app2 = "app2";
+ final String app3 = "app3"; // an app without classified topics
+
+ Map<String, List<Topic>> appClassificationTopicsMap =
+ Map.of(
+ app1, List.of(topic1, topic3),
+ app2, List.of(topic1, topic2, topic3),
+ app3, List.of());
+ List<Topic> topTopics = List.of(topic1, topic2);
+
+ // Only topic1 and topic2 will be computed as they are top topics.
+ Map<Integer, Set<String>> expectedTopTopicsToContributorsMap =
+ Map.of(
+ topic1.getTopic(), Set.of(app1, app2),
+ topic2.getTopic(), Set.of(app2));
+
+ // Ignore the effect of padded topics
+ when(mMockFlag.getTopicsNumberOfTopTopics()).thenReturn(topTopics.size());
+
+ assertThat(
+ epochManager.computeTopTopicsToContributorsMap(
+ appClassificationTopicsMap, topTopics))
+ .isEqualTo(expectedTopTopicsToContributorsMap);
+ }
+
+ @Test
+ public void testComputeTopTopicsToContributorsMap_emptyTopTopics() {
+ EpochManager epochManager = createEpochManagerWithMockedFlag();
+ // Topic1 and Topic2 are top topics. Topic3 is not a top topic.
+ final Topic topic1 = createTopic(1);
+ final Topic topic2 = createTopic(2);
+ final Topic topic3 = createTopic(3);
+
+ final String app1 = "app1";
+ final String app2 = "app2";
+ final String app3 = "app3";
+
+ Map<String, List<Topic>> appClassificationTopicsMap =
+ Map.of(
+ app1, List.of(topic1, topic3),
+ app2, List.of(topic1, topic2, topic3),
+ app3, List.of());
+ List<Topic> topTopics = List.of();
+
+ // Ignore the effect of padded topics
+ when(mMockFlag.getTopicsNumberOfTopTopics()).thenReturn(topTopics.size());
+
+ assertThat(
+ epochManager.computeTopTopicsToContributorsMap(
+ appClassificationTopicsMap, topTopics))
+ .isEmpty();
+ }
+
+ @Test
+ public void testComputeTopTopicsToContributorsMap_paddedTopics() {
+ EpochManager epochManager = createEpochManagerWithMockedFlag();
+
+ // Topic1 and Topic2 are top topics. Topic3 is not a top topic.
+ final Topic topic1 = createTopic(1);
+ final Topic topic2 = createTopic(2);
+ final Topic topic3 = createTopic(3);
+ final Topic topic4 = createTopic(4);
+ final Topic topic5 = createTopic(5);
+ final Topic topic6 = createTopic(6);
+
+ final String app1 = "app1";
+ final String app2 = "app2";
+
+ Map<String, List<Topic>> appClassificationTopicsMap =
+ Map.of(
+ app1, List.of(topic1, topic3),
+ app2, List.of(topic1, topic2, topic3));
+
+ // app4 and app5 are padded topics without any contributors.
+ List<Topic> topTopics = List.of(topic1, topic2, topic3, topic4, topic5, topic6);
+
+ when(mMockFlag.getTopicsNumberOfTopTopics())
+ .thenReturn(FlagsFactory.getFlagsForTest().getTopicsNumberOfTopTopics());
+
+ // topic1, topic2, topic3 will be computed as they are normal top topics.
+ // topic4 and topic5 will be annotated as padded topics.
+ // topic6 won't be included as it's a random topic.
+ Map<Integer, Set<String>> expectedTopTopicsToContributorsMap =
+ Map.of(
+ topic1.getTopic(), Set.of(app1, app2),
+ topic2.getTopic(), Set.of(app2),
+ topic3.getTopic(), Set.of(app1, app2),
+ topic4.getTopic(), Set.of(PADDED_TOP_TOPICS_STRING),
+ topic5.getTopic(), Set.of(PADDED_TOP_TOPICS_STRING));
+
+ assertThat(
+ epochManager.computeTopTopicsToContributorsMap(
+ appClassificationTopicsMap, topTopics))
+ .isEqualTo(expectedTopTopicsToContributorsMap);
+ }
+
+ @Test
+ public void testSupportsTopicContributorFeature() {
+ DbHelper dbHelper = spy(DbTestUtil.getDbHelperForTest());
+ EpochManager epochManager =
+ new EpochManager(
+ new TopicsDao(dbHelper),
+ dbHelper,
+ new Random(),
+ mMockClassifier,
+ mMockFlag,
+ mMockClock);
+
+ // Both on
+ when(dbHelper.supportsTopicContributorsTable()).thenReturn(true);
+ when(mMockFlag.getEnableTopicContributorsCheck()).thenReturn(true);
+ assertThat(epochManager.supportsTopicContributorFeature()).isTrue();
+
+ // On and Off
+ when(dbHelper.supportsTopicContributorsTable()).thenReturn(true);
+ when(mMockFlag.getEnableTopicContributorsCheck()).thenReturn(false);
+ assertThat(epochManager.supportsTopicContributorFeature()).isFalse();
+
+ when(dbHelper.supportsTopicContributorsTable()).thenReturn(false);
+ when(mMockFlag.getEnableTopicContributorsCheck()).thenReturn(true);
+ assertThat(epochManager.supportsTopicContributorFeature()).isFalse();
+
+ // Both off
+ when(dbHelper.supportsTopicContributorsTable()).thenReturn(false);
+ when(mMockFlag.getEnableTopicContributorsCheck()).thenReturn(false);
+ assertThat(epochManager.supportsTopicContributorFeature()).isFalse();
+ }
+
private Topic createTopic(int topicId) {
return Topic.create(topicId, TAXONOMY_VERSION, MODEL_VERSION);
}
@@ -859,4 +1135,9 @@ public final class EpochManagerTest {
private List<Topic> createTopics(List<Integer> topicIds) {
return topicIds.stream().map(this::createTopic).collect(Collectors.toList());
}
+
+ private EpochManager createEpochManagerWithMockedFlag() {
+ return new EpochManager(
+ mTopicsDao, mDbHelper, new Random(), mMockClassifier, mMockFlag, mMockClock);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsServiceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsServiceImplTest.java
index 46a39e251..d6790172e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsServiceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsServiceImplTest.java
@@ -26,10 +26,10 @@ import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZE
import static android.adservices.common.AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS_PREVIEW_API;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -103,6 +103,7 @@ public class TopicsServiceImplTest {
private static final String INVALID_PACKAGE_NAME = "com.do_not_exists";
private static final String SOME_SDK_NAME = "SomeSdkName";
private static final int BINDER_CONNECTION_TIMEOUT_MS = 5_000;
+ private static final int RECORD_USAGE_CALLED_LATCH_TIMEOUT_MS = 1_000;
private static final String SDK_PACKAGE_NAME = "test_package_name";
private static final String ALLOWED_SDK_ID = "1234567";
// This is not allowed per the ad_services_config.xml manifest config.
@@ -119,6 +120,7 @@ public class TopicsServiceImplTest {
private CountDownLatch mGetTopicsCallbackLatch;
private CallerMetadata mCallerMetadata;
private TopicsWorker mTopicsWorker;
+ private TopicsWorker mSpyTopicsWorker;
private BlockedTopicsManager mBlockedTopicsManager;
private TopicsDao mTopicsDao;
private GetTopicsParam mRequest;
@@ -135,6 +137,7 @@ public class TopicsServiceImplTest {
@Mock private Throttler mMockThrottler;
@Mock private EnrollmentDao mEnrollmentDao;
@Mock private AppImportanceFilter mMockAppImportanceFilter;
+ @Mock AdServicesLogger mLogger;
@Before
public void setup() throws Exception {
@@ -145,11 +148,12 @@ public class TopicsServiceImplTest {
DbHelper dbHelper = DbTestUtil.getDbHelperForTest();
mTopicsDao = new TopicsDao(dbHelper);
- CacheManager cacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ CacheManager cacheManager =
+ new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
mBlockedTopicsManager = new BlockedTopicsManager(mTopicsDao);
AppUpdateManager appUpdateManager =
- new AppUpdateManager(mTopicsDao, new Random(), mMockFlags);
+ new AppUpdateManager(dbHelper, mTopicsDao, new Random(), mMockFlags);
mTopicsWorker =
new TopicsWorker(
mMockEpochManager,
@@ -157,6 +161,16 @@ public class TopicsServiceImplTest {
mBlockedTopicsManager,
appUpdateManager,
mMockFlags);
+ // Used for verifying recordUsage method invocations.
+ mSpyTopicsWorker =
+ Mockito.spy(
+ new TopicsWorker(
+ mMockEpochManager,
+ cacheManager,
+ mBlockedTopicsManager,
+ appUpdateManager,
+ mMockFlags));
+
when(mClock.elapsedRealtime()).thenReturn(150L, 200L);
mCallerMetadata = new CallerMetadata.Builder().setBinderElapsedTimestamp(100L).build();
mRequest =
@@ -167,13 +181,13 @@ public class TopicsServiceImplTest {
.build();
DbTestUtil.deleteTable(TopicsTables.BlockedTopicsContract.TABLE);
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.GIVEN);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
when(mMockSdkContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0)).thenReturn(Process.myUid());
// Allow all for signature allow list check
when(mMockFlags.getPpapiAppSignatureAllowList()).thenReturn(AllowLists.ALLOW_ALL);
+ when(mMockFlags.getTopicsEpochJobPeriodMs()).thenReturn(Flags.TOPICS_EPOCH_JOB_PERIOD_MS);
// Initialize enrollment data.
EnrollmentData fakeEnrollmentData =
@@ -188,6 +202,8 @@ public class TopicsServiceImplTest {
eq(Throttler.ApiKey.TOPICS_API_APP_PACKAGE_NAME), anyString()))
.thenReturn(true);
+ when(mMockFlags.isEnrollmentBlocklisted(Mockito.any())).thenReturn(false);
+
// Initialize mock static.
mStaticMockitoSession =
ExtendedMockito.mockitoSession()
@@ -204,8 +220,7 @@ public class TopicsServiceImplTest {
@Test
public void checkNoUserConsent() throws InterruptedException {
when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.REVOKED);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.REVOKED);
invokeGetTopicsAndVerifyError(mContext, STATUS_USER_CONSENT_REVOKED);
}
@@ -369,6 +384,19 @@ public class TopicsServiceImplTest {
}
@Test
+ public void checkSdkEnrollmentInBlocklist_blocked() throws Exception {
+ when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
+ EnrollmentData fakeEnrollmentData =
+ new EnrollmentData.Builder().setEnrollmentId(ALLOWED_SDK_ID).build();
+ when(mEnrollmentDao.getEnrollmentDataFromSdkName(SOME_SDK_NAME))
+ .thenReturn(fakeEnrollmentData);
+
+ when(mMockFlags.isEnrollmentBlocklisted(ALLOWED_SDK_ID)).thenReturn(true);
+
+ invokeGetTopicsAndVerifyError(mMockSdkContext, STATUS_CALLER_NOT_ALLOWED);
+ }
+
+ @Test
public void checkSdkEnrollmentIdIsDisallowed() throws Exception {
when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
EnrollmentData fakeEnrollmentData =
@@ -424,7 +452,7 @@ public class TopicsServiceImplTest {
runGetTopics(createTestTopicsServiceImplInstance());
}
- // @Test
+ @Test
public void getTopicsSdk() throws Exception {
Mockito.lenient().when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
PackageManager.Property property =
@@ -452,8 +480,7 @@ public class TopicsServiceImplTest {
// block topic1
mBlockedTopicsManager.blockTopic(topics.get(0));
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.GIVEN);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
@@ -506,8 +533,7 @@ public class TopicsServiceImplTest {
mBlockedTopicsManager.blockTopic(topic);
}
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.GIVEN);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
@@ -550,8 +576,7 @@ public class TopicsServiceImplTest {
final long currentEpochId = 4L;
final int numberOfLookBackEpochs = 3;
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.GIVEN);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
@@ -589,9 +614,9 @@ public class TopicsServiceImplTest {
.containsExactlyElementsIn(expectedGetTopicsResult.getTopics());
// Invocations Summary
- // loadCache() : 1, getTopics(): 1 * 2
- verify(mMockEpochManager, Mockito.times(3)).getCurrentEpochId();
- verify(mMockFlags, Mockito.times(3)).getTopicsNumberOfLookBackEpochs();
+ // loadCache() : 1, getTopics(): 1 * 3
+ verify(mMockEpochManager, Mockito.times(4)).getCurrentEpochId();
+ verify(mMockFlags, Mockito.times(4)).getTopicsNumberOfLookBackEpochs();
}
@Test
@@ -600,8 +625,7 @@ public class TopicsServiceImplTest {
final long currentEpochId = 4L;
final int numberOfLookBackEpochs = 3;
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.GIVEN);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
@@ -626,16 +650,18 @@ public class TopicsServiceImplTest {
// Topic impl service use a background executor to run the task,
// use a countdownLatch and set the countdown in the logging call operation
final CountDownLatch logOperationCalledLatch = new CountDownLatch(1);
- Mockito.doAnswer(new Answer<Object>() {
- @Override
- public Object answer(InvocationOnMock invocation) throws Throwable {
- // The method logAPiCallStats is called.
- invocation.callRealMethod();
- logOperationCalledLatch.countDown();
- return null;
- }
- }).when(mAdServicesLogger).logApiCallStats(
- ArgumentMatchers.any(ApiCallStats.class));
+ Mockito.doAnswer(
+ new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ // The method logAPiCallStats is called.
+ invocation.callRealMethod();
+ logOperationCalledLatch.countDown();
+ return null;
+ }
+ })
+ .when(mAdServicesLogger)
+ .logApiCallStats(ArgumentMatchers.any(ApiCallStats.class));
// Setting up the timestamp for latency calculation, we passing in a client side call
// timestamp as a parameter to the call (100 in the below code), in topic service, it
@@ -700,9 +726,9 @@ public class TopicsServiceImplTest {
assertThat(argument.getValue().getLatencyMillisecond()).isEqualTo(150);
// Invocations Summary
- // loadCache() : 1, getTopics(): 1 * 2
- verify(mMockEpochManager, Mockito.times(3)).getCurrentEpochId();
- verify(mMockFlags, Mockito.times(3)).getTopicsNumberOfLookBackEpochs();
+ // loadCache() : 1, getTopics(): 1 * 3
+ verify(mMockEpochManager, Mockito.times(4)).getCurrentEpochId();
+ verify(mMockFlags, Mockito.times(4)).getTopicsNumberOfLookBackEpochs();
}
@Test
@@ -711,8 +737,7 @@ public class TopicsServiceImplTest {
final long currentEpochId = 4L;
final int numberOfLookBackEpochs = 3;
- when(mConsentManager.getConsent(any(PackageManager.class)))
- .thenReturn(AdServicesApiConsent.GIVEN);
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
@@ -756,6 +781,166 @@ public class TopicsServiceImplTest {
.isTrue();
}
+ @Test
+ public void testGetTopics_recordObservation() throws InterruptedException {
+ when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
+ // To capture result in inner class, we have to declare final.
+ final GetTopicsResult[] capturedResponseParcel = new GetTopicsResult[1];
+
+ final long currentEpochId = 4L;
+ final int numberOfLookBackEpochs = 3;
+
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
+ when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
+
+ TopicsServiceImpl topicsService = createTestTopicsServiceImplInstance_spyTopicsWorker();
+
+ // Not setting isRecordObservation explicitly will make isRecordObservation to have
+ // default value which is true.
+ mRequest =
+ new GetTopicsParam.Builder()
+ .setAppPackageName(TEST_APP_PACKAGE_NAME)
+ .setSdkName(SOME_SDK_NAME)
+ .setSdkPackageName(SOME_SDK_NAME)
+ .build();
+
+ // Topic impl service use a background executor to run the task,
+ // use a countdownLatch and set the countdown in the logging call operation
+ final CountDownLatch logOperationCalledLatch = new CountDownLatch(1);
+ Mockito.doAnswer(
+ new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ // The method logAPiCallStats is called.
+ invocation.callRealMethod();
+ logOperationCalledLatch.countDown();
+ return null;
+ }
+ })
+ .when(mAdServicesLogger)
+ .logApiCallStats(ArgumentMatchers.any(ApiCallStats.class));
+
+ ArgumentCaptor<ApiCallStats> argument = ArgumentCaptor.forClass(ApiCallStats.class);
+
+ topicsService.getTopics(
+ mRequest,
+ mCallerMetadata,
+ new IGetTopicsCallback() {
+ @Override
+ public void onResult(GetTopicsResult responseParcel) {
+ capturedResponseParcel[0] = responseParcel;
+ }
+
+ @Override
+ public void onFailure(int resultCode) {
+ Assert.fail();
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ });
+
+ // getTopics method finished executing.
+ assertThat(
+ logOperationCalledLatch.await(
+ BINDER_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+ .isTrue();
+
+ // Record the call from App and Sdk to usage history only when isRecordObservation is true.
+ verify(mSpyTopicsWorker, Mockito.times(1))
+ .recordUsage(TEST_APP_PACKAGE_NAME, SOME_SDK_NAME);
+
+ verify(mAdServicesLogger).logApiCallStats(argument.capture());
+ assertThat(argument.getValue().getResultCode()).isEqualTo(STATUS_SUCCESS);
+ assertThat(argument.getValue().getAppPackageName()).isEqualTo(TEST_APP_PACKAGE_NAME);
+ assertThat(argument.getValue().getSdkPackageName()).isEqualTo(SOME_SDK_NAME);
+ // Verify AdServicesLogger logs getTopics API.
+ assertThat(argument.getValue().getApiName())
+ .isEqualTo(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS);
+ }
+
+ @Test
+ public void testGetTopics_notRecordObservation() throws InterruptedException {
+ when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
+ // To capture result in inner class, we have to declare final.
+ final GetTopicsResult[] capturedResponseParcel = new GetTopicsResult[1];
+
+ final long currentEpochId = 4L;
+ final int numberOfLookBackEpochs = 3;
+
+ when(mConsentManager.getConsent()).thenReturn(AdServicesApiConsent.GIVEN);
+ when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
+
+ TopicsServiceImpl topicsService = createTestTopicsServiceImplInstance_spyTopicsWorker();
+
+ // Topic impl service use a background executor to run the task,
+ // use a countdownLatch and set the countdown in the logging call operation
+ final CountDownLatch logOperationCalledLatch = new CountDownLatch(1);
+ Mockito.doAnswer(
+ new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ // The method logAPiCallStats is called.
+ invocation.callRealMethod();
+ logOperationCalledLatch.countDown();
+ return null;
+ }
+ })
+ .when(mAdServicesLogger)
+ .logApiCallStats(ArgumentMatchers.any(ApiCallStats.class));
+
+ ArgumentCaptor<ApiCallStats> argument = ArgumentCaptor.forClass(ApiCallStats.class);
+
+ mRequest =
+ new GetTopicsParam.Builder()
+ .setAppPackageName(TEST_APP_PACKAGE_NAME)
+ .setSdkName(SOME_SDK_NAME)
+ .setSdkPackageName(SOME_SDK_NAME)
+ .setShouldRecordObservation(false)
+ .build();
+
+ topicsService.getTopics(
+ mRequest,
+ mCallerMetadata,
+ new IGetTopicsCallback() {
+ @Override
+ public void onResult(GetTopicsResult responseParcel) {
+ capturedResponseParcel[0] = responseParcel;
+ }
+
+ @Override
+ public void onFailure(int resultCode) {
+ Assert.fail();
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ });
+
+ // getTopics method finished executing.
+ assertThat(
+ logOperationCalledLatch.await(
+ RECORD_USAGE_CALLED_LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+ .isTrue();
+
+ // Not record the call from App and Sdk to usage history when isRecordObservation is false.
+ verify(mSpyTopicsWorker, never()).recordUsage(anyString(), anyString());
+
+ verify(mAdServicesLogger).logApiCallStats(argument.capture());
+ assertThat(argument.getValue().getResultCode()).isEqualTo(STATUS_SUCCESS);
+ assertThat(argument.getValue().getAppPackageName()).isEqualTo(TEST_APP_PACKAGE_NAME);
+ assertThat(argument.getValue().getSdkPackageName()).isEqualTo(SOME_SDK_NAME);
+ // Verify AdServicesLogger logs Preview API.
+ assertThat(argument.getValue().getApiName())
+ .isEqualTo(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS_PREVIEW_API);
+ }
+
private void invokeGetTopicsAndVerifyError(Context context, int expectedResultCode)
throws InterruptedException {
invokeGetTopicsAndVerifyError(context, expectedResultCode, mRequest);
@@ -917,4 +1102,18 @@ public class TopicsServiceImplTest {
mEnrollmentDao,
mMockAppImportanceFilter);
}
+
+ @NonNull
+ private TopicsServiceImpl createTestTopicsServiceImplInstance_spyTopicsWorker() {
+ return new TopicsServiceImpl(
+ mContext,
+ mSpyTopicsWorker,
+ mConsentManager,
+ mAdServicesLogger,
+ mClock,
+ mMockFlags,
+ mMockThrottler,
+ mEnrollmentDao,
+ mMockAppImportanceFilter);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsWorkerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsWorkerTest.java
index 71e558dd3..c5ddc5b4b 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsWorkerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/TopicsWorkerTest.java
@@ -19,7 +19,12 @@ import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -42,6 +47,7 @@ import com.android.adservices.data.topics.Topic;
import com.android.adservices.data.topics.TopicsDao;
import com.android.adservices.data.topics.TopicsTables;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.stats.AdServicesLogger;
import com.google.common.collect.ImmutableList;
@@ -51,6 +57,7 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -63,35 +70,44 @@ import java.util.Set;
public class TopicsWorkerTest {
// Spy the Context to test app reconciliation
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
+ private final DbHelper mDbHelper = spy(DbTestUtil.getDbHelperForTest());
private TopicsWorker mTopicsWorker;
private TopicsDao mTopicsDao;
- private AppUpdateManager mAppUpdateManager;
private CacheManager mCacheManager;
private BlockedTopicsManager mBlockedTopicsManager;
+ // Spy DbHelper to mock supportsTopContributorsTable feature.
@Mock private EpochManager mMockEpochManager;
@Mock private Flags mMockFlags;
+ @Mock AdServicesLogger mLogger;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
// Clean DB before each test
+ DbTestUtil.deleteTable(TopicsTables.TaxonomyContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.AppClassificationTopicsContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.CallerCanLearnTopicsContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.TopTopicsContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.ReturnedTopicContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.UsageHistoryContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.AppUsageHistoryContract.TABLE);
DbTestUtil.deleteTable(TopicsTables.BlockedTopicsContract.TABLE);
+ DbTestUtil.deleteTable(TopicsTables.TopicContributorsContract.TABLE);
- DbHelper dbHelper = DbTestUtil.getDbHelperForTest();
- mTopicsDao = new TopicsDao(dbHelper);
- mAppUpdateManager = new AppUpdateManager(mTopicsDao, new Random(), mMockFlags);
- mCacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags);
+ mTopicsDao = new TopicsDao(mDbHelper);
+ mCacheManager = new CacheManager(mMockEpochManager, mTopicsDao, mMockFlags, mLogger);
+ AppUpdateManager appUpdateManager =
+ new AppUpdateManager(mDbHelper, mTopicsDao, new Random(), mMockFlags);
mBlockedTopicsManager = new BlockedTopicsManager(mTopicsDao);
mTopicsWorker =
new TopicsWorker(
mMockEpochManager,
mCacheManager,
mBlockedTopicsManager,
- mAppUpdateManager,
+ appUpdateManager,
mMockFlags);
}
@@ -174,10 +190,6 @@ public class TopicsWorkerTest {
.containsExactlyElementsIn(expectedGetTopicsResult.getModelVersions());
assertThat(getTopicsResult.getTopics())
.containsExactlyElementsIn(expectedGetTopicsResult.getTopics());
-
- // getTopic() + loadCache() + handleSdkTopicsAssignmentForAppInstallation()
- verify(mMockEpochManager, times(3)).getCurrentEpochId();
- verify(mMockFlags, times(3)).getTopicsNumberOfLookBackEpochs();
}
@Test
@@ -213,10 +225,6 @@ public class TopicsWorkerTest {
.build();
assertThat(getTopicsResult).isEqualTo(expectedGetTopicsResult);
-
- // getTopic() + loadCache() + handleSdkTopicsAssignmentForAppInstallation()
- verify(mMockEpochManager, times(3)).getCurrentEpochId();
- verify(mMockFlags, times(3)).getTopicsNumberOfLookBackEpochs();
}
@Test
@@ -252,10 +260,6 @@ public class TopicsWorkerTest {
.build();
assertThat(getTopicsResult).isEqualTo(expectedGetTopicsResult);
-
- // getTopic() + loadCache() + handleSdkTopicsAssignmentForAppInstallation()
- verify(mMockEpochManager, times(3)).getCurrentEpochId();
- verify(mMockFlags, times(3)).getTopicsNumberOfLookBackEpochs();
}
@Test
@@ -308,11 +312,65 @@ public class TopicsWorkerTest {
.containsExactlyElementsIn(expectedGetTopicsResult.getModelVersions());
assertThat(getTopicsResult.getTopics())
.containsExactlyElementsIn(expectedGetTopicsResult.getTopics());
+ }
- // Invocation Summary
- // getTopic(): 1, handleSdkTopicsAssignmentForAppInstallation(): 1 * 2
- verify(mMockEpochManager, times(3)).getCurrentEpochId();
- verify(mMockFlags, times(3)).getTopicsNumberOfLookBackEpochs();
+ @Test
+ public void testGetTopics_handleSdkTopicAssignment_existingTopicsForSdk() {
+ final int numberOfLookBackEpochs = 3;
+ final long currentEpochId = 5L;
+
+ final String app = "app";
+ final String sdk = "sdk";
+
+ Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
+ Pair<String, String> appSdkCaller = Pair.create(app, sdk);
+
+ Topic topic1 =
+ Topic.create(/* topic */ 1, /* taxonomyVersion = */ 1L, /* modelVersion = */ 4L);
+ Topic topic2 =
+ Topic.create(/* topic */ 2, /* taxonomyVersion = */ 2L, /* modelVersion = */ 5L);
+ Topic topic3 =
+ Topic.create(/* topic */ 3, /* taxonomyVersion = */ 3L, /* modelVersion = */ 6L);
+ Topic[] topics = {topic1, topic2, topic3};
+
+ // persist returned topics into DB
+ for (long epoch = 0; epoch < numberOfLookBackEpochs; epoch++) {
+ long epochId = currentEpochId - 1 - epoch;
+ Topic topic = topics[(int) epoch];
+
+ mTopicsDao.persistReturnedAppTopicsMap(epochId, Map.of(appOnlyCaller, topic));
+ // SDK needs to be able to learn this topic in past epochs
+ mTopicsDao.persistCallerCanLearnTopics(epochId, Map.of(topic, Set.of(sdk)));
+ }
+
+ // Sdk has an existing topic in Epoch 1
+ mTopicsDao.persistReturnedAppTopicsMap(
+ currentEpochId - numberOfLookBackEpochs + 1, Map.of(appSdkCaller, topic1));
+
+ when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
+
+ mTopicsWorker.loadCache();
+ GetTopicsResult getTopicsResult = mTopicsWorker.getTopics(app, sdk);
+
+ // Only the existing topic will be returned, i.e. No topic assignment has happened.
+ GetTopicsResult expectedGetTopicsResult =
+ new GetTopicsResult.Builder()
+ .setResultCode(STATUS_SUCCESS)
+ .setTaxonomyVersions(List.of(1L))
+ .setModelVersions(List.of(4L))
+ .setTopics(List.of(1))
+ .build();
+
+ // Since the returned topic list is shuffled, elements have to be verified separately
+ assertThat(getTopicsResult.getResultCode())
+ .isEqualTo(expectedGetTopicsResult.getResultCode());
+ assertThat(getTopicsResult.getTaxonomyVersions())
+ .containsExactlyElementsIn(expectedGetTopicsResult.getTaxonomyVersions());
+ assertThat(getTopicsResult.getModelVersions())
+ .containsExactlyElementsIn(expectedGetTopicsResult.getModelVersions());
+ assertThat(getTopicsResult.getTopics())
+ .containsExactlyElementsIn(expectedGetTopicsResult.getTopics());
}
@Test
@@ -499,7 +557,8 @@ public class TopicsWorkerTest {
final String app = "app";
final String sdk = "sdk";
- List<String> tableExclusionList = List.of(TopicsTables.BlockedTopicsContract.TABLE);
+ ArrayList<String> tableExclusionList = new ArrayList<>();
+ tableExclusionList.add(TopicsTables.BlockedTopicsContract.TABLE);
Topic topic1 =
Topic.create(/* topic */ 1, /* taxonomyVersion = */ 1L, /* modelVersion = */ 4L);
@@ -521,6 +580,7 @@ public class TopicsWorkerTest {
mTopicsDao.recordBlockedTopic(topic1);
when(mMockEpochManager.getCurrentEpochId()).thenReturn(epochId);
+ when(mMockEpochManager.supportsTopicContributorFeature()).thenReturn(true);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numberOfLookBackEpochs);
// Real Cache Manager requires loading cache before getTopics() being called.
@@ -555,15 +615,11 @@ public class TopicsWorkerTest {
assertThat(getTopicsResultAppSdk1.getTopics())
.containsExactlyElementsIn(expectedGetTopicsResult.getTopics());
- verify(mMockEpochManager, times(5)).getCurrentEpochId();
- // app only caller has 1 fewer invocation of getTopicsNumberOfLookBackEpochs()
- verify(mMockFlags, times(4)).getTopicsNumberOfLookBackEpochs();
-
// Clear all data in database belonging to app except blocked topics table
mTopicsWorker.clearAllTopicsData(tableExclusionList);
assertThat(mTopicsDao.retrieveAllBlockedTopics()).isNotEmpty();
- mTopicsWorker.clearAllTopicsData(Collections.emptyList());
+ mTopicsWorker.clearAllTopicsData(new ArrayList<>());
assertThat(mTopicsDao.retrieveAllBlockedTopics()).isEmpty();
GetTopicsResult emptyGetTopicsResult =
@@ -576,13 +632,35 @@ public class TopicsWorkerTest {
assertThat(mTopicsWorker.getTopics(app, sdk)).isEqualTo(emptyGetTopicsResult);
assertThat(mTopicsWorker.getTopics(app, /* sdk */ "")).isEqualTo(emptyGetTopicsResult);
+ }
- // Invocation Summary:
- // loadCache(): 1, getTopics(): 4 * 2, clearAllTopicsData(): 2
- verify(mMockEpochManager, times(11)).getCurrentEpochId();
- // app only caller has 1 fewer invocation of getTopicsNumberOfLookBackEpochs(), and it
- // happens twice.
- verify(mMockFlags, times(9)).getTopicsNumberOfLookBackEpochs();
+ @Test
+ public void testClearAllTopicsData_topicContributorsTable() {
+ final long epochId = 1;
+ final int topicId = 1;
+ final String app = "app";
+ Map<Integer, Set<String>> topicContributorsMap = Map.of(topicId, Set.of(app));
+ mTopicsDao.persistTopicContributors(epochId, topicContributorsMap);
+
+ // To test feature flag is off
+ doReturn(false).when(mMockEpochManager).supportsTopicContributorFeature();
+ mTopicsWorker.clearAllTopicsData(/* tables to exclude */ new ArrayList<>());
+ // TopicContributors table should remain the same
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId))
+ .isEqualTo(topicContributorsMap);
+
+ // To test feature flag is on
+ doReturn(true).when(mMockEpochManager).supportsTopicContributorFeature();
+ mTopicsWorker.clearAllTopicsData(/* tables to exclude */ new ArrayList<>());
+ // TopicContributors table be cleared.
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId)).isEmpty();
+ }
+
+ @Test
+ public void testClearAllTopicsData_ImmutableList() {
+ assertThrows(
+ ClassCastException.class,
+ () -> mTopicsWorker.clearAllTopicsData((ArrayList<String>) List.of("anyString")));
}
@Test
@@ -599,7 +677,6 @@ public class TopicsWorkerTest {
final long modelVersion = 1L;
final int numOfLookBackEpochs = 3;
final int topicsNumberOfTopTopics = 5;
- final int topicsNumberOfRandomTopics = 1;
final int topicsPercentageForRandomTopic = 5;
Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
@@ -638,14 +715,13 @@ public class TopicsWorkerTest {
topicsPercentageForRandomTopic, // Will select a regular topic
1, // Index of second topic
0, // Will select a random topic
- topicsNumberOfRandomTopics - 1 // Select the last random topic
+ 0 // Select the first random topic
});
AppUpdateManager appUpdateManager =
- new AppUpdateManager(mTopicsDao, mockRandom, mMockFlags);
+ new AppUpdateManager(mDbHelper, mTopicsDao, mockRandom, mMockFlags);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numOfLookBackEpochs);
when(mMockFlags.getTopicsNumberOfTopTopics()).thenReturn(topicsNumberOfTopTopics);
- when(mMockFlags.getTopicsNumberOfRandomTopics()).thenReturn(topicsNumberOfRandomTopics);
when(mMockFlags.getTopicsPercentageForRandomTopic())
.thenReturn(topicsPercentageForRandomTopic);
@@ -679,6 +755,12 @@ public class TopicsWorkerTest {
mTopicsDao.recordUsageHistory(epochId, app1, sdk);
mTopicsDao.recordUsageHistory(epochId, app2, sdk);
mTopicsDao.recordUsageHistory(epochId, app4, sdk);
+
+ // Persist topics to TopicContributors Table avoid being filtered out
+ for (Topic topic : topTopics) {
+ mTopicsDao.persistTopicContributors(
+ epochId, Map.of(topic.getTopic(), Set.of(app1, app2, app3, app4)));
+ }
}
// Persist returned topic to app 5. Note that the epoch id to persist is older than
// (currentEpochId - numOfLookBackEpochs). Therefore, app5 won't be handled as a newly
@@ -688,6 +770,9 @@ public class TopicsWorkerTest {
when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numOfLookBackEpochs);
+ // Enable Topic Contributors Check Feature
+ doReturn(true).when(mDbHelper).supportsTopicContributorsTable();
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
// Initialize a local TopicsWorker to use mocked AppUpdateManager
TopicsWorker topicsWorker =
@@ -768,19 +853,12 @@ public class TopicsWorkerTest {
// Therefore, it won't get any topic in recent epochs.
assertThat((topicsWorker.getTopics(app5, sdk))).isEqualTo(emptyGetTopicsResult);
- // Invocations Summary
- // reconcileInstalledApps(): 1, loadCache(): 1, getTopics(): 4 * 2,
- verify(mMockEpochManager, times(10)).getCurrentEpochId();
- // app3 is passed as app only caller, so it doesn't assign topic to sdk. Therefore, the
- // invocation time for getTopicsNumberOfLookBackEpochs() is 1 time fewer.
- verify(mMockFlags, times(9)).getTopicsNumberOfLookBackEpochs();
verify(mMockFlags).getTopicsNumberOfTopTopics();
- verify(mMockFlags).getTopicsNumberOfRandomTopics();
verify(mMockFlags).getTopicsPercentageForRandomTopic();
}
@Test
- public void testDeletePackageData() {
+ public void testHandleAppUninstallation() {
final long epochId = 4L;
final int numberOfLookBackEpochs = 3;
final String app = "app";
@@ -833,7 +911,7 @@ public class TopicsWorkerTest {
// Delete data belonging to the app
Uri packageUri = Uri.parse("package:" + app);
- mTopicsWorker.deletePackageData(packageUri);
+ mTopicsWorker.handleAppUninstallation(packageUri);
GetTopicsResult emptyGetTopicsResult =
new GetTopicsResult.Builder()
@@ -843,11 +921,284 @@ public class TopicsWorkerTest {
.setTopics(Collections.emptyList())
.build();
assertThat((mTopicsWorker.getTopics(app, sdk))).isEqualTo(emptyGetTopicsResult);
+ }
+
+ @Test
+ public void testHandleAppUninstallation_handleTopTopicsWithoutContributors() {
+ // The test sets up to handle below scenarios:
+ // * Both app1 and app2 have usage in the epoch and all 6 topics are top topics.
+ // * app1 is classified to topic1, topic2. app2 is classified to topic1 and topic3.
+ // * Both app1 and app2 calls Topics API via .
+ // * In Epoch1, as app2 is able to learn topic2 via sdk, though topic2 is not a classified
+ // topic of app2, both app1 and app2 can have topic2 as the returned topic.
+ // * In Epoch4, app1 gets uninstalled. Since app1 is the only contributor of topic2, topic2
+ // will be deleted from epoch1. Therefore, app2 will also have empty returned topic in
+ // Epoch1, along with app1.
+ // * Comparison case in Epoch2 (multiple contributors): Both app1 and app3 has usages and
+ // are classified topic1 and topic2 with topic1 as the returned topic . When app1 gets
+ // uninstalled in Epoch4, app3 will still be able to return topic2 which comes from
+ // Epoch2.
+ // * Comparison case in Epoch3 (the feature topic is only removed on epoch basis): app4 has
+ // same setup as app2 in Epoch1: it has topic2 as returned topic in Epoch1, but is NOT
+ // classified to topic2. app4 also has topic2 as returned topic in Epoch3. Therefore, when
+ // app1 is uninstalled in Epoch4, topic1 will be removed for app4 as returned topic in
+ // Epoch1 but not in Epoch3. So if app4 calls Topics API in Epoch4, it's still able to
+ // return topic1 as a result.
+ final long epochId1 = 1;
+ final long epochId2 = 2;
+ final long epochId3 = 3;
+ final long epochId4 = 4;
+ final long taxonomyVersion = 1L;
+ final long modelVersion = 1L;
+ final String app1 = "app1"; // app to uninstall at Epoch4
+ final String app2 = "app2"; // positive case to verify the removal of the returned topic
+ final String app3 = "app3"; // negative case to verify scenario of multiple contributors
+ final String app4 = "app4"; // negative ase to verify the removal is on epoch basis
+ final String sdk = "sdk";
+ Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
+ Topic topic2 = Topic.create(/* topic */ 2, taxonomyVersion, modelVersion);
+ Topic topic3 = Topic.create(/* topic */ 3, taxonomyVersion, modelVersion);
+ Topic topic4 = Topic.create(/* topic */ 4, taxonomyVersion, modelVersion);
+ Topic topic5 = Topic.create(/* topic */ 5, taxonomyVersion, modelVersion);
+ Topic topic6 = Topic.create(/* topic */ 6, taxonomyVersion, modelVersion);
+
+ // Set the number in flag so that it doesn't depend on the actual value in PhFlags.
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(3);
+
+ // Persist Top topics for epoch1 ~ epoch4
+ mTopicsDao.persistTopTopics(
+ epochId1, List.of(topic1, topic2, topic3, topic4, topic5, topic6));
+ mTopicsDao.persistTopTopics(
+ epochId2, List.of(topic1, topic2, topic3, topic4, topic5, topic6));
+ mTopicsDao.persistTopTopics(
+ epochId3, List.of(topic1, topic2, topic3, topic4, topic5, topic6));
+
+ // Persist AppClassificationTopics Table
+ mTopicsDao.persistAppClassificationTopics(
+ epochId1,
+ Map.of(
+ app1, List.of(topic1, topic2),
+ app2, List.of(topic1, topic3),
+ app4, List.of(topic1, topic3)));
+ mTopicsDao.persistAppClassificationTopics(
+ epochId2, // app1 and app3 have same setup in epoch2
+ Map.of(
+ app1, List.of(topic1, topic2),
+ app3, List.of(topic1, topic2)));
+ mTopicsDao.persistAppClassificationTopics(
+ epochId3, // app4 has topic2 as returned topic in epoch3, which won't be removed.
+ Map.of(app4, List.of(topic2)));
+
+ // Compute and persist TopicContributors table
+ mTopicsDao.persistTopicContributors(
+ epochId1,
+ Map.of(
+ topic1.getTopic(), Set.of(app1, app2, app4),
+ topic2.getTopic(), Set.of(app1),
+ topic3.getTopic(), Set.of(app2, app4)));
+ mTopicsDao.persistTopicContributors(
+ epochId2,
+ Map.of(
+ topic1.getTopic(), Set.of(app1, app3),
+ topic2.getTopic(), Set.of(app1, app3)));
+ mTopicsDao.persistTopicContributors(epochId3, Map.of(topic2.getTopic(), Set.of(app4)));
+
+ // Persist Usage table to ensure each app has called Topics API in favored epoch
+ mTopicsDao.recordUsageHistory(epochId1, app1, sdk);
+ mTopicsDao.recordUsageHistory(epochId1, app2, sdk);
+ mTopicsDao.recordUsageHistory(epochId2, app1, sdk);
+ mTopicsDao.recordUsageHistory(epochId2, app3, sdk);
+ mTopicsDao.recordUsageHistory(epochId3, app4, sdk);
+
+ // Persist ReturnedTopics table, all returned topics should be topic2 based on the setup
+ mTopicsDao.persistReturnedAppTopicsMap(
+ epochId1,
+ Map.of(
+ Pair.create(app1, sdk), topic2,
+ Pair.create(app2, sdk), topic2));
+ mTopicsDao.persistReturnedAppTopicsMap(
+ epochId2,
+ Map.of(
+ Pair.create(app1, sdk), topic2,
+ Pair.create(app3, sdk), topic2));
+ mTopicsDao.persistReturnedAppTopicsMap(epochId3, Map.of(Pair.create(app4, sdk), topic2));
+
+ // Real Cache Manager requires loading cache before getTopics() being called.
+ // The results are observed at Epoch4
+ when(mMockEpochManager.getCurrentEpochId()).thenReturn(epochId4);
+ mTopicsWorker.loadCache();
+ // Enable Topic Contributors check feature
+ doReturn(true).when(mDbHelper).supportsTopicContributorsTable();
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+
+ // Verify apps are able to get topic before uninstallation happens
+ GetTopicsResult topic2GetTopicsResult =
+ new GetTopicsResult.Builder()
+ .setResultCode(STATUS_SUCCESS)
+ .setTaxonomyVersions(List.of(taxonomyVersion))
+ .setModelVersions(List.of(modelVersion))
+ .setTopics(List.of(topic2.getTopic()))
+ .build();
+ assertThat(mTopicsWorker.getTopics(app1, sdk)).isEqualTo(topic2GetTopicsResult);
+ assertThat(mTopicsWorker.getTopics(app2, sdk)).isEqualTo(topic2GetTopicsResult);
+ assertThat(mTopicsWorker.getTopics(app3, sdk)).isEqualTo(topic2GetTopicsResult);
+ assertThat(mTopicsWorker.getTopics(app4, sdk)).isEqualTo(topic2GetTopicsResult);
+
+ // Uninstall app1
+ Uri packageUri = Uri.parse("package:" + app1);
+ mTopicsWorker.handleAppUninstallation(packageUri);
+
+ // Verify Topics API results at Epoch 4
+ GetTopicsResult emptyGetTopicsResult =
+ new GetTopicsResult.Builder()
+ .setResultCode(STATUS_SUCCESS)
+ .setTaxonomyVersions(Collections.emptyList())
+ .setModelVersions(Collections.emptyList())
+ .setTopics(Collections.emptyList())
+ .build();
+
+ // app1 doesn't have any returned topics due to uninstallation
+ assertThat(mTopicsWorker.getTopics(app1, sdk)).isEqualTo(emptyGetTopicsResult);
+ // app2 doesn't have returned topics as it only calls Topics API at Epoch1 and its returned
+ // topic topic2 is cleaned due to app1's uninstallation.
+ assertThat(mTopicsWorker.getTopics(app2, sdk)).isEqualTo(emptyGetTopicsResult);
+ // app3 has topic2 as returned topic. topic2 won't be cleaned at Epoch2 as both app1 and
+ // app3 are contributors to topic2 in Epoch3.
+ assertThat(mTopicsWorker.getTopics(app3, sdk)).isEqualTo(topic2GetTopicsResult);
+ // app4 has topic2 as returned topic. topic2 is cleaned as returned topic for app4 in
+ // Epoch1. However, app4 is still able to return topic2 as topic2 is a returned topic for
+ // app4 at Epoch3.
+ assertThat(mTopicsWorker.getTopics(app4, sdk)).isEqualTo(topic2GetTopicsResult);
+
+ // Verify TopicContributors Map is updated: app1 should be removed after the uninstallation.
+ // To make the result more readable, original TopicContributors Map before uninstallation is
+ // Epoch1: topic1 -> app1, app2, app4
+ // topic2 -> app1
+ // topic3 -> app2, app4
+ // Epoch2: topic1 -> app1, app3
+ // topic2 -> app1, app3
+ // Epoch3: topic2 -> app4
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId1))
+ .isEqualTo(
+ Map.of(
+ topic1.getTopic(),
+ Set.of(app2, app4),
+ topic3.getTopic(),
+ Set.of(app2, app4)));
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId2))
+ .isEqualTo(
+ Map.of(topic1.getTopic(), Set.of(app3), topic2.getTopic(), Set.of(app3)));
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId3))
+ .isEqualTo(Map.of(topic2.getTopic(), Set.of(app4)));
+ }
+
+ @Test
+ public void testHandleAppUninstallation_contributorDeletionsToSameTopic() {
+ // To test the scenario a topic has two contributors, and both are deleted consecutively.
+ // Both app1 and app2 are contributors to topic1 and return topic1. app3 is not the
+ // contributor but also returns topic1, learnt via same SDK.
+ final long epochId1 = 1;
+ final long epochId2 = 2;
+ final long taxonomyVersion = 1L;
+ final long modelVersion = 1L;
+ final String app1 = "app1";
+ final String app2 = "app2";
+ final String app3 = "app3";
+ final String sdk = "sdk";
+ Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
+ Topic topic2 = Topic.create(/* topic */ 2, taxonomyVersion, modelVersion);
+ Topic topic3 = Topic.create(/* topic */ 3, taxonomyVersion, modelVersion);
+ Topic topic4 = Topic.create(/* topic */ 4, taxonomyVersion, modelVersion);
+ Topic topic5 = Topic.create(/* topic */ 5, taxonomyVersion, modelVersion);
+ Topic topic6 = Topic.create(/* topic */ 6, taxonomyVersion, modelVersion);
+
+ // Set the number in flag so that it doesn't depend on the actual value in PhFlags.
+ when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(1);
+
+ // Persist Top topics for epoch1 ~ epoch4
+ mTopicsDao.persistTopTopics(
+ epochId1, List.of(topic1, topic2, topic3, topic4, topic5, topic6));
+
+ // Persist AppClassificationTopics Table
+ mTopicsDao.persistAppClassificationTopics(
+ epochId1,
+ Map.of(
+ app1, List.of(topic1),
+ app2, List.of(topic1)));
+
+ // Compute and persist TopicContributors table
+ mTopicsDao.persistTopicContributors(
+ epochId1, Map.of(topic1.getTopic(), Set.of(app1, app2)));
+
+ // Persist ReturnedTopics table. App3 is able to
+ mTopicsDao.persistReturnedAppTopicsMap(
+ epochId1,
+ Map.of(
+ Pair.create(app1, sdk), topic1,
+ Pair.create(app2, sdk), topic1,
+ Pair.create(app3, sdk), topic1));
+
+ // Real Cache Manager requires loading cache before getTopics() being called.
+ // The results are observed at EpochId = 2
+ when(mMockEpochManager.getCurrentEpochId()).thenReturn(epochId2);
+ mTopicsWorker.loadCache();
+ // Enable Topic Contributors check feature
+ doReturn(true).when(mDbHelper).supportsTopicContributorsTable();
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
+
+ // An empty getTopics() result to verify
+ GetTopicsResult emptyGetTopicsResult =
+ new GetTopicsResult.Builder()
+ .setResultCode(STATUS_SUCCESS)
+ .setTaxonomyVersions(Collections.emptyList())
+ .setModelVersions(Collections.emptyList())
+ .setTopics(Collections.emptyList())
+ .build();
+
+ // Delete app1
+ mTopicsWorker.handleAppUninstallation(Uri.parse(app1));
+ // app1 should be deleted from TopicContributors Map
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId1))
+ .isEqualTo(Map.of(topic1.getTopic(), Set.of(app2)));
+ // app1 should have empty result
+ assertThat(mTopicsWorker.getTopics(app1, sdk)).isEqualTo(emptyGetTopicsResult);
+
+ // Delete app2
+ mTopicsWorker.handleAppUninstallation(Uri.parse(app2));
+ // topic1 has app2 as the only contributor, and will be removed.
+ assertThat(mTopicsDao.retrieveTopicToContributorsMap(epochId1)).isEmpty();
+ // app2 should have empty result
+ assertThat(mTopicsWorker.getTopics(app2, sdk)).isEqualTo(emptyGetTopicsResult);
+
+ // As topic1 is removed, app3 also has empty result
+ assertThat(mTopicsWorker.getTopics(app3, sdk)).isEqualTo(emptyGetTopicsResult);
+ }
+
+ @Test
+ public void testHandleAppUninstallation_disableTopicContributorsCheck() {
+ final String app = "app";
+ Uri packageUri = Uri.parse("package:" + app);
+
+ AppUpdateManager appUpdateManager =
+ spy(new AppUpdateManager(mDbHelper, mTopicsDao, new Random(), mMockFlags));
+ TopicsWorker topicsWorker =
+ new TopicsWorker(
+ mMockEpochManager,
+ mCacheManager,
+ mBlockedTopicsManager,
+ appUpdateManager,
+ mMockFlags);
+
+ when(mMockEpochManager.getCurrentEpochId()).thenReturn(/* any value */ 1L);
+ when(appUpdateManager.convertUriToAppName(packageUri)).thenReturn(app);
+
+ // Verify when feature flag is off
+ doReturn(false).when(mDbHelper).supportsTopicContributorsTable();
+ topicsWorker.handleAppUninstallation(packageUri);
- // Invocations Summary
- // loadCache() : 1, getTopics(): 2 * 2, deletePackageData(): 1
- verify(mMockEpochManager, times(6)).getCurrentEpochId();
- verify(mMockFlags, times(6)).getTopicsNumberOfLookBackEpochs();
+ verify(appUpdateManager).convertUriToAppName(packageUri);
+ verify(appUpdateManager, never()).handleTopTopicsWithoutContributors(anyLong(), any());
+ verify(appUpdateManager).deleteAppDataFromTableByApps(List.of(app));
}
@Test
@@ -859,7 +1210,6 @@ public class TopicsWorkerTest {
final long modelVersion = 1L;
final int numOfLookBackEpochs = 3;
final int topicsNumberOfTopTopics = 5;
- final int topicsNumberOfRandomTopics = 1;
final int topicsPercentageForRandomTopic = 5;
// As selectAssignedTopicFromTopTopics() randomly assigns a top topic, pass in a Mocked
@@ -876,10 +1226,10 @@ public class TopicsWorkerTest {
topicsPercentageForRandomTopic, // Will select a regular topic
1, // Index of second topic
0, // Will select a random topic
- topicsNumberOfRandomTopics - 1 // Select the last random topic
+ 0 // Select the first random topic
});
AppUpdateManager appUpdateManager =
- new AppUpdateManager(mTopicsDao, mockRandom, mMockFlags);
+ new AppUpdateManager(mDbHelper, mTopicsDao, mockRandom, mMockFlags);
// Create a local TopicsWorker in order to user above local AppUpdateManager
TopicsWorker topicsWorker =
new TopicsWorker(
@@ -891,10 +1241,12 @@ public class TopicsWorkerTest {
when(mMockFlags.getTopicsNumberOfLookBackEpochs()).thenReturn(numOfLookBackEpochs);
when(mMockFlags.getTopicsNumberOfTopTopics()).thenReturn(topicsNumberOfTopTopics);
- when(mMockFlags.getTopicsNumberOfRandomTopics()).thenReturn(topicsNumberOfRandomTopics);
when(mMockFlags.getTopicsPercentageForRandomTopic())
.thenReturn(topicsPercentageForRandomTopic);
when(mMockEpochManager.getCurrentEpochId()).thenReturn(currentEpochId);
+ // Enable Topic Contributors check feature
+ doReturn(true).when(mDbHelper).supportsTopicContributorsTable();
+ when(mMockFlags.getEnableTopicContributorsCheck()).thenReturn(true);
Topic topic1 = Topic.create(/* topic */ 1, taxonomyVersion, modelVersion);
Topic topic2 = Topic.create(/* topic */ 2, taxonomyVersion, modelVersion);
@@ -909,6 +1261,11 @@ public class TopicsWorkerTest {
epochId >= currentEpochId - numOfLookBackEpochs;
epochId--) {
mTopicsDao.persistTopTopics(epochId, topTopics);
+ // Persist topics to TopicContributors Table avoid being filtered out
+ for (Topic topic : topTopics) {
+ mTopicsDao.persistTopicContributors(
+ epochId, Map.of(topic.getTopic(), Set.of(appName)));
+ }
}
// Verify getTopics() returns nothing before calling assignTopicsToNewlyInstalledApps()
@@ -944,13 +1301,7 @@ public class TopicsWorkerTest {
assertThat(getTopicsResult.getTopics())
.containsExactlyElementsIn(expectedGetTopicsResult.getTopics());
- // Invocations Summary
- // loadCache() : 1, assignTopicsToNewlyInstalledApps() : 1, getTopics(): 2 * 2
- verify(mMockEpochManager, times(6)).getCurrentEpochId();
- // loadCache() : 1, assignTopicsToNewlyInstalledApps() : 1, getTopics(): 1 * 2
- verify(mMockFlags, times(4)).getTopicsNumberOfLookBackEpochs();
verify(mMockFlags).getTopicsNumberOfTopTopics();
- verify(mMockFlags).getTopicsNumberOfRandomTopics();
verify(mMockFlags).getTopicsPercentageForRandomTopic();
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/CommonClassifierHelperTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/CommonClassifierHelperTest.java
index 010a69dfb..563c40d87 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/CommonClassifierHelperTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/CommonClassifierHelperTest.java
@@ -22,6 +22,7 @@ import static com.android.adservices.service.topics.classifier.CommonClassifierH
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -29,6 +30,8 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.MockRandom;
import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.EpochComputationGetTopTopicsStats;
import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
import com.google.common.collect.ImmutableList;
@@ -37,6 +40,7 @@ import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -83,6 +87,7 @@ public class CommonClassifierHelperTest {
@Mock SynchronousFileStorage mMockFileStorage;
@Mock Map<String, ClientFile> mMockDownloadedFiles;
+ @Mock AdServicesLogger mLogger;
@Before
public void setUp() {
@@ -133,6 +138,8 @@ public class CommonClassifierHelperTest {
@Test
public void testGetTopTopics_legalInput() {
+ ArgumentCaptor<EpochComputationGetTopTopicsStats> argument =
+ ArgumentCaptor.forClass(EpochComputationGetTopTopicsStats.class);
// construction the appTopics map so that when sorting by the number of occurrences,
// the order of topics are:
// topic1, topic2, topic3, topic4, topic5, ...,
@@ -151,7 +158,8 @@ public class CommonClassifierHelperTest {
testLabels,
new Random(),
/* numberOfTopTopics = */ 5,
- /* numberOfRandomTopics = */ 1);
+ /* numberOfRandomTopics = */ 1,
+ mLogger);
assertThat(testResponse.get(0)).isEqualTo(getTestTopic(1));
assertThat(testResponse.get(1)).isEqualTo(getTestTopic(2));
@@ -161,10 +169,23 @@ public class CommonClassifierHelperTest {
// Check the random topic is not empty
// The random topic is at the end
assertThat(testResponse.get(5)).isNotNull();
+
+ verify(mLogger).logEpochComputationGetTopTopicsStats(argument.capture());
+ assertThat(argument.getValue())
+ .isEqualTo(
+ EpochComputationGetTopTopicsStats.builder()
+ .setTopTopicCount(5)
+ .setPaddedRandomTopicsCount(0)
+ .setAppsConsideredCount(-1)
+ .setSdksConsideredCount(-1)
+ .build());
}
@Test
public void testGetTopTopics_largeTopTopicsInput() {
+ ArgumentCaptor<EpochComputationGetTopTopicsStats> argument =
+ ArgumentCaptor.forClass(EpochComputationGetTopTopicsStats.class);
+
Map<String, List<Topic>> appTopics = new HashMap<>();
appTopics.put("app1", getTestTopics(Arrays.asList(1, 2, 3, 4, 5)));
@@ -176,10 +197,20 @@ public class CommonClassifierHelperTest {
testLabels,
new Random(),
/* numberOfTopTopics = */ 15,
- /* numberOfRandomTopics = */ 1);
+ /* numberOfRandomTopics = */ 1,
+ mLogger);
// The response body should contain 11 topics.
assertThat(testResponse.size()).isEqualTo(16);
+ verify(mLogger).logEpochComputationGetTopTopicsStats(argument.capture());
+ assertThat(argument.getValue())
+ .isEqualTo(
+ EpochComputationGetTopTopicsStats.builder()
+ .setTopTopicCount(15)
+ .setPaddedRandomTopicsCount(10)
+ .setAppsConsideredCount(-1)
+ .setSdksConsideredCount(-1)
+ .build());
}
@Test
@@ -196,7 +227,8 @@ public class CommonClassifierHelperTest {
testLabels,
new Random(),
/* numberOfTopTopics = */ 0,
- /* numberOfRandomTopics = */ 1));
+ /* numberOfRandomTopics = */ 1,
+ mLogger));
}
@Test
@@ -212,7 +244,8 @@ public class CommonClassifierHelperTest {
testLabels,
new Random(),
/* numberOfTopTopics = */ 3,
- /* numberOfRandomTopics = */ 0));
+ /* numberOfRandomTopics = */ 0,
+ mLogger));
}
@Test
@@ -229,7 +262,8 @@ public class CommonClassifierHelperTest {
testLabels,
new Random(),
/* numberOfTopTopics = */ -5,
- /* numberOfRandomTopics = */ 1));
+ /* numberOfRandomTopics = */ 1,
+ mLogger));
}
@Test
@@ -247,7 +281,8 @@ public class CommonClassifierHelperTest {
testLabels,
new Random(),
/* numberOfTopTopics = */ 3,
- /* numberOfRandomTopics = */ -1));
+ /* numberOfRandomTopics = */ -1,
+ mLogger));
}
@Test
@@ -261,7 +296,8 @@ public class CommonClassifierHelperTest {
testLabels,
new Random(),
/* numberOfTopTopics = */ 5,
- /* numberOfRandomTopics = */ 1);
+ /* numberOfRandomTopics = */ 1,
+ mLogger);
// The response body should be empty.
assertThat(testResponse).isEmpty();
@@ -269,6 +305,8 @@ public class CommonClassifierHelperTest {
@Test
public void testGetTopTopics_emptyTopicInEachApp() {
+ ArgumentCaptor<EpochComputationGetTopTopicsStats> argument =
+ ArgumentCaptor.forClass(EpochComputationGetTopTopicsStats.class);
Map<String, List<Topic>> appTopics = new HashMap<>();
// app1 and app2 do not have any classification topics.
@@ -283,10 +321,20 @@ public class CommonClassifierHelperTest {
testLabels,
new Random(),
/* numberOfTopTopics = */ 5,
- /* numberOfRandomTopics = */ 1);
+ /* numberOfRandomTopics = */ 1,
+ mLogger);
// The response body should be empty
assertThat(testResponse).isEmpty();
+ verify(mLogger).logEpochComputationGetTopTopicsStats(argument.capture());
+ assertThat(argument.getValue())
+ .isEqualTo(
+ EpochComputationGetTopTopicsStats.builder()
+ .setTopTopicCount(0)
+ .setPaddedRandomTopicsCount(0)
+ .setAppsConsideredCount(-1)
+ .setSdksConsideredCount(-1)
+ .build());
}
@Test
@@ -312,7 +360,8 @@ public class CommonClassifierHelperTest {
testLabels,
mockRandom,
/* numberOfTopTopics = */ 5,
- /* numberOfRandomTopics = */ 1);
+ /* numberOfRandomTopics = */ 1,
+ mLogger);
// The response body should contain 5 topics + 1 random topic.
assertThat(testResponse.size()).isEqualTo(6);
@@ -335,7 +384,8 @@ public class CommonClassifierHelperTest {
productionLabels,
new MockRandom(new long[] {50, 100, 300}),
/* numberOfTopTopics = */ 5,
- /* numberOfRandomTopics = */ 1);
+ /* numberOfRandomTopics = */ 1,
+ mLogger);
// The response body should contain 5 topics + 1 random topic.
assertThat(productionResponse.size()).isEqualTo(6);
@@ -372,7 +422,8 @@ public class CommonClassifierHelperTest {
testLabels,
mockRandom,
/* numberOfTopTopics = */ 5,
- /* numberOfRandomTopics = */ 7);
+ /* numberOfRandomTopics = */ 7,
+ mLogger);
// The response body should contain 5 topics + 7 random topic.
assertThat(testResponse.size()).isEqualTo(12);
@@ -406,6 +457,8 @@ public class CommonClassifierHelperTest {
@Test
public void testGetTopTopics_selectDuplicateRandomTopic() {
+ ArgumentCaptor<EpochComputationGetTopTopicsStats> argument =
+ ArgumentCaptor.forClass(EpochComputationGetTopTopicsStats.class);
// In this test, in order to make test result to be deterministic so CommonClassifierHelper
// has to be mocked to get a random topic. However, real CommonClassifierHelper need to
// be tested as well. Therefore, real methods will be called for the other top topics.
@@ -430,7 +483,8 @@ public class CommonClassifierHelperTest {
testLabels,
mockRandom,
/* numberOfTopTopics = */ 5,
- /* numberOfRandomTopics = */ 1);
+ /* numberOfRandomTopics = */ 1,
+ mLogger);
// The response body should contain 5 topics + 1 random topic
assertThat(testResponse.size()).isEqualTo(6);
@@ -443,6 +497,15 @@ public class CommonClassifierHelperTest {
// we should select the one corresponding to the sixth index
// in the MockRandom array topicId, i.e. random = 1, topicId = 10002
assertThat(testResponse.get(5)).isEqualTo(getTestTopic(10002));
+ verify(mLogger).logEpochComputationGetTopTopicsStats(argument.capture());
+ assertThat(argument.getValue())
+ .isEqualTo(
+ EpochComputationGetTopTopicsStats.builder()
+ .setTopTopicCount(5)
+ .setPaddedRandomTopicsCount(0)
+ .setAppsConsideredCount(-1)
+ .setSdksConsideredCount(-1)
+ .build());
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/ModelManagerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/ModelManagerTest.java
index e21304c39..6b39e0932 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/ModelManagerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/ModelManagerTest.java
@@ -19,12 +19,16 @@ package com.android.adservices.service.topics.classifier;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
+import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -45,6 +49,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -62,11 +67,14 @@ public class ModelManagerTest {
private static final String TEST_APPS_FILE_PATH = "classifier/precomputed_test_app_list.csv";
private static final String TEST_CLASSIFIER_ASSETS_METADATA_FILE_PATH =
"classifier/classifier_test_assets_metadata.json";
+ private static final String TEST_CLASSIFIER_MODEL_PATH = "classifier/test_model.tflite";
+
private static final String PRODUCTION_LABELS_FILE_PATH = "classifier/labels_topics.txt";
private static final String PRODUCTION_APPS_FILE_PATH = "classifier/precomputed_app_list.csv";
private static final String PRODUCTION_CLASSIFIER_ASSETS_METADATA_FILE_PATH =
"classifier/classifier_assets_metadata.json";
private static final String MODEL_FILE_PATH = "classifier/model.tflite";
+ private static final String DOWNLOADED_MODEL_FILE_ID = "model.tflite";
@Mock SynchronousFileStorage mMockFileStorage;
@Mock Map<String, ClientFile> mMockDownloadedFiles;
@@ -81,6 +89,8 @@ public class ModelManagerTest {
.initMocks(this)
.strictness(Strictness.WARN)
.startMocking();
+
+ ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
}
@After
@@ -92,7 +102,6 @@ public class ModelManagerTest {
@Test
public void testGetInstance() {
- ExtendedMockito.doReturn(FlagsFactory.getFlagsForTest()).when(FlagsFactory::getFlags);
ModelManager firstInstance = ModelManager.getInstance(sContext);
ModelManager secondInstance = ModelManager.getInstance(sContext);
@@ -102,14 +111,14 @@ public class ModelManagerTest {
}
@Test
- public void testRetrieveModel_bundled() throws IOException {
+ public void testRetrieveModel_bundled_emptyDownloadedFiles() throws IOException {
mProductionModelManager =
new ModelManager(
sContext,
- PRODUCTION_LABELS_FILE_PATH,
- PRODUCTION_APPS_FILE_PATH,
- PRODUCTION_CLASSIFIER_ASSETS_METADATA_FILE_PATH,
- MODEL_FILE_PATH,
+ TEST_LABELS_FILE_PATH,
+ TEST_APPS_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_FILE_PATH,
+ TEST_CLASSIFIER_MODEL_PATH,
mMockFileStorage,
mMockDownloadedFiles);
@@ -120,6 +129,75 @@ public class ModelManagerTest {
}
@Test
+ public void testRetrieveModel_bundled_forceUsingBundledFiles() throws IOException {
+ // Pass in a non-null and non-empty downloadedFiles to Model Manager.
+ Map<String, ClientFile> downloadedFiles = new HashMap<>();
+ downloadedFiles.put(DOWNLOADED_MODEL_FILE_ID, ClientFile.newBuilder().build());
+
+ Flags mockedFlags = mock(Flags.class);
+ doReturn(true).when(mockedFlags).getClassifierForceUseBundledFiles();
+ // Force using bundled file
+ ExtendedMockito.doReturn(mockedFlags).when(FlagsFactory::getFlags);
+
+ mProductionModelManager =
+ new ModelManager(
+ sContext,
+ TEST_LABELS_FILE_PATH,
+ TEST_APPS_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_FILE_PATH,
+ TEST_CLASSIFIER_MODEL_PATH,
+ mMockFileStorage,
+ downloadedFiles);
+
+ ByteBuffer byteBuffer = mProductionModelManager.retrieveModel();
+ // Check byteBuffer capacity greater than 0 when retrieveModel() finds bundled TFLite model
+ // and loads file as a ByteBuffer.
+ assertThat(byteBuffer.capacity()).isGreaterThan(0);
+ }
+
+ @Test
+ public void testRetrieveModel_bundled_incorrectFilePath() throws IOException {
+ mProductionModelManager =
+ new ModelManager(
+ sContext,
+ TEST_LABELS_FILE_PATH,
+ TEST_APPS_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_FILE_PATH,
+ "IncorrectPathWithNoModel",
+ mMockFileStorage,
+ mMockDownloadedFiles);
+
+ ByteBuffer byteBuffer = mProductionModelManager.retrieveModel();
+ // Check byteBuffer capacity is 0 when failed to read a model.
+ assertThat(byteBuffer.capacity()).isEqualTo(0);
+ }
+
+ @Test
+ public void testRetrieveModel_downloaded() throws IOException {
+ // Pass in a non-null and non-empty downloadedFiles to Model Manager
+ Map<String, ClientFile> downloadedFiles = new HashMap<>();
+ downloadedFiles.put(DOWNLOADED_MODEL_FILE_ID, ClientFile.newBuilder().build());
+
+ // Mock File Storage to return null when gets invoked.
+ doReturn(null).when(mMockFileStorage).open(any(), any());
+
+ mProductionModelManager =
+ new ModelManager(
+ sContext,
+ TEST_LABELS_FILE_PATH,
+ TEST_APPS_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_FILE_PATH,
+ TEST_CLASSIFIER_MODEL_PATH,
+ mMockFileStorage,
+ downloadedFiles);
+
+ // The invocation should return null according to above mock.
+ assertThat(mProductionModelManager.retrieveModel()).isNull();
+
+ verify(mMockFileStorage).open(any(), any());
+ }
+
+ @Test
public void testRetrieveLabels_bundled_successfulRead() {
// Test the labels list in production assets
// Check size of list.
@@ -310,12 +388,12 @@ public class ModelManagerTest {
mTestClassifierAssetsMetadata = mTestModelManager.retrieveClassifierAssetsMetadata();
assertThat(mTestClassifierAssetsMetadata).hasSize(7);
- // The property of metadata with correct format should contain 3 attributions:
- // "taxonomy_type", "taxonomy_version", "updated_date".
+ // The property of metadata with correct format should contain 4 attributions:
+ // "taxonomy_type", "taxonomy_version", "updated_date", "build_id".
// The key name of property is "version_info"
- assertThat(mTestClassifierAssetsMetadata.get("version_info")).hasSize(3);
- assertThat(mTestClassifierAssetsMetadata.get("version_info").keySet()).containsExactly(
- "taxonomy_type", "taxonomy_version", "updated_date");
+ assertThat(mTestClassifierAssetsMetadata.get("version_info")).hasSize(4);
+ assertThat(mTestClassifierAssetsMetadata.get("version_info").keySet())
+ .containsExactly("taxonomy_type", "taxonomy_version", "updated_date", "build_id");
// The property "version_info" should have attribution "taxonomy_version"
// and its value should be "12".
@@ -337,7 +415,7 @@ public class ModelManagerTest {
// The asset "labels_topics" should have attribution "asset_version" and its value should be
// "34"
assertThat(mTestClassifierAssetsMetadata.get("labels_topics").get("asset_version"))
- .isEqualTo("34");
+ .isEqualTo("2");
// The asset "labels_topics" should have attribution "path" and its value should be
// "assets/classifier/labels_test_topics.txt"
@@ -383,15 +461,21 @@ public class ModelManagerTest {
// The property of metadata in production metadata should contain 4 attributions:
// "taxonomy_type", "taxonomy_version", "updated_date".
// The key name of property is "version_info"
- assertThat(mProductionClassifierAssetsMetadata.get("version_info")).hasSize(3);
+ assertThat(mProductionClassifierAssetsMetadata.get("version_info")).hasSize(4);
assertThat(mProductionClassifierAssetsMetadata.get("version_info").keySet())
- .containsExactly("taxonomy_type", "taxonomy_version", "updated_date");
+ .containsExactly("taxonomy_type", "taxonomy_version", "build_id", "updated_date");
// The property "version_info" should have attribution "taxonomy_version"
// and its value should be "2".
assertThat(mProductionClassifierAssetsMetadata.get("version_info").get("taxonomy_version"))
.isEqualTo("2");
+ // The property "version_info" should have attribution "build_id"
+ // and its value should be "2". This is used for comparing the model version with MDD
+ // downloaded model.
+ assertThat(mProductionClassifierAssetsMetadata.get("version_info").get("build_id"))
+ .isEqualTo("2");
+
// The property "version_info" should have attribution "taxonomy_type"
// and its value should be "chrome_and_mobile_taxonomy".
assertThat(mProductionClassifierAssetsMetadata.get("version_info").get("taxonomy_type"))
@@ -428,9 +512,9 @@ public class ModelManagerTest {
.isEqualTo("assets/classifier/topic_id_to_name.csv");
// The asset "precomputed_app_list" should have attribution "checksum" and
- // its value should be "780c4edecfe703bdddd57bd84ea7860bba2577f66f2d759558cfe9be3186bece"
+ // its value should be "8749598423bb8baca59e0da508739d544e40f230e7edcdb92438e9e76f75e830"
assertThat(mProductionClassifierAssetsMetadata.get("precomputed_app_list").get("checksum"))
- .isEqualTo("780c4edecfe703bdddd57bd84ea7860bba2577f66f2d759558cfe9be3186bece");
+ .isEqualTo("8749598423bb8baca59e0da508739d544e40f230e7edcdb92438e9e76f75e830");
}
@Test
@@ -458,15 +542,21 @@ public class ModelManagerTest {
// The property of metadata in production metadata should contain 4 attributions:
// "taxonomy_type", "taxonomy_version", "updated_date".
// The key name of property is "version_info"
- assertThat(mProductionClassifierAssetsMetadata.get("version_info")).hasSize(3);
+ assertThat(mProductionClassifierAssetsMetadata.get("version_info")).hasSize(4);
assertThat(mProductionClassifierAssetsMetadata.get("version_info").keySet())
- .containsExactly("taxonomy_type", "taxonomy_version", "updated_date");
+ .containsExactly("taxonomy_type", "taxonomy_version", "build_id", "updated_date");
// The property "version_info" should have attribution "taxonomy_version"
// and its value should be "2".
assertThat(mProductionClassifierAssetsMetadata.get("version_info").get("taxonomy_version"))
.isEqualTo("2");
+ // The property "version_info" should have attribution "build_id"
+ // and its value should be "2". This is used for comparing the model version with MDD
+ // downloaded model.
+ assertThat(mProductionClassifierAssetsMetadata.get("version_info").get("build_id"))
+ .isEqualTo("2");
+
// The property "version_info" should have attribution "taxonomy_type"
// and its value should be "chrome_and_mobile_taxonomy".
assertThat(mProductionClassifierAssetsMetadata.get("version_info").get("taxonomy_type"))
@@ -503,9 +593,57 @@ public class ModelManagerTest {
.isEqualTo("assets/classifier/topic_id_to_name.csv");
// The asset "precomputed_app_list" should have attribution "checksum" and
- // its value should be "780c4edecfe703bdddd57bd84ea7860bba2577f66f2d759558cfe9be3186bece"
+ // its value should be "8749598423bb8baca59e0da508739d544e40f230e7edcdb92438e9e76f75e830"
assertThat(mProductionClassifierAssetsMetadata.get("precomputed_app_list").get("checksum"))
- .isEqualTo("780c4edecfe703bdddd57bd84ea7860bba2577f66f2d759558cfe9be3186bece");
+ .isEqualTo("8749598423bb8baca59e0da508739d544e40f230e7edcdb92438e9e76f75e830");
+ }
+
+ @Test
+ public void testIsModelAvailable_downloadedModelIsAvailable() {
+ mTestModelManager =
+ new ModelManager(
+ sContext,
+ TEST_LABELS_FILE_PATH,
+ TEST_APPS_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_FILE_PATH,
+ TEST_CLASSIFIER_MODEL_PATH,
+ mMockFileStorage,
+ mMockDownloadedFiles);
+
+ // If the downloaded model is available, return true.
+ assertThat(mTestModelManager.isModelAvailable()).isTrue();
+ }
+
+ @Test
+ public void testIsModelAvailable_nonNullBundledModel() {
+ mTestModelManager =
+ new ModelManager(
+ sContext,
+ TEST_LABELS_FILE_PATH,
+ TEST_APPS_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_FILE_PATH,
+ TEST_CLASSIFIER_MODEL_PATH,
+ mMockFileStorage,
+ null /*No downloaded files.*/);
+
+ // If the bundled model is available and non-null, return true.
+ assertThat(mTestModelManager.isModelAvailable()).isTrue();
+ }
+
+ @Test
+ public void testIsModelAvailable_nullBundledModel() {
+ mTestModelManager =
+ new ModelManager(
+ sContext,
+ TEST_LABELS_FILE_PATH,
+ TEST_APPS_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_FILE_PATH,
+ "ModelWrongPath",
+ mMockFileStorage,
+ null /*No downloaded files.*/);
+
+ // If the bundled model is available but null, return false.
+ assertThat(mTestModelManager.isModelAvailable()).isFalse();
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/OnDeviceClassifierTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/OnDeviceClassifierTest.java
index e1ade9345..1b7aa60c6 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/OnDeviceClassifierTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/OnDeviceClassifierTest.java
@@ -17,6 +17,10 @@
package com.android.adservices.service.topics.classifier;
import static com.android.adservices.service.Flags.CLASSIFIER_NUMBER_OF_TOP_LABELS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__ON_DEVICE_CLASSIFIER;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_SUCCESS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_NOT_INVOKED;
import static com.google.common.truth.Truth.assertThat;
@@ -31,11 +35,14 @@ import android.provider.DeviceConfig;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.data.topics.Topic;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.EpochComputationClassifierStats;
import com.android.adservices.service.topics.AppInfo;
import com.android.adservices.service.topics.PackageManagerUtil;
import com.android.modules.utils.testing.TestableDeviceConfig;
import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
@@ -43,6 +50,7 @@ import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -55,6 +63,13 @@ import java.util.stream.Collectors;
/** Topic Classifier Test {@link OnDeviceClassifier}. */
public class OnDeviceClassifierTest {
+ private static final String TEST_LABELS_FILE_PATH = "classifier/labels_test_topics.txt";
+ private static final String TEST_PRECOMPUTED_FILE_PATH =
+ "classifier/precomputed_test_app_list.csv";
+ private static final String TEST_CLASSIFIER_ASSETS_METADATA_PATH =
+ "classifier/classifier_test_assets_metadata.json";
+ private static final String TEST_CLASSIFIER_MODEL_PATH = "classifier/test_model.tflite";
+
// (b/244313803): Refactor tests to remove DeviceConfig and use Flags.
@Rule
public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
@@ -68,6 +83,7 @@ public class OnDeviceClassifierTest {
@Mock private ModelManager mModelManager;
@Mock Map<String, ClientFile> mMockDownloadedFiles;
private OnDeviceClassifier mOnDeviceClassifier;
+ @Mock AdServicesLogger mLogger;
@Before
public void setUp() throws IOException {
@@ -75,22 +91,49 @@ public class OnDeviceClassifierTest {
mModelManager =
new ModelManager(
sContext,
- ModelManager.BUNDLED_LABELS_FILE_PATH,
- ModelManager.BUNDLED_TOP_APP_FILE_PATH,
- ModelManager.BUNDLED_CLASSIFIER_ASSETS_METADATA_PATH,
- ModelManager.BUNDLED_MODEL_FILE_PATH,
+ TEST_LABELS_FILE_PATH,
+ TEST_PRECOMPUTED_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_PATH,
+ TEST_CLASSIFIER_MODEL_PATH,
mMockFileStorage,
mMockDownloadedFiles);
sPreprocessor = new Preprocessor(sContext);
mOnDeviceClassifier =
new OnDeviceClassifier(
- sPreprocessor, mPackageManagerUtil, new Random(), mModelManager);
+ sPreprocessor, mPackageManagerUtil, new Random(), mModelManager, mLogger);
+ }
+
+ @Test
+ public void testClassify_earlyReturnIfNoModelAvailable() {
+ mModelManager =
+ new ModelManager(
+ sContext,
+ TEST_LABELS_FILE_PATH,
+ TEST_PRECOMPUTED_FILE_PATH,
+ TEST_CLASSIFIER_ASSETS_METADATA_PATH,
+ "ModelWrongPath",
+ mMockFileStorage,
+ null /*No downloaded files.*/);
+ sPreprocessor = new Preprocessor(sContext);
+ mOnDeviceClassifier =
+ new OnDeviceClassifier(
+ sPreprocessor, mPackageManagerUtil, new Random(), mModelManager, mLogger);
+
+ String appPackage1 = "com.example.adservices.samples.topics.sampleapp1";
+ ImmutableSet<String> appPackages = ImmutableSet.of(appPackage1);
+
+ ImmutableMap<String, List<Topic>> classifications =
+ mOnDeviceClassifier.classify(appPackages);
+
+ // Result is empty due to no bundled model available.
+ assertThat(classifications).isEmpty();
}
@Test
- public void testClassify_packageManagerError_returnsDefaultClassifications()
- throws IOException {
+ public void testClassify_packageManagerError_returnsDefaultClassifications() {
+ ArgumentCaptor<EpochComputationClassifierStats> argument =
+ ArgumentCaptor.forClass(EpochComputationClassifierStats.class);
String appPackage1 = "com.example.adservices.samples.topics.sampleapp1";
ImmutableSet<String> appPackages = ImmutableSet.of(appPackage1);
// If fetch from PackageManagerUtil fails, we will use empty strings as descriptions.
@@ -106,6 +149,22 @@ public class OnDeviceClassifierTest {
// Check all the returned labels for default empty string descriptions.
assertThat(classifications.get(appPackage1))
.isEqualTo(createTopics(Arrays.asList(10230, 10253, 10227)));
+
+ // Verify logged atom.
+ verify(mLogger).logEpochComputationClassifierStats(argument.capture());
+ assertThat(argument.getValue())
+ .isEqualTo(
+ EpochComputationClassifierStats.builder()
+ .setTopicIds(ImmutableList.of(10230, 10253, 10227))
+ .setBuildId(8)
+ .setAssetVersion("2")
+ .setClassifierType(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__ON_DEVICE_CLASSIFIER)
+ .setOnDeviceClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_SUCCESS)
+ .setPrecomputedClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_NOT_INVOKED)
+ .build());
}
@Test
@@ -126,7 +185,7 @@ public class OnDeviceClassifierTest {
// One value for input package name.
assertThat(classifications).hasSize(1);
// Verify size of the labels returned.
- assertThat(classifications.get(appPackage1)).hasSize(2);
+ assertThat(classifications.get(appPackage1)).hasSize(3);
// Check if the first category matches in the top CLASSIFIER_NUMBER_OF_TOP_LABELS.
// Scores can differ a little on devices. Using this technique to reduce flakiness.
@@ -148,6 +207,8 @@ public class OnDeviceClassifierTest {
@Test
public void testClassify_successfulClassifications() {
+ ArgumentCaptor<EpochComputationClassifierStats> argument =
+ ArgumentCaptor.forClass(EpochComputationClassifierStats.class);
// Check getClassification for sample descriptions.
String appPackage1 = "com.example.adservices.samples.topics.sampleapp1";
String appPackage2 = "com.example.adservices.samples.topics.sampleapp2";
@@ -172,7 +233,7 @@ public class OnDeviceClassifierTest {
// Two values for two input package names.
assertThat(classifications).hasSize(2);
// Verify size of the labels returned.
- assertThat(classifications.get(appPackage1)).hasSize(2);
+ assertThat(classifications.get(appPackage1)).hasSize(3);
assertThat(classifications.get(appPackage2)).hasSize(CLASSIFIER_NUMBER_OF_TOP_LABELS);
// Check if the first category matches in the top CLASSIFIER_NUMBER_OF_TOP_LABELS.
@@ -183,10 +244,44 @@ public class OnDeviceClassifierTest {
// Expected top 10: 10227, 10225, 10235, 10230, 10238, 10253, 10247, 10254, 10234, 10229
assertThat(classifications.get(appPackage2))
.containsAtLeastElementsIn(createTopics(Arrays.asList(10227)));
+
+ // Verify logged atom.
+ verify(mLogger, times(2)).logEpochComputationClassifierStats(argument.capture());
+ assertThat(argument.getAllValues()).hasSize(2);
+ // Log for appPackage1.
+ assertThat(argument.getAllValues().get(0))
+ .isEqualTo(
+ EpochComputationClassifierStats.builder()
+ .setTopicIds(ImmutableList.of(10253, 10230, 10237))
+ .setBuildId(8)
+ .setAssetVersion("2")
+ .setClassifierType(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__ON_DEVICE_CLASSIFIER)
+ .setOnDeviceClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_SUCCESS)
+ .setPrecomputedClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_NOT_INVOKED)
+ .build());
+ // Log for appPackage2.
+ assertThat(argument.getAllValues().get(1))
+ .isEqualTo(
+ EpochComputationClassifierStats.builder()
+ .setTopicIds(ImmutableList.of(10230, 10227, 10238))
+ .setBuildId(8)
+ .setAssetVersion("2")
+ .setClassifierType(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__ON_DEVICE_CLASSIFIER)
+ .setOnDeviceClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_SUCCESS)
+ .setPrecomputedClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_NOT_INVOKED)
+ .build());
}
@Test
public void testClassify_successfulClassifications_overrideNumberOfTopLabels() {
+ ArgumentCaptor<EpochComputationClassifierStats> argument =
+ ArgumentCaptor.forClass(EpochComputationClassifierStats.class);
// Check getClassification for sample descriptions.
String appPackage1 = "com.example.adservices.samples.topics.sampleapp1";
ImmutableMap<String, AppInfo> appInfoMap =
@@ -196,7 +291,7 @@ public class OnDeviceClassifierTest {
ImmutableSet<String> appPackages = ImmutableSet.of(appPackage1);
when(mPackageManagerUtil.getAppInformation(eq(appPackages))).thenReturn(appInfoMap);
// Override classifierNumberOfTopLabels.
- int overrideNumberOfTopLabels = 2;
+ int overrideNumberOfTopLabels = 0;
setClassifierNumberOfTopLabels(overrideNumberOfTopLabels);
ImmutableMap<String, List<Topic>> classifications =
@@ -206,6 +301,21 @@ public class OnDeviceClassifierTest {
assertThat(classifications).hasSize(1);
// Verify size of the labels returned is equal to the override value.
assertThat(classifications.get(appPackage1)).hasSize(overrideNumberOfTopLabels);
+ // Verify logged atom.
+ verify(mLogger).logEpochComputationClassifierStats(argument.capture());
+ assertThat(argument.getValue())
+ .isEqualTo(
+ EpochComputationClassifierStats.builder()
+ .setTopicIds(ImmutableList.of())
+ .setBuildId(8)
+ .setAssetVersion("2")
+ .setClassifierType(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__ON_DEVICE_CLASSIFIER)
+ .setOnDeviceClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_FAILURE)
+ .setPrecomputedClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_NOT_INVOKED)
+ .build());
}
@Test
@@ -228,7 +338,7 @@ public class OnDeviceClassifierTest {
verify(mPackageManagerUtil).getAppInformation(eq(appPackages));
assertThat(classifications).hasSize(1);
// Expecting 2 values greater than 0.1 threshold.
- assertThat(classifications.get(appPackage1)).hasSize(2);
+ assertThat(classifications.get(appPackage1)).hasSize(3);
}
@Test
@@ -317,7 +427,7 @@ public class OnDeviceClassifierTest {
assertThat(topTopics).hasSize(numberOfTopTopics + numberOfRandomTopics);
// Verify the top topics are from the description that was repeated.
List<Topic> expectedLabelsForCommonDescription =
- createTopics(Arrays.asList(10220, 10235, 10247, 10225));
+ createTopics(Arrays.asList(10230, 10227, 10238, 10253));
assertThat(topTopics.subList(0, numberOfTopTopics))
.containsAnyIn(expectedLabelsForCommonDescription);
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PrecomputedClassifierTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PrecomputedClassifierTest.java
index 4bd4c74a7..3b461bc94 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PrecomputedClassifierTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PrecomputedClassifierTest.java
@@ -16,9 +16,15 @@
package com.android.adservices.service.topics.classifier;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__PRECOMPUTED_CLASSIFIER;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_NOT_INVOKED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_SUCCESS;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -26,14 +32,18 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.data.topics.Topic;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.stats.AdServicesLogger;
+import com.android.adservices.service.stats.EpochComputationClassifierStats;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
+import com.google.common.collect.ImmutableList;
import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
@@ -61,6 +71,7 @@ public class PrecomputedClassifierTest {
private MockitoSession mMockitoSession = null;
@Mock private SynchronousFileStorage mMockFileStorage;
@Mock Map<String, ClientFile> mMockDownloadedFiles;
+ @Mock AdServicesLogger mLogger;
@Before
public void setUp() throws IOException {
@@ -86,7 +97,7 @@ public class PrecomputedClassifierTest {
MODEL_FILE_PATH,
mMockFileStorage,
mMockDownloadedFiles);
- sPrecomputedClassifier = new PrecomputedClassifier(mModelManager);
+ sPrecomputedClassifier = new PrecomputedClassifier(mModelManager, mLogger);
}
@After
@@ -98,6 +109,8 @@ public class PrecomputedClassifierTest {
@Test
public void testClassify_existingApp() {
+ ArgumentCaptor<EpochComputationClassifierStats> argument =
+ ArgumentCaptor.forClass(EpochComputationClassifierStats.class);
// Using sample App. This app has 5 classification topic.
List<Topic> expectedSampleAppTopics =
createTopics(Arrays.asList(10222, 10223, 10116, 10243, 10254));
@@ -113,16 +126,48 @@ public class PrecomputedClassifierTest {
// The correct response body should be exactly the same as expectedAppTopicsResponse
assertThat(testResponse).isEqualTo(expectedAppTopicsResponse);
+ // Verify logged atom.
+ verify(mLogger).logEpochComputationClassifierStats(argument.capture());
+ assertThat(argument.getValue())
+ .isEqualTo(
+ EpochComputationClassifierStats.builder()
+ .setTopicIds(ImmutableList.of(10222, 10223, 10116, 10243, 10254))
+ .setBuildId(2)
+ .setAssetVersion("2")
+ .setClassifierType(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__PRECOMPUTED_CLASSIFIER)
+ .setOnDeviceClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_NOT_INVOKED)
+ .setPrecomputedClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_SUCCESS)
+ .build());
}
@Test
public void testClassify_nonExistingApp() {
+ ArgumentCaptor<EpochComputationClassifierStats> argument =
+ ArgumentCaptor.forClass(EpochComputationClassifierStats.class);
// Check the non-existing app "random_app"
Map<String, List<Topic>> testResponse =
sPrecomputedClassifier.classify(new HashSet<>(Arrays.asList("random_app")));
// The topics list of "random_app" should be empty
assertThat(testResponse.get("random_app")).isEmpty();
+ // Verify logged atom.
+ verify(mLogger).logEpochComputationClassifierStats(argument.capture());
+ assertThat(argument.getValue())
+ .isEqualTo(
+ EpochComputationClassifierStats.builder()
+ .setTopicIds(ImmutableList.of())
+ .setBuildId(2)
+ .setAssetVersion("2")
+ .setClassifierType(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__CLASSIFIER_TYPE__PRECOMPUTED_CLASSIFIER)
+ .setOnDeviceClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__ON_DEVICE_CLASSIFIER_STATUS__ON_DEVICE_CLASSIFIER_STATUS_NOT_INVOKED)
+ .setPrecomputedClassifierStatus(
+ AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED__PRECOMPUTED_CLASSIFIER_STATUS__PRECOMPUTED_CLASSIFIER_STATUS_FAILURE)
+ .build());
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PreprocessorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PreprocessorTest.java
index 25fb0e519..82e047c60 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PreprocessorTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/classifier/PreprocessorTest.java
@@ -42,21 +42,24 @@ public final class PreprocessorTest {
@Test
public void removeStopWords_removesLegitStopWords() {
- assertThat(mPreprocessor.removeStopWords("sample it they them input string is are"))
- .isEqualTo("sample input string");
- assertThat(mPreprocessor.removeStopWords("do does sample it they them input string is are"))
- .isEqualTo("sample input string");
+ assertThat(mPreprocessor.removeStopWords(
+ "sample it they them input string is CustomStopWord1"))
+ .isEqualTo("sample it they them input string is");
+ assertThat(mPreprocessor.removeStopWords(
+ "do CustomStopWord1 sample it they them input string"))
+ .isEqualTo("do sample it they them input string");
}
@Test
public void removeStopWords_checksFinalTrimming() {
- assertThat(mPreprocessor.removeStopWords(" do does sample it they them input is are "))
- .isEqualTo("sample input");
+ assertThat(mPreprocessor.removeStopWords(
+ " do does sample it they them input is CustomStopWord1 "))
+ .isEqualTo("do does sample it they them input is");
}
@Test
public void removeStopWords_justStopWords() {
- assertThat(mPreprocessor.removeStopWords("can will now")).isEqualTo("");
+ assertThat(mPreprocessor.removeStopWords("CustomStopWord1")).isEqualTo("");
}
@Test
@@ -72,43 +75,58 @@ public final class PreprocessorTest {
@Test
public void testPreprocessing_forHttpsURLRemoval() {
assertThat(preprocessAppDescription("The website is https://youtube.com"))
- .isEqualTo("the website is ");
+ .isEqualTo("the website is");
assertThat(preprocessAppDescription("https://youtube.com is the website"))
- .isEqualTo(" is the website");
+ .isEqualTo("is the website");
assertThat(preprocessAppDescription("https://www.tensorflow.org/lite/tutorials")).isEmpty();
}
@Test
public void testPreprocessing_forHttpURLRemoval() {
assertThat(preprocessAppDescription("The website is http://google.com"))
- .isEqualTo("the website is ");
+ .isEqualTo("the website is");
assertThat(preprocessAppDescription("http://google.com is the website"))
- .isEqualTo(" is the website");
+ .isEqualTo("is the website");
assertThat(preprocessAppDescription("http://google.com")).isEmpty();
}
@Test
+ public void testPreprocessing_forNotHttpURLRemoval() {
+ assertThat(preprocessAppDescription("The website is www.youtube.com"))
+ .isEqualTo("the website is");
+ assertThat(preprocessAppDescription("www.youtube.com is the website"))
+ .isEqualTo("is the website");
+ assertThat(preprocessAppDescription("www.tensorflow.org/lite/tutorials")).isEmpty();
+ }
+
+ @Test
public void testPreprocessing_forMentionsRemoval() {
- assertThat(preprocessAppDescription("Code author: @xyz123")).isEqualTo("code author ");
+ assertThat(preprocessAppDescription("Code author: @xyz123")).isEqualTo("code author:");
assertThat(preprocessAppDescription("@xyz123 Code author: @xyz123"))
- .isEqualTo(" code author ");
+ .isEqualTo("code author:");
assertThat(preprocessAppDescription("Code @xyz123 author: @xyz123"))
- .isEqualTo("code author ");
+ .isEqualTo("code author:");
assertThat(preprocessAppDescription("@xyz123")).isEmpty();
}
@Test
- public void testPreprocessing_forUpperCaseToLowerCase() {
- String inputDescription = "SOCIAL MEDIA APP";
- String result = preprocessAppDescription(inputDescription);
- assertThat(result).isEqualTo("social media app");
+ public void testPreprocessing_forHtmlTagsRemoval() {
+ assertThat(preprocessAppDescription("<title>Google is a search engine.</title>"))
+ .isEqualTo("google is a search engine.");
+ assertThat(preprocessAppDescription("<!DOCTYPE html>"
+ + "<html lang=\"en\">"
+ + "<head>"
+ + "<title>Hello World!</title>"
+ + "</head>"))
+ .isEqualTo("hello world!");
+ assertThat(preprocessAppDescription("<p></p>")).isEmpty();
}
@Test
- public void testPreprocessing_forPunctuationRemoval() {
- String inputDescription = "This. is a test, to check! punctuation removal?!@#$%^&*()_+";
+ public void testPreprocessing_forUpperCaseToLowerCase() {
+ String inputDescription = "SOCIAL MEDIA APP";
String result = preprocessAppDescription(inputDescription);
- assertThat(result).isEqualTo("this is a test to check punctuation removal");
+ assertThat(result).isEqualTo("social media app");
}
@Test
@@ -119,17 +137,17 @@ public final class PreprocessorTest {
}
@Test
- public void testPreprocessing_forNumberRemoval() {
- String inputDescription = "Remove 11 numbers 0.";
+ public void testPreprocessing_forTabRemoval() {
+ String inputDescription = "check\tmultiple\t\ttabs.";
String result = preprocessAppDescription(inputDescription);
- assertThat(result).isEqualTo("remove numbers ");
+ assertThat(result).isEqualTo("check multiple tabs.");
}
@Test
public void testPreprocessing_forRemovingMultipleSpaces() {
String inputDescription = "This sentence \n has multiple spaces.";
String result = preprocessAppDescription(inputDescription);
- assertThat(result).isEqualTo("this sentence has multiple spaces");
+ assertThat(result).isEqualTo("this sentence has multiple spaces.");
}
@Test
@@ -139,16 +157,55 @@ public final class PreprocessorTest {
+ " has multiple spaces, 234 4 (). \n"
+ " @Mention &*\n"
+ " BLOCK LETTERS\n"
- + " http://sampleURL.com as well!";
+ + " http://sampleURL.com as well!"
+ + " <p>Hello world!</p>";
String result = preprocessAppDescription(inputDescription);
- assertThat(result).isEqualTo("this description has multiple spaces block letters as well");
+ assertThat(result).isEqualTo("this description has multiple spaces, 234 4 (). &* "
+ + "block letters as well! hello world!");
+ }
+
+ @Test
+ public void testPreprocessing_forRealAppDescription() {
+ String googleTranslateDescription =
+ "• Text translation: Translate between 108 languages by typing\n"
+ + "• Tap to Translate: Copy text in any app and tap the Google Translate "
+ + "icon to translate (all languages)\n"
+ + "• Offline: Translate with no internet connection (59 languages)\n"
+ + "• Instant camera translation: Translate text in images instantly by "
+ + "just pointing your camera (94 languages)\n"
+ + "• Photos: Take or import photos for higher quality translations (90 "
+ + "languages)\n"
+ + "• Conversations: Translate bilingual conversations on the fly (70 "
+ + "languages)";
+ String googleTranslateResult = preprocessAppDescription(googleTranslateDescription);
+ assertThat(googleTranslateResult).isEqualTo(
+ "• text translation: translate between 108 languages by typing "
+ + "• tap to translate: copy text in any app and tap the google translate "
+ + "icon to translate (all languages) • offline: translate with no internet "
+ + "connection (59 languages) • instant camera translation: translate text "
+ + "in images instantly by just pointing your camera (94 languages) "
+ + "• photos: take or import photos for higher quality translations "
+ + "(90 languages) • conversations: translate bilingual conversations "
+ + "on the fly (70 languages)");
+
+ String googleDescription =
+ "• Use voice commands while navigating – even when your device has no connection."
+ + " Try saying \"cancel my navigation\" \"what's my ETA?\" or \"what's my"
+ + " next turn?\"\n"
+ + "• It's easier to access privacy settings from the homescreen. Just tap"
+ + " your Google Account profile picture.";
+ String googleResult = preprocessAppDescription(googleDescription);
+ assertThat(googleResult).isEqualTo("• use voice commands while navigating – even when "
+ + "your device has no connection. try saying \"cancel my navigation\" \"what's my"
+ + " eta?\" or \"what's my next turn?\" • it's easier to access privacy settings "
+ + "from the homescreen. just tap your google account profile picture.");
}
@Test
public void testPreprocessing_forEmptyDescription() {
assertThat(preprocessAppDescription("")).isEmpty();
- assertThat(preprocessAppDescription(" ")).isEqualTo(" ");
- assertThat(preprocessAppDescription(" \n \n \n")).isEqualTo(" ");
+ assertThat(preprocessAppDescription(" ")).isEmpty();
+ assertThat(preprocessAppDescription(" \n \n \n")).isEmpty();
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/jni/java/com/android/adservices/HpkeJniTest.java b/adservices/tests/unittest/service-core/src/jni/java/com/android/adservices/HpkeJniTest.java
index e7a817198..5135cdf5e 100644
--- a/adservices/tests/unittest/service-core/src/jni/java/com/android/adservices/HpkeJniTest.java
+++ b/adservices/tests/unittest/service-core/src/jni/java/com/android/adservices/HpkeJniTest.java
@@ -16,6 +16,8 @@
package com.android.adservices;
+import com.android.adservices.service.measurement.aggregation.AggregateCryptoFixture;
+
import org.junit.Assert;
import org.junit.Test;
@@ -27,9 +29,8 @@ public class HpkeJniTest {
private static final byte[] sPlaintext = "plaintext".getBytes();
private static final byte[] sCiphertext =
decode("0Ie+jDZ/Hznx1IrIkS06V+kAHuD5RsybXWwrKRIbGEL5TJT4/HYny2SHfWbeXxMydwvS0FEZqvzs");
- private static final byte[] sPublicKey = decode("rSJBSUYG0ebvfW1AXCWO0CMGMJhDzpfQm3eLyw1uxX8=");
- private static final byte[] sPrivateKey =
- decode("f86EzLmGaVmc+PwjJk5ADPE4ijQvliWf0CQyY/Zyy7I=");
+ private static final byte[] sPublicKey = AggregateCryptoFixture.getPublicKey();
+ private static final byte[] sPrivateKey = AggregateCryptoFixture.getPrivateKey();
@Test
public void testHpkeEncrypt_Success() {
diff --git a/adservices/tests/unittest/system-service/Android.bp b/adservices/tests/unittest/system-service/Android.bp
index 8d69bff67..5758f9c97 100644
--- a/adservices/tests/unittest/system-service/Android.bp
+++ b/adservices/tests/unittest/system-service/Android.bp
@@ -22,6 +22,7 @@ android_test {
"src/com/android/server/adservices/*.java",
"src/**/*.java",
],
+ defaults: ["modules-utils-testable-device-config-defaults"],
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
static_libs: [
@@ -45,10 +46,10 @@ android_test {
],
sdk_version: "module_current",
min_sdk_version: "Tiramisu",
- target_sdk_version: "current",
+ target_sdk_version: "Tiramisu",
test_suites: [
"general-tests",
"mts-adservices"
],
test_mainline_modules: ["com.google.android.adservices.apex"],
-} \ No newline at end of file
+}
diff --git a/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java b/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java
index aab7236ed..900f5bc16 100644
--- a/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java
+++ b/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java
@@ -16,25 +16,52 @@
package com.android.server.adservices;
+import static com.android.server.adservices.PhFlags.KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
import android.Manifest;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.os.Handler;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.google.common.truth.Truth;
+import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
/** Tests for {@link AdServicesManagerService} */
public class AdServicesManagerServiceTest {
+ @Rule
+ public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
+ new TestableDeviceConfig.TestableDeviceConfigRule();
+
private AdServicesManagerService mService;
private Context mSpyContext;
+ @Mock private PackageManager mMockPackageManager;
+
+ private static final String PPAPI_PACKAGE_NAME = "com.google.android.adservices.api";
private static final String PACKAGE_NAME = "com.package.example";
private static final String PACKAGE_CHANGED_BROADCAST =
"com.android.adservices.PACKAGE_CHANGED";
@@ -45,6 +72,8 @@ public class AdServicesManagerServiceTest {
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
+
Context context = InstrumentationRegistry.getInstrumentation().getContext();
mSpyContext = Mockito.spy(context);
@@ -52,11 +81,93 @@ public class AdServicesManagerServiceTest {
.getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ doReturn(mMockPackageManager).when(mSpyContext).getPackageManager();
+ }
+
+ @Test
+ public void testAdServicesSystemService_enabled_then_disabled() {
+ // First enable the flag.
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED,
+ Boolean.toString(Boolean.TRUE),
+ /* makeDefault */ false);
+
+ // This will trigger the registration of the Receiver.
mService = new AdServicesManagerService(mSpyContext);
+
+ ArgumentCaptor<BroadcastReceiver> argumentReceiver =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor<IntentFilter> argumentIntentFilter =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ ArgumentCaptor<String> argumentPermission = ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor<Handler> argumentHandler = ArgumentCaptor.forClass(Handler.class);
+
+ // Calling the second time will not register again.
+ mService.registerPackagedChangedBroadcastReceivers();
+
+ // The flag is enabled so we call registerReceiverForAllUsers
+ Mockito.verify(mSpyContext, Mockito.times(1))
+ .registerReceiverForAllUsers(
+ argumentReceiver.capture(),
+ argumentIntentFilter.capture(),
+ argumentPermission.capture(),
+ argumentHandler.capture());
+
+ BroadcastReceiver receiver = argumentReceiver.getValue();
+ assertThat(receiver).isNotNull();
+
+ assertThat(argumentIntentFilter.getValue().getAction(0))
+ .isEqualTo(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+ assertThat(argumentIntentFilter.getValue().getAction(1))
+ .isEqualTo(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ assertThat(argumentIntentFilter.getValue().getAction(2))
+ .isEqualTo(Intent.ACTION_PACKAGE_ADDED);
+ assertThat(argumentIntentFilter.getValue().getDataScheme(0)).isEqualTo("package");
+
+ assertThat(argumentPermission.getValue()).isNull();
+ assertThat(argumentHandler.getValue()).isNotNull();
+
+ // Now disable the flag.
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED,
+ Boolean.toString(Boolean.FALSE),
+ /* makeDefault */ false);
+
+ // Calling when the flag is disabled will unregister the Receiver!
+ mService.registerPackagedChangedBroadcastReceivers();
+ Mockito.verify(mSpyContext, Mockito.times(1))
+ .unregisterReceiver(argumentReceiver.capture());
+
+ // The unregistered is called on the same receiver when registered above.
+ assertThat(argumentReceiver.getValue()).isSameInstanceAs(receiver);
+ }
+
+ @Test
+ public void testAdServicesSystemService_disabled() {
+ // Disable the flag.
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED,
+ Boolean.toString(Boolean.FALSE),
+ /* makeDefault */ false);
+
+ mService = new AdServicesManagerService(mSpyContext);
+
+ // The flag is disabled so there is no registerReceiverForAllUsers
+ Mockito.verify(mSpyContext, Mockito.times(0))
+ .registerReceiverForAllUsers(
+ any(BroadcastReceiver.class),
+ any(IntentFilter.class),
+ any(String.class),
+ any(Handler.class));
}
@Test
public void testSendBroadcastForPackageFullyRemoved() {
+ mService = new AdServicesManagerService(mSpyContext);
+
Intent i = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED);
i.setData(Uri.parse("package:" + PACKAGE_NAME));
i.putExtra(Intent.EXTRA_UID, PACKAGE_UID);
@@ -64,24 +175,27 @@ public class AdServicesManagerServiceTest {
ArgumentCaptor<Intent> argumentIntent = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<UserHandle> argumentUser = ArgumentCaptor.forClass(UserHandle.class);
+ setupMockResolveInfo();
Mockito.doNothing().when(mSpyContext).sendBroadcastAsUser(Mockito.any(), Mockito.any());
+
mService.onPackageChange(i, mSpyContext.getUser());
Mockito.verify(mSpyContext, Mockito.times(1))
.sendBroadcastAsUser(argumentIntent.capture(), argumentUser.capture());
- Truth.assertThat(argumentIntent.getValue().getAction())
- .isEqualTo(PACKAGE_CHANGED_BROADCAST);
- Truth.assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData());
- Truth.assertThat(argumentIntent.getValue().getStringExtra("action"))
+ assertThat(argumentIntent.getValue().getAction()).isEqualTo(PACKAGE_CHANGED_BROADCAST);
+ assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData());
+ assertThat(argumentIntent.getValue().getStringExtra("action"))
.isEqualTo(PACKAGE_FULLY_REMOVED);
- Truth.assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1))
+ assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1))
.isEqualTo(PACKAGE_UID);
- Truth.assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser());
+ assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser());
}
@Test
public void testSendBroadcastForPackageAdded() {
+ mService = new AdServicesManagerService(mSpyContext);
+
Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED);
i.setData(Uri.parse("package:" + PACKAGE_NAME));
i.putExtra(Intent.EXTRA_UID, PACKAGE_UID);
@@ -90,24 +204,26 @@ public class AdServicesManagerServiceTest {
ArgumentCaptor<Intent> argumentIntent = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<UserHandle> argumentUser = ArgumentCaptor.forClass(UserHandle.class);
+ setupMockResolveInfo();
Mockito.doNothing().when(mSpyContext).sendBroadcastAsUser(Mockito.any(), Mockito.any());
+
mService.onPackageChange(i, mSpyContext.getUser());
Mockito.verify(mSpyContext, Mockito.times(1))
.sendBroadcastAsUser(argumentIntent.capture(), argumentUser.capture());
- Truth.assertThat(argumentIntent.getValue().getAction())
- .isEqualTo(PACKAGE_CHANGED_BROADCAST);
- Truth.assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData());
- Truth.assertThat(argumentIntent.getValue().getStringExtra("action"))
- .isEqualTo(PACKAGE_ADDED);
- Truth.assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1))
+ assertThat(argumentIntent.getValue().getAction()).isEqualTo(PACKAGE_CHANGED_BROADCAST);
+ assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData());
+ assertThat(argumentIntent.getValue().getStringExtra("action")).isEqualTo(PACKAGE_ADDED);
+ assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1))
.isEqualTo(PACKAGE_UID);
- Truth.assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser());
+ assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser());
}
@Test
public void testSendBroadcastForPackageDataCleared() {
+ mService = new AdServicesManagerService(mSpyContext);
+
Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
i.setData(Uri.parse("package:" + PACKAGE_NAME));
i.putExtra(Intent.EXTRA_UID, PACKAGE_UID);
@@ -115,19 +231,35 @@ public class AdServicesManagerServiceTest {
ArgumentCaptor<Intent> argumentIntent = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<UserHandle> argumentUser = ArgumentCaptor.forClass(UserHandle.class);
+ setupMockResolveInfo();
Mockito.doNothing().when(mSpyContext).sendBroadcastAsUser(Mockito.any(), Mockito.any());
+
mService.onPackageChange(i, mSpyContext.getUser());
Mockito.verify(mSpyContext, Mockito.times(1))
.sendBroadcastAsUser(argumentIntent.capture(), argumentUser.capture());
- Truth.assertThat(argumentIntent.getValue().getAction())
- .isEqualTo(PACKAGE_CHANGED_BROADCAST);
- Truth.assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData());
- Truth.assertThat(argumentIntent.getValue().getStringExtra("action"))
+ assertThat(argumentIntent.getValue().getAction()).isEqualTo(PACKAGE_CHANGED_BROADCAST);
+ assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData());
+ assertThat(argumentIntent.getValue().getStringExtra("action"))
.isEqualTo(PACKAGE_DATA_CLEARED);
- Truth.assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1))
+ assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1))
.isEqualTo(PACKAGE_UID);
- Truth.assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser());
+ assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser());
+ }
+
+ private void setupMockResolveInfo() {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = PPAPI_PACKAGE_NAME;
+ activityInfo.name = "SomeName";
+ resolveInfo.activityInfo = activityInfo;
+ ArrayList<ResolveInfo> resolveInfoList = new ArrayList<>();
+ resolveInfoList.add(resolveInfo);
+ when(mMockPackageManager.queryBroadcastReceiversAsUser(
+ any(Intent.class),
+ any(PackageManager.ResolveInfoFlags.class),
+ any(UserHandle.class)))
+ .thenReturn(resolveInfoList);
}
}
diff --git a/adservices/tests/unittest/system-service/src/com/android/server/adservices/PhFlagsTest.java b/adservices/tests/unittest/system-service/src/com/android/server/adservices/PhFlagsTest.java
new file mode 100644
index 000000000..dfd8b80b0
--- /dev/null
+++ b/adservices/tests/unittest/system-service/src/com/android/server/adservices/PhFlagsTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.adservices;
+
+import static com.android.server.adservices.Flags.ADSERVICES_SYSTEM_SERVICE_ENABLED;
+import static com.android.server.adservices.PhFlags.KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED;
+
+import android.provider.DeviceConfig;
+
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+public class PhFlagsTest {
+
+ @Rule
+ public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
+ new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @Test
+ public void testAdServicesSystemServiceEnabled() {
+ // Without any overriding, the value is the hard coded constant.
+ assertThat(FlagsFactory.getFlags().getAdServicesSystemServiceEnabled())
+ .isEqualTo(ADSERVICES_SYSTEM_SERVICE_ENABLED);
+
+ // Now overriding with the value from PH.
+ final boolean phOverridingValue = true;
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED,
+ Boolean.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ Flags phFlags = FlagsFactory.getFlags();
+ assertThat(phFlags.getAdServicesSystemServiceEnabled()).isEqualTo(phOverridingValue);
+ }
+}
diff --git a/sdksandbox/Android.bp b/sdksandbox/Android.bp
index 788f772d3..b3f357b1c 100644
--- a/sdksandbox/Android.bp
+++ b/sdksandbox/Android.bp
@@ -31,5 +31,4 @@ java_defaults {
generate_get_transaction_name: true,
},
min_sdk_version: "33",
- target_sdk_version: "33",
}
diff --git a/sdksandbox/SdkSandbox/AndroidManifest.xml b/sdksandbox/SdkSandbox/AndroidManifest.xml
index 2f247a54d..24efad1ec 100644
--- a/sdksandbox/SdkSandbox/AndroidManifest.xml
+++ b/sdksandbox/SdkSandbox/AndroidManifest.xml
@@ -23,8 +23,7 @@
android:versionName="T-initial">
<uses-sdk
- android:minSdkVersion="33"
- android:targetSdkVersion="33" />
+ android:minSdkVersion="33"/>
<!-- @hide @TestApi -->
<permission android:name="com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX"
@@ -34,11 +33,15 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- Permissions to access PP APIs. -->
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
- <application android:usesNonSdkApi="false" android:forceQueryable="true">
+ <application
+ android:usesNonSdkApi="false"
+ android:forceQueryable="true"
+ android:allowBackup="false">
<service android:name=".SdkSandboxServiceImpl" android:exported="true">
<intent-filter>
<action android:name="com.android.sdksandbox.SdkSandboxService"/>
diff --git a/sdksandbox/SdkSandbox/aidl/com/android/sdksandbox/ISdkSandboxService.aidl b/sdksandbox/SdkSandbox/aidl/com/android/sdksandbox/ISdkSandboxService.aidl
index b81cb6fce..8516f3a60 100644
--- a/sdksandbox/SdkSandbox/aidl/com/android/sdksandbox/ISdkSandboxService.aidl
+++ b/sdksandbox/SdkSandbox/aidl/com/android/sdksandbox/ISdkSandboxService.aidl
@@ -29,14 +29,14 @@ import com.android.sdksandbox.SandboxLatencyInfo;
/** @hide */
oneway interface ISdkSandboxService {
+ void initialize(in ISdkToServiceCallback sdkToService);
void isDisabled(in ISdkSandboxDisabledCallback callback);
// TODO(b/228045863): Wrap parameters in a parcelable
- void loadSdk(in String callingPackageName, IBinder sdkToken, in ApplicationInfo info,
+ void loadSdk(in String callingPackageName, in ApplicationInfo info,
in String sdkName, in String sdkProviderClassName,
in String sdkCeDataDir, in String sdkDeDataDir,
in Bundle params, in ILoadSdkInSandboxCallback callback,
- in SandboxLatencyInfo sandboxLatencyInfo,
- in ISdkToServiceCallback sdkToService);
- void unloadSdk(IBinder sdkToken, in IUnloadSdkCallback callback, in SandboxLatencyInfo sandboxLatencyInfo);
+ in SandboxLatencyInfo sandboxLatencyInfo);
+ void unloadSdk(in String sdkName, in IUnloadSdkCallback callback, in SandboxLatencyInfo sandboxLatencyInfo);
void syncDataFromClient(in SharedPreferencesUpdate update);
}
diff --git a/sdksandbox/SdkSandbox/src/com/android/sdksandbox/SandboxedSdkHolder.java b/sdksandbox/SdkSandbox/src/com/android/sdksandbox/SandboxedSdkHolder.java
index 4ff19932a..e551e9101 100644
--- a/sdksandbox/SdkSandbox/src/com/android/sdksandbox/SandboxedSdkHolder.java
+++ b/sdksandbox/SdkSandbox/src/com/android/sdksandbox/SandboxedSdkHolder.java
@@ -16,8 +16,8 @@
package com.android.sdksandbox;
-import android.app.sdksandbox.ISdkToServiceCallback;
import android.app.sdksandbox.LoadSdkException;
+import android.app.sdksandbox.LogUtil;
import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SandboxedSdkContext;
import android.app.sdksandbox.SandboxedSdkProvider;
@@ -66,8 +66,7 @@ class SandboxedSdkHolder {
ClassLoader loader,
SandboxedSdkContext sandboxedSdkContext,
SdkSandboxServiceImpl.Injector injector,
- SandboxLatencyInfo sandboxLatencyInfo,
- ISdkToServiceCallback sdkToServiceCallback) {
+ SandboxLatencyInfo sandboxLatencyInfo) {
if (mInitialized) {
throw new IllegalStateException("Already initialized!");
}
@@ -215,6 +214,8 @@ class SandboxedSdkHolder {
sandboxLatencyInfo.setTimeSandboxReceivedCallFromSystemServer(
mInjector.getCurrentTime());
+ LogUtil.d(TAG, "onSurfacePackageRequested received");
+
try {
Context displayContext = mContext.createDisplayContext(
mDisplayManager.getDisplay(displayId));
@@ -224,6 +225,7 @@ class SandboxedSdkHolder {
// Creating a SurfaceControlViewHost needs to done on the handler thread.
mHandler.post(
() -> {
+ LogUtil.d(TAG, "Creating SurfaceControlViewHost on handler thread");
final View view;
sandboxLatencyInfo.setTimeSandboxCalledSdk(mInjector.getCurrentTime());
try {
@@ -246,7 +248,9 @@ class SandboxedSdkHolder {
windowContext,
mDisplayManager.getDisplay(displayId),
token);
+ LogUtil.d(TAG, "SurfaceControlViewHost created");
host.setView(view, width, height);
+ LogUtil.d(TAG, "View from SDK set to SurfaceControlViewHost");
SurfaceControlViewHost.SurfacePackage surfacePackage =
host.getSurfacePackage();
int surfacePackageId = allocateSurfacePackageId(surfacePackage);
diff --git a/sdksandbox/SdkSandbox/src/com/android/sdksandbox/SdkSandboxServiceImpl.java b/sdksandbox/SdkSandbox/src/com/android/sdksandbox/SdkSandboxServiceImpl.java
index a03e5553b..c5142deaf 100644
--- a/sdksandbox/SdkSandbox/src/com/android/sdksandbox/SdkSandboxServiceImpl.java
+++ b/sdksandbox/SdkSandbox/src/com/android/sdksandbox/SdkSandboxServiceImpl.java
@@ -18,13 +18,15 @@ package com.android.sdksandbox;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.app.Service;
import android.app.sdksandbox.ISdkToServiceCallback;
import android.app.sdksandbox.LoadSdkException;
+import android.app.sdksandbox.LogUtil;
import android.app.sdksandbox.SandboxedSdkContext;
+import android.app.sdksandbox.SdkSandboxLocalSingleton;
import android.app.sdksandbox.SharedPreferencesKey;
import android.app.sdksandbox.SharedPreferencesUpdate;
+import android.app.sdksandbox.sdkprovider.SdkSandboxController;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -36,7 +38,6 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
-import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -46,11 +47,10 @@ import android.webkit.WebView;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import dalvik.system.DexClassLoader;
+import dalvik.system.PathClassLoader;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Objects;
@@ -59,8 +59,11 @@ public class SdkSandboxServiceImpl extends Service {
private static final String TAG = "SdkSandbox";
+ // Mapping from sdk name to its holder
@GuardedBy("mHeldSdk")
- private final Map<IBinder, SandboxedSdkHolder> mHeldSdk = new ArrayMap<>();
+ private final Map<String, SandboxedSdkHolder> mHeldSdk = new ArrayMap<>();
+
+ private volatile boolean mInitialized;
private Injector mInjector;
private ISdkSandboxService.Stub mBinder;
@@ -104,10 +107,32 @@ public class SdkSandboxServiceImpl extends Service {
mInjector = new Injector(getApplicationContext());
}
+ /**
+ * Initializes the Sandbox.
+ *
+ * <p>Sandbox should be initialized before any SDKs are loaded. This method is idempotent.
+ *
+ * @param sdkToServiceCallback for initialization of {@link SdkSandboxLocalSingleton}
+ */
+ public void initialize(ISdkToServiceCallback sdkToServiceCallback) {
+ enforceCallerIsSystemServer();
+
+ if (mInitialized) {
+ Log.e(TAG, "Sandbox is already initialized");
+ return;
+ }
+
+ SdkSandboxLocalSingleton.initInstance(sdkToServiceCallback.asBinder());
+
+ cleanUpSyncedSharedPreferencesData();
+
+ mInitialized = true;
+ LogUtil.d(TAG, "Sandbox initialized");
+ }
+
/** Loads SDK. */
public void loadSdk(
String callingPackageName,
- IBinder sdkToken,
ApplicationInfo applicationInfo,
String sdkName,
String sdkProviderClassName,
@@ -115,14 +140,12 @@ public class SdkSandboxServiceImpl extends Service {
String sdkDeDataDir,
Bundle params,
ILoadSdkInSandboxCallback callback,
- SandboxLatencyInfo sandboxLatencyInfo,
- ISdkToServiceCallback sdkToServiceCallback) {
+ SandboxLatencyInfo sandboxLatencyInfo) {
enforceCallerIsSystemServer();
final long token = Binder.clearCallingIdentity();
try {
loadSdkInternal(
callingPackageName,
- sdkToken,
applicationInfo,
sdkName,
sdkProviderClassName,
@@ -130,8 +153,7 @@ public class SdkSandboxServiceImpl extends Service {
sdkDeDataDir,
params,
callback,
- sandboxLatencyInfo,
- sdkToServiceCallback);
+ sandboxLatencyInfo);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -139,12 +161,12 @@ public class SdkSandboxServiceImpl extends Service {
/** Unloads SDK. */
public void unloadSdk(
- IBinder sdkToken, IUnloadSdkCallback callback, SandboxLatencyInfo sandboxLatencyInfo) {
+ String sdkName, IUnloadSdkCallback callback, SandboxLatencyInfo sandboxLatencyInfo) {
enforceCallerIsSystemServer();
final long token = Binder.clearCallingIdentity();
try {
sandboxLatencyInfo.setTimeSandboxCalledSdk(mInjector.getCurrentTime());
- unloadSdkInternal(sdkToken);
+ unloadSdkInternal(sdkName);
sandboxLatencyInfo.setTimeSdkCallCompleted(mInjector.getCurrentTime());
} finally {
Binder.restoreCallingIdentity(token);
@@ -157,19 +179,35 @@ public class SdkSandboxServiceImpl extends Service {
}
}
- /** Sync data from client. */
+ /** Syncs data from client. */
public void syncDataFromClient(SharedPreferencesUpdate update) {
- SharedPreferences pref =
- PreferenceManager.getDefaultSharedPreferences(mInjector.getContext());
+ enforceCallerIsSystemServer();
+
+ LogUtil.d(TAG, "Syncing data from client");
+
+ SharedPreferences pref = getClientSharedPreferences();
SharedPreferences.Editor editor = pref.edit();
final Bundle data = update.getData();
for (SharedPreferencesKey keyInUpdate : update.getKeysInUpdate()) {
updateSharedPreferences(editor, data, keyInUpdate);
}
- // TODO(b/239403323): What if writing to persistent storage fails?
editor.apply();
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ SharedPreferences getClientSharedPreferences() {
+ // TODO(b/248214708): We should retrieve synced data from a separate internal storage
+ // directory.
+ return mInjector
+ .getContext()
+ .getSharedPreferences(
+ SdkSandboxController.CLIENT_SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ }
+
+ private void cleanUpSyncedSharedPreferencesData() {
+ getClientSharedPreferences().edit().clear().apply();
+ }
+
private void updateSharedPreferences(
SharedPreferences.Editor editor, Bundle data, SharedPreferencesKey keyInUpdate) {
final String key = keyInUpdate.getName();
@@ -255,10 +293,7 @@ public class SdkSandboxServiceImpl extends Service {
}
@Override
- @RequiresPermission(android.Manifest.permission.DUMP)
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- mInjector.getContext().enforceCallingPermission(android.Manifest.permission.DUMP,
- "Can't dump " + TAG);
synchronized (mHeldSdk) {
// TODO(b/211575098): Use IndentingPrintWriter for better formatting
if (mHeldSdk.isEmpty()) {
@@ -274,6 +309,7 @@ public class SdkSandboxServiceImpl extends Service {
}
}
+ // TODO(b/249760546): Write test for enforceCallerIsSystemServer
private void enforceCallerIsSystemServer() {
if (mInjector.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException(
@@ -284,7 +320,6 @@ public class SdkSandboxServiceImpl extends Service {
private void loadSdkInternal(
@NonNull String callingPackageName,
- @NonNull IBinder sdkToken,
@NonNull ApplicationInfo applicationInfo,
@NonNull String sdkName,
@NonNull String sdkProviderClassName,
@@ -292,10 +327,9 @@ public class SdkSandboxServiceImpl extends Service {
@Nullable String sdkDeDataDir,
@NonNull Bundle params,
@NonNull ILoadSdkInSandboxCallback callback,
- @NonNull SandboxLatencyInfo sandboxLatencyInfo,
- @NonNull ISdkToServiceCallback sdkToServiceCallback) {
+ @NonNull SandboxLatencyInfo sandboxLatencyInfo) {
synchronized (mHeldSdk) {
- if (mHeldSdk.containsKey(sdkToken)) {
+ if (mHeldSdk.containsKey(sdkName)) {
sendLoadError(
callback,
ILoadSdkInSandboxCallback.LOAD_SDK_ALREADY_LOADED,
@@ -305,27 +339,8 @@ public class SdkSandboxServiceImpl extends Service {
}
}
- ClassLoader loader;
- SandboxedSdkHolder sandboxedSdkHolder;
- try {
- loader = getClassLoader(applicationInfo);
- Class<?> clz = Class.forName(SandboxedSdkHolder.class.getName(), true, loader);
- sandboxedSdkHolder = (SandboxedSdkHolder) clz.getDeclaredConstructor().newInstance();
- } catch (ClassNotFoundException | NoSuchMethodException e) {
- sendLoadError(
- callback,
- ILoadSdkInSandboxCallback.LOAD_SDK_NOT_FOUND,
- "Failed to find: " + SandboxedSdkHolder.class.getName(),
- sandboxLatencyInfo);
- return;
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
- sendLoadError(
- callback,
- ILoadSdkInSandboxCallback.LOAD_SDK_INSTANTIATION_ERROR,
- "Failed to instantiate " + SandboxedSdkHolder.class.getName() + ": " + e,
- sandboxLatencyInfo);
- return;
- }
+ ClassLoader loader = getClassLoader(applicationInfo);
+ SandboxedSdkHolder sandboxedSdkHolder = new SandboxedSdkHolder();
// We want to ensure that SandboxedSdkContext.getSystemService() will return different
// instances for different SandboxedSdkContext contexts, so that different SDKs
@@ -346,6 +361,7 @@ public class SdkSandboxServiceImpl extends Service {
SandboxedSdkContext sandboxedSdkContext =
new SandboxedSdkContext(
ctx,
+ loader,
callingPackageName,
applicationInfo,
sdkName,
@@ -358,19 +374,18 @@ public class SdkSandboxServiceImpl extends Service {
loader,
sandboxedSdkContext,
mInjector,
- sandboxLatencyInfo,
- sdkToServiceCallback);
+ sandboxLatencyInfo);
synchronized (mHeldSdk) {
- mHeldSdk.put(sdkToken, sandboxedSdkHolder);
+ mHeldSdk.put(sdkName, sandboxedSdkHolder);
}
}
- private void unloadSdkInternal(@NonNull IBinder sdkToken) {
+ private void unloadSdkInternal(@NonNull String sdkName) {
synchronized (mHeldSdk) {
- SandboxedSdkHolder sandboxedSdkHolder = mHeldSdk.get(sdkToken);
+ SandboxedSdkHolder sandboxedSdkHolder = mHeldSdk.get(sdkName);
if (sandboxedSdkHolder != null) {
sandboxedSdkHolder.unloadSdk();
- mHeldSdk.remove(sdkToken);
+ mHeldSdk.remove(sdkName);
}
}
}
@@ -390,15 +405,21 @@ public class SdkSandboxServiceImpl extends Service {
}
private ClassLoader getClassLoader(ApplicationInfo appInfo) {
- return new DexClassLoader(appInfo.sourceDir, null, null, getClass().getClassLoader());
+ final ClassLoader current = getClass().getClassLoader();
+ final ClassLoader parent = current != null ? current.getParent() : null;
+ return new PathClassLoader(appInfo.sourceDir, parent);
}
final class SdkSandboxServiceDelegate extends ISdkSandboxService.Stub {
+ @Override
+ public void initialize(@NonNull ISdkToServiceCallback sdkToServiceCallback) {
+ Objects.requireNonNull(sdkToServiceCallback, "sdkToServiceCallback should not be null");
+ SdkSandboxServiceImpl.this.initialize(sdkToServiceCallback);
+ }
@Override
public void loadSdk(
@NonNull String callingPackageName,
- @NonNull IBinder sdkToken,
@NonNull ApplicationInfo applicationInfo,
@NonNull String sdkName,
@NonNull String sdkProviderClassName,
@@ -406,26 +427,22 @@ public class SdkSandboxServiceImpl extends Service {
@Nullable String sdkDeDataDir,
@NonNull Bundle params,
@NonNull ILoadSdkInSandboxCallback callback,
- @NonNull SandboxLatencyInfo sandboxLatencyInfo,
- @NonNull ISdkToServiceCallback sdkToServiceCallback) {
+ @NonNull SandboxLatencyInfo sandboxLatencyInfo) {
sandboxLatencyInfo.setTimeSandboxReceivedCallFromSystemServer(
mInjector.getCurrentTime());
Objects.requireNonNull(callingPackageName, "callingPackageName should not be null");
- Objects.requireNonNull(sdkToken, "sdkToken should not be null");
Objects.requireNonNull(applicationInfo, "applicationInfo should not be null");
Objects.requireNonNull(sdkName, "sdkName should not be null");
Objects.requireNonNull(sdkProviderClassName, "sdkProviderClassName should not be null");
Objects.requireNonNull(params, "params should not be null");
Objects.requireNonNull(callback, "callback should not be null");
- Objects.requireNonNull(sdkToServiceCallback, "sdkToServiceCallback should not be null");
if (TextUtils.isEmpty(sdkProviderClassName)) {
throw new IllegalArgumentException("sdkProviderClassName must not be empty");
}
SdkSandboxServiceImpl.this.loadSdk(
callingPackageName,
- sdkToken,
applicationInfo,
sdkName,
sdkProviderClassName,
@@ -433,21 +450,20 @@ public class SdkSandboxServiceImpl extends Service {
sdkDeDataDir,
params,
callback,
- sandboxLatencyInfo,
- sdkToServiceCallback);
+ sandboxLatencyInfo);
}
@Override
public void unloadSdk(
- @NonNull IBinder sdkToken,
+ @NonNull String sdkName,
@NonNull IUnloadSdkCallback callback,
@NonNull SandboxLatencyInfo sandboxLatencyInfo) {
Objects.requireNonNull(sandboxLatencyInfo, "sandboxLatencyInfo should not be null");
sandboxLatencyInfo.setTimeSandboxReceivedCallFromSystemServer(
mInjector.getCurrentTime());
- Objects.requireNonNull(sdkToken, "sdkToken should not be null");
+ Objects.requireNonNull(sdkName, "sdkName should not be null");
Objects.requireNonNull(callback, "callback should not be null");
- SdkSandboxServiceImpl.this.unloadSdk(sdkToken, callback, sandboxLatencyInfo);
+ SdkSandboxServiceImpl.this.unloadSdk(sdkName, callback, sandboxLatencyInfo);
}
@Override
diff --git a/sdksandbox/TEST_MAPPING b/sdksandbox/TEST_MAPPING
index 5ec6a42fd..dd0955777 100644
--- a/sdksandbox/TEST_MAPPING
+++ b/sdksandbox/TEST_MAPPING
@@ -2,6 +2,9 @@
"mainline-presubmit": [
// Install com.google.android.adservices.apex and run the corresponding test.
{
+ "name": "CtsSdkSandboxHostSideTests[com.google.android.adservices.apex]"
+ },
+ {
"name": "CtsSdkSandboxInprocessTests[com.google.android.adservices.apex]"
},
{
@@ -35,6 +38,9 @@
},
{
"name": "SdkSandboxManagerDisabledTests[com.google.android.adservices.apex]"
+ },
+ {
+ "name": "SdkSandboxPerfScenarioTests[com.google.android.adservices.apex]"
}
],
"mainline-postsubmit": [
@@ -45,6 +51,9 @@
],
"presubmit": [
{
+ "name": "CtsSdkSandboxHostSideTests"
+ },
+ {
"name": "CtsSdkSandboxInprocessTests"
},
{
@@ -78,6 +87,9 @@
},
{
"name": "SdkSandboxManagerDisabledTests"
+ },
+ {
+ "name": "SdkSandboxPerfScenarioTests"
}
],
"postsubmit": [
diff --git a/sdksandbox/framework/api/current.txt b/sdksandbox/framework/api/current.txt
index e7da643a3..ece4793cf 100644
--- a/sdksandbox/framework/api/current.txt
+++ b/sdksandbox/framework/api/current.txt
@@ -22,6 +22,7 @@ package android.app.sdksandbox {
ctor public SandboxedSdk(@NonNull android.os.IBinder);
method public int describeContents();
method @Nullable public android.os.IBinder getInterface();
+ method @NonNull public android.content.pm.SharedLibraryInfo getSharedLibraryInfo();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.sdksandbox.SandboxedSdk> CREATOR;
}
@@ -38,6 +39,7 @@ package android.app.sdksandbox {
public final class SdkSandboxManager {
method public void addSdkSandboxProcessDeathCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.sdksandbox.SdkSandboxManager.SdkSandboxProcessDeathCallback);
method public void addSyncedSharedPreferencesKeys(@NonNull java.util.Set<java.lang.String>);
+ method @NonNull public java.util.List<android.app.sdksandbox.SandboxedSdk> getSandboxedSdks();
method public static int getSdkSandboxState();
method @NonNull public java.util.Set<java.lang.String> getSyncedSharedPreferencesKeys();
method public void loadSdk(@NonNull String, @NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.sdksandbox.SandboxedSdk,android.app.sdksandbox.LoadSdkException>);
@@ -56,6 +58,7 @@ package android.app.sdksandbox {
field public static final int LOAD_SDK_SDK_DEFINED_ERROR = 102; // 0x66
field public static final int LOAD_SDK_SDK_SANDBOX_DISABLED = 103; // 0x67
field public static final int REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR = 700; // 0x2bc
+ field public static final int REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED = 701; // 0x2bd
field public static final int SDK_SANDBOX_PROCESS_NOT_AVAILABLE = 503; // 0x1f7
field public static final String SDK_SANDBOX_SERVICE = "sdk_sandbox";
field public static final int SDK_SANDBOX_STATE_DISABLED = 0; // 0x0
@@ -68,3 +71,13 @@ package android.app.sdksandbox {
}
+package android.app.sdksandbox.sdkprovider {
+
+ public class SdkSandboxController {
+ method @NonNull public android.content.SharedPreferences getClientSharedPreferences();
+ method @NonNull public java.util.List<android.app.sdksandbox.SandboxedSdk> getSandboxedSdks();
+ field public static final String SDK_SANDBOX_CONTROLLER_SERVICE = "sdk_sandbox_controller_service";
+ }
+
+}
+
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl b/sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl
index b0ab1acb5..314ffb413 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl
+++ b/sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl
@@ -23,8 +23,8 @@ import android.app.sdksandbox.ILoadSdkCallback;
import android.app.sdksandbox.IRequestSurfacePackageCallback;
import android.app.sdksandbox.ISdkSandboxProcessDeathCallback;
import android.app.sdksandbox.ISharedPreferencesSyncCallback;
+import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SharedPreferencesUpdate;
-import android.content.pm.SharedLibraryInfo;
/** @hide */
interface ISdkSandboxManager {
@@ -40,7 +40,7 @@ interface ISdkSandboxManager {
void unloadSdk(in String callingPackageName, in String sdkName, long timeAppCalledSystemServer);
// TODO(b/242031240): wrap the many input params in one parcelable object
void requestSurfacePackage(in String callingPackageName, in String sdkName, in IBinder hostToken, int displayId, int width, int height, long timeAppCalledSystemServer, in Bundle params, IRequestSurfacePackageCallback callback);
- List<SharedLibraryInfo> getLoadedSdkLibrariesInfo(in String callingPackageName, long timeAppCalledSystemServer);
+ List<SandboxedSdk> getSandboxedSdks(in String callingPackageName, long timeAppCalledSystemServer);
void syncDataFromClient(in String callingPackageName, long timeAppCalledSystemServer, in SharedPreferencesUpdate update, in ISharedPreferencesSyncCallback callback);
void stopSdkSandbox(in String callingPackageName);
void logLatencyFromSystemServerToApp(in String method, int latency);
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/ISdkToServiceCallback.aidl b/sdksandbox/framework/java/android/app/sdksandbox/ISdkToServiceCallback.aidl
index e0f6dfb4f..48508ce18 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/ISdkToServiceCallback.aidl
+++ b/sdksandbox/framework/java/android/app/sdksandbox/ISdkToServiceCallback.aidl
@@ -16,9 +16,9 @@
package android.app.sdksandbox;
import android.os.Bundle;
-import android.content.pm.SharedLibraryInfo;
+import android.app.sdksandbox.SandboxedSdk;
/** @hide */
interface ISdkToServiceCallback {
- List<SharedLibraryInfo> getLoadedSdkLibrariesInfo(in String clientPackageName);
+ List<SandboxedSdk> getSandboxedSdks(in String clientPackageName);
}
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/LogUtil.java b/sdksandbox/framework/java/android/app/sdksandbox/LogUtil.java
new file mode 100644
index 000000000..2e414bc7c
--- /dev/null
+++ b/sdksandbox/framework/java/android/app/sdksandbox/LogUtil.java
@@ -0,0 +1,48 @@
+/*
+ * 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 android.app.sdksandbox;
+
+import android.util.Log;
+
+/**
+ * Utility class for logging behind {@link Log#isLoggable} check.
+ *
+ * <p>Logging can be enabled by running {@code adb shell setprop persist.log.tag.SDK_SANDBOX DEBUG}
+ * or individual tag used while logging with {@link LogUtil}.
+ *
+ * @hide
+ */
+public class LogUtil {
+
+ public static final String GUARD_TAG = "SDK_SANDBOX";
+
+ /** Log the message as VERBOSE. Return The number of bytes written. */
+ public static int v(String tag, String msg) {
+ if (Log.isLoggable(GUARD_TAG, Log.VERBOSE) || Log.isLoggable(tag, Log.VERBOSE)) {
+ return Log.v(tag, msg);
+ }
+ return 0;
+ }
+
+ /** Log the message as DEBUG. Return The number of bytes written. */
+ public static int d(String tag, String msg) {
+ if (Log.isLoggable(GUARD_TAG, Log.DEBUG) || Log.isLoggable(tag, Log.DEBUG)) {
+ return Log.d(tag, msg);
+ }
+ return 0;
+ }
+}
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdk.java b/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdk.java
index b0cf3fa39..8e6482c8b 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdk.java
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdk.java
@@ -16,12 +16,16 @@
package android.app.sdksandbox;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.SharedLibraryInfo;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* Represents an SDK loaded in the sandbox process.
*
@@ -35,7 +39,20 @@ import android.os.Parcelable;
* {@link SandboxedSdkProvider#beforeUnloadSdk()} has been called.
*/
public final class SandboxedSdk implements Parcelable {
+ public static final @NonNull Creator<SandboxedSdk> CREATOR =
+ new Creator<SandboxedSdk>() {
+ @Override
+ public SandboxedSdk createFromParcel(Parcel in) {
+ return new SandboxedSdk(in);
+ }
+
+ @Override
+ public SandboxedSdk[] newArray(int size) {
+ return new SandboxedSdk[size];
+ }
+ };
private IBinder mInterface;
+ private @Nullable SharedLibraryInfo mSharedLibraryInfo;
/**
* Creates a {@link SandboxedSdk} object.
@@ -51,20 +68,30 @@ public final class SandboxedSdk implements Parcelable {
private SandboxedSdk(@NonNull Parcel in) {
mInterface = in.readStrongBinder();
+ if (in.readInt() != 0) {
+ mSharedLibraryInfo = SharedLibraryInfo.CREATOR.createFromParcel(in);
+ }
}
- public static final @NonNull Creator<SandboxedSdk> CREATOR =
- new Creator<SandboxedSdk>() {
- @Override
- public SandboxedSdk createFromParcel(Parcel in) {
- return new SandboxedSdk(in);
- }
-
- @Override
- public SandboxedSdk[] newArray(int size) {
- return new SandboxedSdk[size];
- }
- };
+ /**
+ * Attaches information about the SDK like name, version and others which may be useful to
+ * identify the SDK.
+ *
+ * <p>This is used by the system service to attach the library info to the {@link SandboxedSdk}
+ * object return by the SDK after it has been loaded
+ *
+ * @param sharedLibraryInfo The SDK's library info. This contains the name, version and other
+ * details about the sdk.
+ * @throws IllegalStateException if a base sharedLibraryInfo has already been set.
+ * @hide
+ */
+ public void attachSharedLibraryInfo(@NonNull SharedLibraryInfo sharedLibraryInfo) {
+ if (mSharedLibraryInfo != null) {
+ throw new IllegalStateException("SharedLibraryInfo already set");
+ }
+ Objects.requireNonNull(sharedLibraryInfo, "SharedLibraryInfo cannot be null");
+ mSharedLibraryInfo = sharedLibraryInfo;
+ }
/**
* Returns the interface to the SDK that was loaded in response to {@link
@@ -77,6 +104,25 @@ public final class SandboxedSdk implements Parcelable {
return mInterface;
}
+ /**
+ * Returns the {@link SharedLibraryInfo} for the SDK.
+ *
+ * @throws IllegalStateException if the system service has not yet attached {@link
+ * SharedLibraryInfo} to the {@link SandboxedSdk} object sent by the SDK.
+ */
+ public @NonNull SharedLibraryInfo getSharedLibraryInfo() {
+ if (mSharedLibraryInfo == null) {
+ throw new IllegalStateException(
+ "SharedLibraryInfo has not been set. This is populated by our system service "
+ + "once the SandboxedSdk is sent back from as a response to "
+ + "android.app.sdksandbox.SandboxedSdkProvider$onLoadSdk. Please use "
+ + "android.app.sdksandbox.SdkSandboxManager#getSandboxedSdks or "
+ + "android.app.sdksandbox.SdkSandboxController#getSandboxedSdks to "
+ + "get the correctly populated SandboxedSdks.");
+ }
+ return mSharedLibraryInfo;
+ }
+
/** {@inheritDoc} */
@Override
public int describeContents() {
@@ -87,5 +133,11 @@ public final class SandboxedSdk implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mInterface);
+ if (mSharedLibraryInfo != null) {
+ dest.writeInt(1);
+ mSharedLibraryInfo.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
}
}
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkContext.java b/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkContext.java
index 80b23e2a0..f90886fdf 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkContext.java
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkContext.java
@@ -56,9 +56,11 @@ public final class SandboxedSdkContext extends ContextWrapper {
@Nullable private final File mCeDataDir;
@Nullable private final File mDeDataDir;
private final SdkSandboxSystemServiceRegistry mSdkSandboxSystemServiceRegistry;
+ private final ClassLoader mClassLoader;
public SandboxedSdkContext(
@NonNull Context baseContext,
+ @NonNull ClassLoader classLoader,
@NonNull String clientPackageName,
@NonNull ApplicationInfo info,
@NonNull String sdkName,
@@ -66,6 +68,7 @@ public final class SandboxedSdkContext extends ContextWrapper {
@Nullable String sdkDeDataDir) {
this(
baseContext,
+ classLoader,
clientPackageName,
info,
sdkName,
@@ -77,6 +80,7 @@ public final class SandboxedSdkContext extends ContextWrapper {
@VisibleForTesting
public SandboxedSdkContext(
@NonNull Context baseContext,
+ @NonNull ClassLoader classLoader,
@NonNull String clientPackageName,
@NonNull ApplicationInfo info,
@NonNull String sdkName,
@@ -105,6 +109,7 @@ public final class SandboxedSdkContext extends ContextWrapper {
mDeDataDir = (sdkDeDataDir != null) ? new File(sdkDeDataDir) : null;
mSdkSandboxSystemServiceRegistry = sdkSandboxSystemServiceRegistry;
+ mClassLoader = classLoader;
}
/**
@@ -119,6 +124,7 @@ public final class SandboxedSdkContext extends ContextWrapper {
Context newBaseContext = getBaseContext().createCredentialProtectedStorageContext();
return new SandboxedSdkContext(
newBaseContext,
+ mClassLoader,
mClientPackageName,
mSdkProviderInfo,
mSdkName,
@@ -138,6 +144,7 @@ public final class SandboxedSdkContext extends ContextWrapper {
Context newBaseContext = getBaseContext().createDeviceProtectedStorageContext();
return new SandboxedSdkContext(
newBaseContext,
+ mClassLoader,
mClientPackageName,
mSdkProviderInfo,
mSdkName,
@@ -215,4 +222,9 @@ public final class SandboxedSdkContext extends ContextWrapper {
}
return service;
}
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
}
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkProvider.java b/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkProvider.java
index e494c49f0..71c567fd4 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkProvider.java
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SandboxedSdkProvider.java
@@ -18,6 +18,7 @@ package android.app.sdksandbox;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.sdksandbox.sdkprovider.SdkSandboxController;
import android.content.Context;
import android.os.Bundle;
import android.view.SurfaceControlViewHost.SurfacePackage;
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxController.java b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxController.java
deleted file mode 100644
index e45880eed..000000000
--- a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxController.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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 android.app.sdksandbox;
-
-import static android.app.sdksandbox.SdkSandboxController.SDK_SANDBOX_CONTROLLER_SERVICE;
-
-import android.annotation.NonNull;
-import android.annotation.SystemService;
-import android.content.Context;
-
-/**
- * Controller that is used by SDK loaded in the sandbox to access information provided by the sdk
- * sandbox.
- *
- * <p>It enables the SDK to communicate with other SDKS in the SDK sandbox and know about the state
- * of the sdks that are currently loaded in it.
- *
- * <p>An instance of {@link SdkSandboxController} can be obtained using {@link
- * Context#getSystemService} and {@link SdkSandboxController class}. The {@link Context} can in turn
- * be obtained using {@link android.app.sdksandbox.SandboxedSdkProvider#getContext()}.
- *
- * @hide
- */
-@SystemService(SDK_SANDBOX_CONTROLLER_SERVICE)
-public class SdkSandboxController {
- public static final String SDK_SANDBOX_CONTROLLER_SERVICE = "sdk_sandbox_controller_service";
-
- private Context mContext;
-
- /** Create SdkSandboxController.* */
- public SdkSandboxController(@NonNull Context context) {
-
- // When SdkSandboxController is initiated from inside the sdk sandbox process, its private
- // members will be immediately rewritten by the initialize method.
- initialize(context);
- }
-
- /**
- * Initializes {@link SdkSandboxController} with the given {@code context}.
- *
- * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
- * For more information check the javadoc on the {@link
- * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
- *
- * @hide
- * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
- */
- public SdkSandboxController initialize(@NonNull Context context) {
- mContext = context;
- return this;
- }
-}
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxLocalSingleton.java b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxLocalSingleton.java
new file mode 100644
index 000000000..09600c4d0
--- /dev/null
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxLocalSingleton.java
@@ -0,0 +1,92 @@
+/*
+ * 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 android.app.sdksandbox;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Singleton for a privacy sandbox, which is initialised when the sandbox is created.
+ *
+ * @hide
+ */
+public class SdkSandboxLocalSingleton {
+
+ private static final String TAG = "SandboxLocalSingleton";
+ private static SdkSandboxLocalSingleton sInstance = null;
+ private final ISdkToServiceCallback mSdkToServiceCallback;
+
+ private SdkSandboxLocalSingleton(ISdkToServiceCallback sdkToServiceCallback) {
+ mSdkToServiceCallback = sdkToServiceCallback;
+ }
+
+ /**
+ * Returns a singleton instance of this class. TODO(b/247313241): Fix parameter once aidl issues
+ * are fixed.
+ *
+ * @param sdkToServiceBinder callback to support communication with the {@link
+ * com.android.server.sdksandbox.SdkSandboxManagerService}
+ * @throws IllegalStateException if singleton is already initialised
+ * @throws UnsupportedOperationException if the interface passed is not of type {@link
+ * ISdkToServiceCallback}
+ */
+ public static synchronized void initInstance(@NonNull IBinder sdkToServiceBinder) {
+ if (sInstance != null) {
+ Log.d(TAG, "Already Initialised");
+ return;
+ }
+ try {
+ if (Objects.nonNull(sdkToServiceBinder)
+ && sdkToServiceBinder
+ .getInterfaceDescriptor()
+ .equals(ISdkToServiceCallback.DESCRIPTOR)) {
+ sInstance =
+ new SdkSandboxLocalSingleton(
+ ISdkToServiceCallback.Stub.asInterface(sdkToServiceBinder));
+ return;
+ }
+ } catch (RemoteException e) {
+ // Fall through to the failure case.
+ }
+ throw new UnsupportedOperationException("IBinder not supported");
+ }
+
+ /** Returns an already initialised singleton instance of this class. */
+ public static SdkSandboxLocalSingleton getExistingInstance() {
+ if (sInstance == null) {
+ throw new IllegalStateException("SdkSandboxLocalSingleton not found");
+ }
+ return sInstance;
+ }
+
+ /** To reset the singleton. Only for Testing. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static void destroySingleton() {
+ sInstance = null;
+ }
+
+ /** Gets the callback to the {@link com.android.server.sdksandbox.SdkSandboxManagerService} */
+ public ISdkToServiceCallback getSdkToServiceCallback() {
+ return mSdkToServiceCallback;
+ }
+}
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java
index cb37402a1..a90796773 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java
@@ -24,9 +24,9 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.sdksandbox.sdkprovider.SdkSandboxController;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.SharedLibraryInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.OutcomeReceiver;
@@ -141,12 +141,24 @@ public final class SdkSandboxManager {
*/
public static final int REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR = 700;
+ /**
+ * SDK is not loaded while requesting a {@link SurfacePackage}.
+ *
+ * <p>This indicates that the SDK for which the {@link SurfacePackage} is being requested is not
+ * loaded, either because the sandbox died or because it was not loaded in the first place.
+ */
+ public static final int REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED = 701;
+
/** @hide */
- @IntDef(value = {REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR, SDK_SANDBOX_PROCESS_NOT_AVAILABLE})
+ @IntDef(
+ prefix = "REQUEST_SURFACE_PACKAGE_",
+ value = {
+ REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR,
+ REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface RequestSurfacePackageErrorCode {}
-
/**
* SDK Sandbox is disabled.
*
@@ -360,12 +372,11 @@ public final class SdkSandboxManager {
/**
* Fetches information about Sdks that are loaded in the sandbox.
*
- * @return List of {@link SharedLibraryInfo} containing all currently loaded sdks
- * @hide
+ * @return List of {@link SandboxedSdk} containing all currently loaded sdks
*/
- public @NonNull List<SharedLibraryInfo> getLoadedSdkLibrariesInfo() {
+ public @NonNull List<SandboxedSdk> getSandboxedSdks() {
try {
- return mService.getLoadedSdkLibrariesInfo(
+ return mService.getSandboxedSdks(
mContext.getPackageName(),
/*timeAppCalledSystemServer=*/ System.currentTimeMillis());
} catch (RemoteException e) {
@@ -530,13 +541,12 @@ public final class SdkSandboxManager {
}
}
- // TODO(b/237410689): Update links to getClientSharedPreferences when cl is merged.
/**
* Adds keys to set of keys being synced from app's default {@link SharedPreferences} to
* SdkSandbox.
*
- * <p>Synced data will be available for sdks to read using the {@code
- * getClientSharedPreferences} API.
+ * <p>Synced data will be available for sdks to read using the {@link
+ * SdkSandboxController#getClientSharedPreferences()} API.
*
* <p>To stop syncing any key that has been added using this API, use {@link
* #removeSyncedSharedPreferencesKeys(Set)}.
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManagerFrameworkInitializer.java b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManagerFrameworkInitializer.java
index f1ce6af1d..37d44adda 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManagerFrameworkInitializer.java
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManagerFrameworkInitializer.java
@@ -16,11 +16,12 @@
package android.app.sdksandbox;
-import static android.app.sdksandbox.SdkSandboxController.SDK_SANDBOX_CONTROLLER_SERVICE;
import static android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_SERVICE;
+import static android.app.sdksandbox.sdkprovider.SdkSandboxController.SDK_SANDBOX_CONTROLLER_SERVICE;
import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
+import android.app.sdksandbox.sdkprovider.SdkSandboxController;
import android.content.Context;
/**
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java b/sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java
index 44bba284d..5e0c38378 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java
@@ -23,6 +23,7 @@ import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.RemoteException;
import android.preference.PreferenceManager;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -45,9 +46,7 @@ import java.util.Set;
public class SharedPreferencesSyncManager {
private static final String TAG = "SdkSandboxSyncManager";
-
- private static SharedPreferencesSyncManager sInstance = null;
-
+ private static ArrayMap<String, SharedPreferencesSyncManager> sInstanceMap = new ArrayMap<>();
private final ISdkSandboxManager mService;
private final Context mContext;
private final Object mLock = new Object();
@@ -71,13 +70,17 @@ public class SharedPreferencesSyncManager {
mService = service;
}
- /** Returns a singleton instance of this class. */
+ /**
+ * Returns a new instance of this class if there is a new package, otherewise returns a
+ * singleton instance.
+ */
public static synchronized SharedPreferencesSyncManager getInstance(
@NonNull Context context, @NonNull ISdkSandboxManager service) {
- if (sInstance == null) {
- sInstance = new SharedPreferencesSyncManager(context, service);
+ final String packageName = context.getPackageName();
+ if (!sInstanceMap.containsKey(packageName)) {
+ sInstanceMap.put(packageName, new SharedPreferencesSyncManager(context, service));
}
- return sInstance;
+ return sInstanceMap.get(packageName);
}
/**
@@ -107,7 +110,24 @@ public class SharedPreferencesSyncManager {
public void removeSharedPreferencesSyncKeys(@NonNull Set<String> keys) {
synchronized (mLock) {
mKeysToSync.removeAll(keys);
- // TODO(b/19742283): removed keys need to be erased from sandbox.
+
+ final ArrayList<SharedPreferencesKey> keysWithTypeBeingRemoved = new ArrayList<>();
+
+ for (final String key : keys) {
+ keysWithTypeBeingRemoved.add(
+ new SharedPreferencesKey(key, SharedPreferencesKey.KEY_TYPE_STRING));
+ }
+ final SharedPreferencesUpdate update =
+ new SharedPreferencesUpdate(keysWithTypeBeingRemoved, new Bundle());
+ try {
+ mService.syncDataFromClient(
+ mContext.getPackageName(),
+ /*timeAppCalledSystemServer=*/ System.currentTimeMillis(),
+ update,
+ mCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't connect to SdkSandboxManagerService: " + e.getMessage());
+ }
}
}
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/sdkprovider/SdkSandboxController.java b/sdksandbox/framework/java/android/app/sdksandbox/sdkprovider/SdkSandboxController.java
new file mode 100644
index 000000000..ed97f625a
--- /dev/null
+++ b/sdksandbox/framework/java/android/app/sdksandbox/sdkprovider/SdkSandboxController.java
@@ -0,0 +1,130 @@
+/*
+ * 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 android.app.sdksandbox.sdkprovider;
+
+import static android.app.sdksandbox.sdkprovider.SdkSandboxController.SDK_SANDBOX_CONTROLLER_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.app.sdksandbox.SdkSandboxLocalSingleton;
+import android.app.sdksandbox.SdkSandboxManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Controller that is used by SDK loaded in the sandbox to access information provided by the sdk
+ * sandbox.
+ *
+ * <p>It enables the SDK to communicate with other SDKS in the SDK sandbox and know about the state
+ * of the sdks that are currently loaded in it.
+ *
+ * <p>An instance of {@link SdkSandboxController} can be obtained using {@link
+ * Context#getSystemService} and {@link SdkSandboxController class}. The {@link Context} can in turn
+ * be obtained using {@link android.app.sdksandbox.SandboxedSdkProvider#getContext()}.
+ */
+@SystemService(SDK_SANDBOX_CONTROLLER_SERVICE)
+public class SdkSandboxController {
+ public static final String SDK_SANDBOX_CONTROLLER_SERVICE = "sdk_sandbox_controller_service";
+ /** @hide */
+ public static final String CLIENT_SHARED_PREFERENCES_NAME =
+ "com.android.sdksandbox.client_sharedpreferences";
+
+ private static final String TAG = "SdkSandboxController";
+
+ private SdkSandboxLocalSingleton mSdkSandboxLocalSingleton;
+ private Context mContext;
+
+ /**
+ * Create SdkSandboxController.
+ *
+ * @hide
+ */
+ public SdkSandboxController(@NonNull Context context) {
+ // When SdkSandboxController is initiated from inside the sdk sandbox process, its private
+ // members will be immediately rewritten by the initialize method.
+ initialize(context);
+ }
+
+ /**
+ * Initializes {@link SdkSandboxController} with the given {@code context}.
+ *
+ * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
+ * For more information check the javadoc on the {@link
+ * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
+ *
+ * @hide
+ * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
+ */
+ public SdkSandboxController initialize(@NonNull Context context) {
+ mContext = context;
+ mSdkSandboxLocalSingleton = SdkSandboxLocalSingleton.getExistingInstance();
+ return this;
+ }
+
+ /**
+ * Fetches information about Sdks that are loaded in the sandbox.
+ *
+ * @return List of {@link SandboxedSdk} containing all currently loaded sdks
+ * @throws UnsupportedOperationException if the controller is obtained from an unexpected
+ * context. Use {@link SandboxedSdkProvider#getContext()} for the right context
+ */
+ public @NonNull List<SandboxedSdk> getSandboxedSdks() {
+ enforceSandboxedSdkContextInitialization();
+ try {
+ return mSdkSandboxLocalSingleton
+ .getSdkToServiceCallback()
+ .getSandboxedSdks(((SandboxedSdkContext) mContext).getClientPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@link SharedPreferences} containing data synced from the client app.
+ *
+ * <p>Keys that have been synced by the client app using {@link
+ * SdkSandboxManager#addSyncedSharedPreferencesKeys(Set)} can be found in this {@link
+ * SharedPreferences}.
+ *
+ * <p>The returned {@link SharedPreferences} should only be read. Writing to it is not
+ * supported.
+ *
+ * @return {@link SharedPreferences} containing data synced from client app.
+ * @throws UnsupportedOperationException if the controller is obtained from an unexpected
+ * context. Use {@link SandboxedSdkProvider#getContext()} for the right context
+ */
+ @NonNull
+ public SharedPreferences getClientSharedPreferences() {
+ enforceSandboxedSdkContextInitialization();
+
+ // TODO(b/248214708): We should store synced data in a separate internal storage directory.
+ return mContext.getSharedPreferences(CLIENT_SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ }
+
+ private void enforceSandboxedSdkContextInitialization() {
+ if (!(mContext instanceof SandboxedSdkContext)) {
+ throw new UnsupportedOperationException(
+ "Only available from the context obtained by calling android.app.sdksandbox"
+ + ".SandboxedSdkProvider#getContext()");
+ }
+ }
+}
diff --git a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
index 7ea1b51b4..0f221d3f1 100644
--- a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
+++ b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
@@ -19,6 +19,7 @@ package com.android.server.sdksandbox;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_SDK_SANDBOX_DISABLED;
+import static android.app.sdksandbox.SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED;
import static android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_PROCESS_NOT_AVAILABLE;
import static android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_SERVICE;
@@ -27,7 +28,7 @@ import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_AP
import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED;
import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX;
import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP;
-import static com.android.server.sdksandbox.SdkSandboxStorageManager.SdkDataDirInfo;
+import static com.android.server.sdksandbox.SdkSandboxStorageManager.StorageDirInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,9 +41,11 @@ import android.app.sdksandbox.ISdkSandboxProcessDeathCallback;
import android.app.sdksandbox.ISdkToServiceCallback;
import android.app.sdksandbox.ISharedPreferencesSyncCallback;
import android.app.sdksandbox.LoadSdkException;
+import android.app.sdksandbox.LogUtil;
import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SdkSandboxManager;
import android.app.sdksandbox.SharedPreferencesUpdate;
+import android.app.sdksandbox.sdkprovider.SdkSandboxController;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -91,14 +94,11 @@ import com.android.server.pm.PackageManagerLocal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import javax.annotation.concurrent.ThreadSafe;
/**
* Implementation of {@link SdkSandboxManager}.
@@ -117,7 +117,6 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private static final String SANDBOX_DISABLED_MSG = "SDK sandbox is disabled";
private final Context mContext;
- private final SdkTokenManager mSdkTokenManager = new SdkTokenManager();
private final ActivityManager mActivityManager;
private final Handler mHandler;
@@ -126,12 +125,18 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private final Object mLock = new Object();
- // For communication between app<-ManagerService->RemoteCode for each codeToken
+ // For communication between app<-ManagerService->RemoteSdk for each {CallingInfo, SDK name}
@GuardedBy("mLock")
- private final ArrayMap<IBinder, AppAndRemoteSdkLink> mAppAndRemoteSdkLinks = new ArrayMap<>();
+ private final ArrayMap<Pair<CallingInfo, String>, AppAndRemoteSdkLink> mAppAndRemoteSdkLinks =
+ new ArrayMap<>();
+
+ // Denotes which SDKs are currently in the process of being loaded
+ @GuardedBy("mLock")
+ private final ArraySet<Pair<CallingInfo, String>> mSdksBeingLoaded = new ArraySet<>();
@GuardedBy("mLock")
- private final ArraySet<CallingInfo> mCallingInfosWithDeathRecipients = new ArraySet<>();
+ private final ArrayMap<CallingInfo, IBinder> mCallingInfosWithDeathRecipients =
+ new ArrayMap<>();
@GuardedBy("mLock")
private final Set<CallingInfo> mRunningInstrumentations = new ArraySet<>();
@@ -233,37 +238,41 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
}
@Override
- public List<SharedLibraryInfo> getLoadedSdkLibrariesInfo(
+ public List<SandboxedSdk> getSandboxedSdks(
String callingPackageName, long timeAppCalledSystemServer) {
final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime();
+ final int callingUid = Binder.getCallingUid();
+ final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
+ enforceCallingPackageBelongsToUid(callingInfo);
+
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__GET_LOADED_SDK_LIBRARIES_INFO,
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__GET_SANDBOXED_SDKS,
/*latency=*/ (int)
(timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER);
- final int callingUid = Binder.getCallingUid();
- final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
- enforceCallingPackageBelongsToUid(callingInfo);
- List<SharedLibraryInfo> sharedLibraryInfos = new ArrayList<>();
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ callingUid);
+
+ final List<SandboxedSdk> sandboxedSdks = new ArrayList<>();
synchronized (mLock) {
for (int i = mAppAndRemoteSdkLinks.size() - 1; i >= 0; i--) {
AppAndRemoteSdkLink link = mAppAndRemoteSdkLinks.valueAt(i);
- if (link.mCallingInfo.equals(callingInfo) && link.mSdkProviderInfo != null) {
- sharedLibraryInfos.add(link.mSdkProviderInfo.mSdkInfo);
+ if (link.mCallingInfo.equals(callingInfo) && link.mSandboxedSdk != null) {
+ sandboxedSdks.add(link.mSandboxedSdk);
}
}
}
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__GET_LOADED_SDK_LIBRARIES_INFO,
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__GET_SANDBOXED_SDKS,
/*latency=*/ (int)
(mInjector.getCurrentTime() - timeSystemServerReceivedCallFromApp),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX);
- return sharedLibraryInfos;
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ callingUid);
+ return sandboxedSdks;
}
@Override
@@ -272,16 +281,19 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
long timeAppCalledSystemServer,
ISdkSandboxProcessDeathCallback callback) {
final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime();
+
+ final int callingUid = Binder.getCallingUid();
+ final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
+ enforceCallingPackageBelongsToUid(callingInfo);
+
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__ADD_SDK_SANDBOX_LIFECYCLE_CALLBACK,
/*latency=*/ (int)
(timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER);
- final int callingUid = Binder.getCallingUid();
- final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
- enforceCallingPackageBelongsToUid(callingInfo);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ callingUid);
synchronized (mLock) {
if (mSandboxLifecycleCallbacks.containsKey(callingInfo)) {
@@ -299,7 +311,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
/*latency=*/ (int)
(mInjector.getCurrentTime() - timeSystemServerReceivedCallFromApp),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX);
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ callingUid);
}
@Override
@@ -308,6 +321,11 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
long timeAppCalledSystemServer,
ISdkSandboxProcessDeathCallback callback) {
final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime();
+
+ final int callingUid = Binder.getCallingUid();
+ final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
+ enforceCallingPackageBelongsToUid(callingInfo);
+
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
SdkSandboxStatsLog
@@ -315,10 +333,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
/*latency=*/ (int)
(timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER);
- final int callingUid = Binder.getCallingUid();
- final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
- enforceCallingPackageBelongsToUid(callingInfo);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ callingUid);
synchronized (mLock) {
RemoteCallbackList<ISdkSandboxProcessDeathCallback> sandboxLifecycleCallbacks =
@@ -334,7 +350,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
/*latency=*/ (int)
(mInjector.getCurrentTime() - timeSystemServerReceivedCallFromApp),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX);
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ callingUid);
}
@Override
@@ -346,25 +363,27 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
ILoadSdkCallback callback) {
// Log the IPC latency from app to system server
final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime();
- SdkSandboxStatsLog.write(
- SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
- /*latency=*/ (int)
- (timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer),
- /*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER);
final int callingUid = Binder.getCallingUid();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
+ enforceCallingPackageBelongsToUid(callingInfo);
+ enforceCallerHasNetworkAccess(callingPackageName);
+ enforceCallerRunsInForeground(callingInfo);
synchronized (mLock) {
if (mRunningInstrumentations.contains(callingInfo)) {
throw new SecurityException(
"Currently running instrumentation of this sdk sandbox process");
}
}
- enforceCallingPackageBelongsToUid(callingInfo);
- enforceCallerHasNetworkAccess(callingPackageName);
- enforceCallerRunsInForeground(callingInfo);
+
+ SdkSandboxStatsLog.write(
+ SdkSandboxStatsLog.SANDBOX_API_CALLED,
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
+ /*latency=*/ (int)
+ (timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer),
+ /*success=*/ true,
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ callingUid);
// TODO(b/232924025): Sdk data should be prepared once per sandbox instantiation
mSdkSandboxStorageManager.prepareSdkDataOnLoad(callingInfo);
@@ -383,10 +402,7 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
Bundle params,
ILoadSdkCallback callback,
long timeSystemServerReceivedCallFromApp) {
- // Step 1: create unique identity for the {callingUid, sdkName} pair
- final IBinder sdkToken = mSdkTokenManager.createOrGetSdkToken(callingInfo, sdkName);
-
- // Step 2: fetch the installed code in device
+ // Fetch the installed SDK in device
SdkProviderInfo sdkProviderInfo =
createSdkProviderInfo(sdkName, callingInfo.getPackageName());
@@ -397,40 +413,54 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
errorMsg = sdkName + " did not set " + PROPERTY_SDK_PROVIDER_CLASS_NAME;
}
- // Ensure we are not already loading sdk for this sdkToken. That's determined by
- // checking if we already have an AppAndRemoteCodeLink for the sdkToken.
final AppAndRemoteSdkLink link =
- new AppAndRemoteSdkLink(callingInfo, sdkToken, callback, sdkProviderInfo);
+ new AppAndRemoteSdkLink(callingInfo, sdkName, callback, sdkProviderInfo);
+ if (!TextUtils.isEmpty(errorMsg)) {
+ Log.w(TAG, errorMsg);
+ link.handleLoadSdkException(
+ new LoadSdkException(SdkSandboxManager.LOAD_SDK_NOT_FOUND, errorMsg),
+ /*startTimeOfErrorStage=*/ timeSystemServerReceivedCallFromApp,
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ /*successAtStage=*/ false);
+ return;
+ }
+
+ // Ensure we are not already loading this sdk. That's determined by checking if we already
+ // have an AppAndRemoteSdkLink for the calling info and sdk name.
synchronized (mLock) {
- if (mAppAndRemoteSdkLinks.putIfAbsent(sdkToken, link) != null) {
+ final Pair<CallingInfo, String> appAndSdkInfo = Pair.create(callingInfo, sdkName);
+ if (mAppAndRemoteSdkLinks.containsKey(appAndSdkInfo)) {
link.handleLoadSdkException(
new LoadSdkException(
SdkSandboxManager.LOAD_SDK_ALREADY_LOADED,
- sdkName + " is being loaded or has been loaded already"),
- /*cleanUpInternalState=*/ false,
+ sdkName + " has been loaded already"),
/*startTimeOfErrorStage=*/ timeSystemServerReceivedCallFromApp,
SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
/*successAtStage=*/ false);
return;
}
- }
- if (!TextUtils.isEmpty(errorMsg)) {
- Log.w(TAG, errorMsg);
- link.handleLoadSdkException(
- new LoadSdkException(SdkSandboxManager.LOAD_SDK_NOT_FOUND, errorMsg),
- /*cleanUpInternalState=*/ true,
- /*startTimeOfErrorStage=*/ timeSystemServerReceivedCallFromApp,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
- /*successAtStage=*/ false);
- return;
+ if (mSdksBeingLoaded.contains(appAndSdkInfo)) {
+ link.handleLoadSdkException(
+ new LoadSdkException(
+ SdkSandboxManager.LOAD_SDK_ALREADY_LOADED,
+ sdkName + " is currently being loaded"),
+ /*startTimeOfErrorStage=*/ timeSystemServerReceivedCallFromApp,
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ /*successAtStage=*/ false);
+ return;
+ } else {
+ mSdksBeingLoaded.add(appAndSdkInfo);
+ }
}
- // Register a death recipient to clean up sdkToken and unbind its service after app dies.
+ // Register a death recipient to clean up app related state and unbind its service after
+ // the app dies.
try {
synchronized (mLock) {
- if (!mCallingInfosWithDeathRecipients.contains(callingInfo)) {
+ if (!mCallingInfosWithDeathRecipients.containsKey(callingInfo)) {
+ Log.d(TAG, "Registering " + callingInfo + " for death notification");
callback.asBinder().linkToDeath(() -> onAppDeath(callingInfo), 0);
- mCallingInfosWithDeathRecipients.add(callingInfo);
+ mCallingInfosWithDeathRecipients.put(callingInfo, callback.asBinder());
mUidImportanceListener.startListening();
}
}
@@ -442,22 +472,16 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
/*latency=*/ (int)
(mInjector.getCurrentTime() - timeSystemServerReceivedCallFromApp),
/*success=*/ false,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX);
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ callingInfo.getUid());
- // App has already died, cleanup sdk token and link, and unbind its service
+ // App has already died, cleanup sdk link, and unbind its service
onAppDeath(callingInfo);
return;
}
- final SdkToServiceLink sdkToServiceLink = new SdkToServiceLink();
invokeSdkSandboxServiceToLoadSdk(
- callingInfo,
- sdkToken,
- sdkProviderInfo,
- params,
- link,
- timeSystemServerReceivedCallFromApp,
- sdkToServiceLink);
+ callingInfo, sdkProviderInfo, params, link, timeSystemServerReceivedCallFromApp);
}
@Override
@@ -465,30 +489,39 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
String callingPackageName, String sdkName, long timeAppCalledSystemServer) {
final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime();
+ final int callingUid = Binder.getCallingUid();
+ final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
+ enforceCallingPackageBelongsToUid(callingInfo);
+ enforceCallerRunsInForeground(callingInfo);
+
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
SANDBOX_API_CALLED__METHOD__UNLOAD_SDK,
/*latency=*/ (int)
(timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER);
- final CallingInfo callingInfo = new CallingInfo(Binder.getCallingUid(), callingPackageName);
- enforceCallingPackageBelongsToUid(callingInfo);
- enforceCallerRunsInForeground(callingInfo);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ callingUid);
final long token = Binder.clearCallingIdentity();
try {
- IBinder sdkToken = mSdkTokenManager.getSdkToken(callingInfo, sdkName);
- if (sdkToken == null) {
- if (!isSdkSandboxServiceRunning(callingInfo)) {
- // If the sandbox has died and unloadSdk is called after,
- // the app should not crash from an uncaught exception.
- Log.i(TAG, "Sdk sandbox for " + callingInfo
- + " is not available, cannot unload SDK " + sdkName);
+ final Pair<CallingInfo, String> appAndSdkInfo = Pair.create(callingInfo, sdkName);
+ synchronized (mLock) {
+ // TODO(b/254657226): Add a callback or return value for unloadSdk() to indicate
+ // success of unload.
+ if (mSdksBeingLoaded.contains(appAndSdkInfo)) {
+ throw new IllegalArgumentException(
+ "SDK "
+ + sdkName
+ + " is currently being loaded for "
+ + callingInfo
+ + " - wait till onLoadSdkSuccess() to unload");
+ }
+ if (mAppAndRemoteSdkLinks.get(appAndSdkInfo) == null) {
+ // Unloading SDK that is not loaded is a no-op, return.
+ Log.i(TAG, "SDK " + sdkName + " is not loaded for " + callingInfo);
return;
}
- throw new IllegalArgumentException(
- "SDK " + sdkName + " is not loaded for " + callingInfo);
}
unloadSdkWithClearIdentity(callingInfo, sdkName, timeSystemServerReceivedCallFromApp);
} finally {
@@ -511,7 +544,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
(mInjector.getCurrentTime()
- response.getTimeSystemServerReceivedCallFromSandbox()),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP,
+ callingInfo.getUid());
}
private void enforceCallingPackageBelongsToUid(CallingInfo callingInfo) {
@@ -552,6 +586,7 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private void onAppDeath(CallingInfo callingInfo) {
synchronized (mLock) {
+ Log.d(TAG, "App " + callingInfo + " has died, cleaning up associated sandbox info");
mSandboxLifecycleCallbacks.remove(callingInfo);
mPendingCallbacks.remove(callingInfo);
mCallingInfosWithDeathRecipients.remove(callingInfo);
@@ -559,7 +594,7 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
mUidImportanceListener.stopListening();
}
mSyncDataCallbacks.remove(callingInfo);
- removeAllSdkTokensAndLinks(callingInfo);
+ removeAllSdkLinks(callingInfo);
stopSdkSandboxService(callingInfo, "Caller " + callingInfo + " has died");
}
}
@@ -577,40 +612,29 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
IRequestSurfacePackageCallback callback) {
final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime();
+ LogUtil.d(
+ TAG,
+ "requestSurfacePackage call received. callingPackageName: " + callingPackageName);
+
+ final int callingUid = Binder.getCallingUid();
+ final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
+ enforceCallingPackageBelongsToUid(callingInfo);
+ enforceCallerRunsInForeground(callingInfo);
+
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
/*latency=*/ (int)
(timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ callingUid);
- final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
-
- final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
- enforceCallingPackageBelongsToUid(callingInfo);
- enforceCallerRunsInForeground(callingInfo);
try {
- final IBinder sdkToken = mSdkTokenManager.getSdkToken(callingInfo, sdkName);
- if (sdkToken == null) {
- if (!isSdkSandboxServiceRunning(callingInfo)) {
- handleSurfacePackageError(
- callingInfo,
- SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
- SANDBOX_NOT_AVAILABLE_MSG,
- timeSystemServerReceivedCallFromApp,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
- /*successAtStage*/ false,
- callback);
- return;
- }
- throw new IllegalArgumentException("Sdk " + sdkName + " is not loaded");
- }
requestSurfacePackageWithClearIdentity(
callingInfo,
sdkName,
- sdkToken,
hostToken,
displayId,
width,
@@ -626,7 +650,6 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private void requestSurfacePackageWithClearIdentity(
CallingInfo callingInfo,
String sdkName,
- IBinder sdkToken,
IBinder hostToken,
int displayId,
int width,
@@ -634,24 +657,26 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
long timeSystemServerReceivedCallFromApp,
Bundle params,
IRequestSurfacePackageCallback callback) {
+ final Pair<CallingInfo, String> appAndSdkInfo = Pair.create(callingInfo, sdkName);
final AppAndRemoteSdkLink link;
synchronized (mLock) {
- link = mAppAndRemoteSdkLinks.get(sdkToken);
- if (link == null) {
- if (!isSdkSandboxServiceRunning(callingInfo)) {
- handleSurfacePackageError(
- callingInfo,
- SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
- SANDBOX_NOT_AVAILABLE_MSG,
- timeSystemServerReceivedCallFromApp,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
- /*successAtStage=*/ false,
- callback);
- return;
- }
- throw new SecurityException("Sdk " + sdkName + " has not been loaded correctly");
- }
+ link = mAppAndRemoteSdkLinks.get(appAndSdkInfo);
+ }
+ if (link == null) {
+ LogUtil.d(
+ TAG,
+ callingInfo + " requested surface package, but could not find SDK " + sdkName);
+ handleSurfacePackageError(
+ callingInfo,
+ REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED,
+ "SDK " + sdkName + " is not loaded",
+ timeSystemServerReceivedCallFromApp,
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ /*successAtStage*/ false,
+ callback);
+ return;
}
+
link.requestSurfacePackageFromSdk(
hostToken,
displayId,
@@ -684,12 +709,13 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
writer.println(
"Killswitch enabled: " + mSdkSandboxSettingsListener.isKillSwitchEnabled());
writer.println("mAppAndRemoteSdkLinks size: " + mAppAndRemoteSdkLinks.size());
+ for (Pair<CallingInfo, String> pair : mAppAndRemoteSdkLinks.keySet()) {
+ writer.printf("caller: %s, sdkName: %s", pair.first, pair.second);
+ writer.println();
+ }
+ writer.println();
}
- writer.println("mSdkTokenManager:");
- mSdkTokenManager.dump(writer);
- writer.println();
-
writer.println("mServiceProvider:");
mServiceProvider.dump(writer);
writer.println();
@@ -703,19 +729,20 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
ISharedPreferencesSyncCallback callback) {
final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime();
+ final int callingUid = Binder.getCallingUid();
+ final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
+ enforceCallingPackageBelongsToUid(callingInfo);
+
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__SYNC_DATA_FROM_CLIENT,
/*latency=*/ (int)
(timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ callingUid);
- final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
-
- final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
- enforceCallingPackageBelongsToUid(callingInfo);
try {
syncDataFromClientInternal(callingInfo, update, callback);
} finally {
@@ -761,7 +788,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
convertToStatsLogMethodCode(method),
latency,
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_APP);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_APP,
+ Binder.getCallingUid());
}
private int convertToStatsLogMethodCode(String method) {
@@ -810,10 +838,10 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
mServiceProvider.setBoundServiceForApp(mCallingInfo, mService);
try {
- service.linkToDeath(() -> removeAllSdkTokensAndLinks(mCallingInfo), 0);
+ service.linkToDeath(() -> removeAllSdkLinks(mCallingInfo), 0);
} catch (RemoteException re) {
- // Sandbox had already died, cleanup sdk tokens and links.
- removeAllSdkTokensAndLinks(mCallingInfo);
+ // Sandbox had already died, cleanup sdk links.
+ removeAllSdkLinks(mCallingInfo);
}
if (!mServiceBound) {
@@ -827,11 +855,13 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
// Sdk sandbox crashed or killed, system will start it again.
// TODO(b/204991850): Handle restarts differently
// (e.g. Exponential backoff retry strategy)
+ Log.d(TAG, "Sandbox service for " + mCallingInfo + " has been disconnected");
mServiceProvider.setBoundServiceForApp(mCallingInfo, null);
}
@Override
public void onBindingDied(ComponentName name) {
+ Log.d(TAG, "Sandbox service failed to bind for " + mCallingInfo + " : died on binding");
mServiceProvider.setBoundServiceForApp(mCallingInfo, null);
mServiceProvider.unbindService(mCallingInfo, true);
mServiceProvider.bindService(mCallingInfo, this);
@@ -839,6 +869,7 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
@Override
public void onNullBinding(ComponentName name) {
+ Log.d(TAG, "Sandbox service failed to bind for " + mCallingInfo + " : service is null");
mCallback.onBindingFailed();
}
}
@@ -850,25 +881,21 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private void invokeSdkSandboxServiceToLoadSdk(
CallingInfo callingInfo,
- IBinder sdkToken,
SdkProviderInfo info,
Bundle params,
AppAndRemoteSdkLink link,
- long timeSystemServerReceivedCallFromApp,
- SdkToServiceLink sdkToServiceLink) {
+ long timeSystemServerReceivedCallFromApp) {
// check first if service already bound
ISdkSandboxService service = mServiceProvider.getBoundServiceForApp(callingInfo);
if (service != null) {
loadSdkForService(
callingInfo,
- sdkToken,
info,
params,
link,
/*timeToLoadSandbox=*/ -1,
timeSystemServerReceivedCallFromApp,
- service,
- sdkToServiceLink);
+ service);
return;
}
@@ -879,24 +906,16 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
@Override
public void onBindingSuccessful(ISdkSandboxService service) {
try {
- service.asBinder()
- .linkToDeath(
- () -> {
- notifyPendingCallbacks(callingInfo);
- handleSandboxLifecycleCallbacks(callingInfo);
- },
- 0);
+ service.asBinder().linkToDeath(() -> onSdkSandboxDeath(callingInfo), 0);
} catch (RemoteException re) {
link.handleLoadSdkException(
new LoadSdkException(
SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
SANDBOX_NOT_AVAILABLE_MSG),
- false,
/*startTimeOfErrorStage=*/ startTimeForLoadingSandbox,
SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX,
/*successAtStage=*/ false);
- notifyPendingCallbacks(callingInfo);
- handleSandboxLifecycleCallbacks(callingInfo);
+ onSdkSandboxDeath(callingInfo);
return;
}
final int timeToLoadSandbox =
@@ -907,20 +926,33 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
timeToLoadSandbox,
/* success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX,
+ callingInfo.getUid());
- onSandboxStart(callingInfo);
+ try {
+ onSandboxStart(callingInfo, service);
+ } catch (RemoteException e) {
+ final String errorMsg = "Failed to initialize sandbox";
+ Log.e(TAG, errorMsg);
+ link.handleLoadSdkException(
+ new LoadSdkException(
+ SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
+ SANDBOX_NOT_AVAILABLE_MSG + " : " + errorMsg,
+ e),
+ /*startTimeOfErrorStage=*/ startTimeForLoadingSandbox,
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX,
+ /*successAtStage=*/ false);
+ return;
+ }
loadSdkForService(
callingInfo,
- sdkToken,
info,
params,
link,
timeToLoadSandbox,
timeSystemServerReceivedCallFromApp,
- service,
- sdkToServiceLink);
+ service);
}
@Override
@@ -929,7 +961,6 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
new LoadSdkException(
SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR,
"Failed to bind the service"),
- /*cleanUpInternalState=*/ true,
/*startTimeOfErrorStage=*/ startTimeForLoadingSandbox,
/*stage*/ SdkSandboxStatsLog
.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX,
@@ -938,39 +969,50 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
});
}
- private void handleSandboxLifecycleCallbacks(CallingInfo callingInfo) {
- RemoteCallbackList<ISdkSandboxProcessDeathCallback> sandboxLifecycleCallbacks;
+ private void onSdkSandboxDeath(CallingInfo callingInfo) {
synchronized (mLock) {
- sandboxLifecycleCallbacks = mSandboxLifecycleCallbacks.get(callingInfo);
+ notifyPendingCallbacksLocked(callingInfo);
+ handleSandboxLifecycleCallbacksLocked(callingInfo);
+ removeSdksBeingLoadedLocked(callingInfo);
+ }
+ }
- if (sandboxLifecycleCallbacks == null) {
- return;
- }
+ @GuardedBy("mLock")
+ private void handleSandboxLifecycleCallbacksLocked(CallingInfo callingInfo) {
+ RemoteCallbackList<ISdkSandboxProcessDeathCallback> sandboxLifecycleCallbacks;
+ sandboxLifecycleCallbacks = mSandboxLifecycleCallbacks.get(callingInfo);
- int size = sandboxLifecycleCallbacks.beginBroadcast();
- for (int i = 0; i < size; ++i) {
- try {
- sandboxLifecycleCallbacks.getBroadcastItem(i).onSdkSandboxDied();
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to send sdk sandbox death event to app", e);
- }
- }
- sandboxLifecycleCallbacks.finishBroadcast();
+ if (sandboxLifecycleCallbacks == null) {
+ return;
+ }
- mSandboxLifecycleCallbacks.remove(callingInfo);
+ int size = sandboxLifecycleCallbacks.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ try {
+ sandboxLifecycleCallbacks.getBroadcastItem(i).onSdkSandboxDied();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to send sdk sandbox death event to app", e);
+ }
}
+ sandboxLifecycleCallbacks.finishBroadcast();
+
+ mSandboxLifecycleCallbacks.remove(callingInfo);
}
- void notifyPendingCallbacks(CallingInfo callingInfo) {
- synchronized (mLock) {
- if (!mPendingCallbacks.containsKey(callingInfo)) {
- return;
- }
- for (Runnable callbackErrors : mPendingCallbacks.get(callingInfo).values()) {
- callbackErrors.run();
- }
- mPendingCallbacks.remove(callingInfo);
+ @GuardedBy("mLock")
+ void notifyPendingCallbacksLocked(CallingInfo callingInfo) {
+ if (!mPendingCallbacks.containsKey(callingInfo)) {
+ return;
}
+ for (Runnable callbackErrors : mPendingCallbacks.get(callingInfo).values()) {
+ callbackErrors.run();
+ }
+ mPendingCallbacks.remove(callingInfo);
+ }
+
+ @GuardedBy("mLock")
+ private void removeSdksBeingLoadedLocked(CallingInfo callingInfo) {
+ mSdksBeingLoaded.removeIf(it -> it.first.equals(callingInfo));
}
@Override
@@ -1048,11 +1090,9 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private final Object mLock = new Object();
@GuardedBy("mLock")
- private boolean mIsKillSwitchEnabled = false;
-
- // This is required so that the sandbox is not re-enabled in the same boot.
- @GuardedBy("mLock")
- private boolean mKillSwitchBecameEnabled = false;
+ private boolean mIsKillSwitchEnabled =
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES, PROPERTY_DISABLE_SDK_SANDBOX, false);
SdkSandboxSettingsListener(Context context) {
mContext = context;
@@ -1060,7 +1100,7 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private void registerObserver() {
DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_SDK_SANDBOX, mContext.getMainExecutor(), this);
+ DeviceConfig.NAMESPACE_ADSERVICES, mContext.getMainExecutor(), this);
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -1074,26 +1114,22 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
void reset() {
synchronized (mLock) {
DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_SDK_SANDBOX,
+ DeviceConfig.NAMESPACE_ADSERVICES,
PROPERTY_DISABLE_SDK_SANDBOX,
"false",
false);
mIsKillSwitchEnabled = false;
- mKillSwitchBecameEnabled = false;
}
}
@Override
public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
synchronized (mLock) {
- if (!mIsKillSwitchEnabled && !mKillSwitchBecameEnabled) {
- mIsKillSwitchEnabled =
- properties.getBoolean(PROPERTY_DISABLE_SDK_SANDBOX, false);
- if (mIsKillSwitchEnabled) {
- mKillSwitchBecameEnabled = true;
- synchronized (SdkSandboxManagerService.this.mLock) {
- stopAllSandboxesLocked();
- }
+ boolean killSwitchPreviouslyEnabled = mIsKillSwitchEnabled;
+ mIsKillSwitchEnabled = properties.getBoolean(PROPERTY_DISABLE_SDK_SANDBOX, false);
+ if (mIsKillSwitchEnabled && !killSwitchPreviouslyEnabled) {
+ synchronized (SdkSandboxManagerService.this.mLock) {
+ stopAllSandboxesLocked();
}
}
}
@@ -1130,16 +1166,15 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
/** Stops all running sandboxes in the case that the killswitch is triggered. */
@GuardedBy("mLock")
private void stopAllSandboxesLocked() {
- Iterator<Map.Entry<IBinder, AppAndRemoteSdkLink>> it =
- mAppAndRemoteSdkLinks.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<IBinder, AppAndRemoteSdkLink> entry = it.next();
- stopSdkSandboxService(entry.getValue().mCallingInfo, "SDK sandbox killswitch enabled");
+ for (int i = mAppAndRemoteSdkLinks.size() - 1; i >= 0; --i) {
+ stopSdkSandboxService(
+ mAppAndRemoteSdkLinks.keyAt(i).first, "SDK sandbox killswitch enabled");
}
}
void stopSdkSandboxService(CallingInfo currentCallingInfo, String reason) {
if (!isSdkSandboxServiceRunning(currentCallingInfo)) {
+ Log.d(TAG, "Cannot kill sandbox for " + currentCallingInfo + ", already dead");
return;
}
@@ -1149,7 +1184,7 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
// the sandbox by uid.
synchronized (mLock) {
for (int i = 0; i < mCallingInfosWithDeathRecipients.size(); i++) {
- final CallingInfo callingInfo = mCallingInfosWithDeathRecipients.valueAt(i);
+ final CallingInfo callingInfo = mCallingInfosWithDeathRecipients.keyAt(i);
if (callingInfo.getUid() == currentCallingInfo.getUid()) {
mServiceProvider.unbindService(callingInfo, true);
}
@@ -1165,44 +1200,48 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
return mServiceProvider.getBoundServiceForApp(callingInfo) != null;
}
- private void onSandboxStart(CallingInfo callingInfo) {
+ private void onSandboxStart(CallingInfo callingInfo, ISdkSandboxService service)
+ throws RemoteException {
+ service.initialize(new SdkToServiceLink());
+
+ notifySyncManagerSandboxStarted(callingInfo);
+ }
+
+ private void notifySyncManagerSandboxStarted(CallingInfo callingInfo) {
ISharedPreferencesSyncCallback syncManagerCallback = null;
synchronized (mLock) {
syncManagerCallback = mSyncDataCallbacks.get(callingInfo);
- mSyncDataCallbacks.remove(callingInfo);
- }
- if (syncManagerCallback != null) {
- try {
- syncManagerCallback.onSandboxStart();
- } catch (RemoteException ignore) {
- // App died.
+ if (syncManagerCallback != null) {
+ try {
+ syncManagerCallback.onSandboxStart();
+ } catch (RemoteException ignore) {
+ // App died.
+ }
}
+ mSyncDataCallbacks.remove(callingInfo);
}
}
private void loadSdkForService(
CallingInfo callingInfo,
- IBinder sdkToken,
SdkProviderInfo sdkProviderInfo,
Bundle params,
AppAndRemoteSdkLink link,
int timeToLoadSandbox,
long timeSystemServerReceivedCallFromApp,
- ISdkSandboxService service,
- SdkToServiceLink sdkToServiceLink) {
+ ISdkSandboxService service) {
if (isSdkSandboxDisabled(service)) {
link.handleLoadSdkException(
new LoadSdkException(LOAD_SDK_SDK_SANDBOX_DISABLED, SANDBOX_DISABLED_MSG),
- false,
-1,
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__METHOD_UNSPECIFIED,
false);
return;
}
// Gather sdk storage information
- SdkDataDirInfo sdkDataInfo =
- mSdkSandboxStorageManager.getSdkDataDirInfo(
+ final StorageDirInfo sdkDataInfo =
+ mSdkSandboxStorageManager.getSdkStorageDirInfo(
callingInfo, sdkProviderInfo.getSdkInfo().getName());
synchronized (mLock) {
@@ -1216,7 +1255,6 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
new LoadSdkException(
SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
SANDBOX_NOT_AVAILABLE_MSG),
- false,
/*startTimeOfErrorStage=*/ -1,
SdkSandboxStatsLog
.SANDBOX_API_CALLED__METHOD__METHOD_UNSPECIFIED,
@@ -1235,7 +1273,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
latencySystemServerAppToSandbox,
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX);
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ callingInfo.getUid());
final SandboxLatencyInfo sandboxLatencyInfo =
new SandboxLatencyInfo(timeSystemServerCalledSandbox);
@@ -1243,7 +1282,6 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
try {
service.loadSdk(
callingInfo.getPackageName(),
- sdkToken,
sdkProviderInfo.getApplicationInfo(),
sdkProviderInfo.getSdkInfo().getName(),
sdkProviderInfo.getSdkProviderClassName(),
@@ -1251,13 +1289,11 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
sdkDataInfo.getDeDataDir(),
params,
link,
- sandboxLatencyInfo,
- sdkToServiceLink);
+ sandboxLatencyInfo);
} catch (DeadObjectException e) {
link.handleLoadSdkException(
new LoadSdkException(
SDK_SANDBOX_PROCESS_NOT_AVAILABLE, SANDBOX_NOT_AVAILABLE_MSG),
- false,
/*startTimeOfErrorStage=*/ -1,
SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED,
/*successAtStage=*/ false);
@@ -1266,27 +1302,14 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
Log.w(TAG, errorMsg, e);
link.handleLoadSdkException(
new LoadSdkException(SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR, errorMsg),
- /*cleanUpInternalState=*/ true,
/*startTimeOfErrorStage=*/ timeSystemServerReceivedCallFromApp,
/*stage*/ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
/*successAtStage=*/ false);
}
}
- /**
- * Clean up all internal data structures related to {@code sdkToken}
- */
- private void cleanUp(IBinder sdkToken) {
- // Destroy the sdkToken first, to free up the {callingUid, name} pair
- mSdkTokenManager.destroy(sdkToken);
- // Now clean up rest of the state which is using an obsolete sdkToken
- synchronized (mLock) {
- mAppAndRemoteSdkLinks.remove(sdkToken);
- }
- }
-
/** Clean up all internal data structures related to {@code callingInfo} of the app */
- private void removeAllSdkTokensAndLinks(CallingInfo callingInfo) {
+ private void removeAllSdkLinks(CallingInfo callingInfo) {
removeLinksToSdk(callingInfo, null, /*timeSystemServerReceivedCallFromApp=*/ -1);
}
@@ -1306,13 +1329,12 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
final UnloadSdkRemoveLinksToSdkResponse response = new UnloadSdkRemoveLinksToSdkResponse();
synchronized (mLock) {
ISdkSandboxService boundSandbox = mServiceProvider.getBoundServiceForApp(callingInfo);
- ArrayList<IBinder> linksToDelete = new ArrayList<>();
+ ArrayList<Pair<CallingInfo, String>> linksToDelete = new ArrayList<>();
for (int i = 0; i < mAppAndRemoteSdkLinks.size(); i++) {
- AppAndRemoteSdkLink link = mAppAndRemoteSdkLinks.valueAt(i);
- if (link.mCallingInfo.equals(callingInfo)) {
- IBinder sdkToken = mAppAndRemoteSdkLinks.keyAt(i);
- if (TextUtils.isEmpty(sdkName)
- || link.mSdkProviderInfo.getSdkInfo().getName().equals(sdkName)) {
+ Pair<CallingInfo, String> appAndSdkInfo = mAppAndRemoteSdkLinks.keyAt(i);
+ if (appAndSdkInfo.first.equals(callingInfo)) {
+ String curSdkName = appAndSdkInfo.second;
+ if (TextUtils.isEmpty(sdkName) || curSdkName.equals(sdkName)) {
if (boundSandbox != null) {
SandboxLatencyInfo sandboxLatencyInfo =
new SandboxLatencyInfo(mInjector.getCurrentTime());
@@ -1326,19 +1348,21 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
SANDBOX_API_CALLED__METHOD__UNLOAD_SDK,
(int)
(sandboxLatencyInfo
- .getTimeSystemServerCalledSandbox()
+ .getTimeSystemServerCalledSandbox()
- timeSystemServerReceivedCallFromApp),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX);
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ callingInfo.getUid());
}
try {
boundSandbox.unloadSdk(
- sdkToken,
+ curSdkName,
new IUnloadSdkCallback.Stub() {
@Override
public void onUnloadSdk(
SandboxLatencyInfo sandboxLatencyInfo) {
logLatencyMetricsForCallback(
+ callingInfo,
/*timeSystemServerReceivedCallFromSandbox=*/
mInjector.getCurrentTime(),
SANDBOX_API_CALLED__METHOD__UNLOAD_SDK,
@@ -1357,17 +1381,15 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
}
response.setTimeSystemServerReceivedCallFromSandbox(
mInjector.getCurrentTime());
- linksToDelete.add(sdkToken);
+ linksToDelete.add(appAndSdkInfo);
} else {
response.setShouldStopSandbox(false);
}
}
}
- for (int i = 0; i < linksToDelete.size(); i++) {
- IBinder sdkToken = linksToDelete.get(i);
- mSdkTokenManager.destroy(sdkToken);
- mAppAndRemoteSdkLinks.remove(sdkToken);
+ for (Pair<CallingInfo, String> appAndSdkInfo : linksToDelete) {
+ mAppAndRemoteSdkLinks.remove(appAndSdkInfo);
}
return response;
}
@@ -1485,66 +1507,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
}
}
- @ThreadSafe
- private static class SdkTokenManager {
- // Keep track of codeToken for each unique pair of {callingUid, sdkName}
- @GuardedBy("mSdkTokens")
- final ArrayMap<Pair<CallingInfo, String>, IBinder> mSdkTokens = new ArrayMap<>();
- @GuardedBy("mSdkTokens")
- final ArrayMap<IBinder, Pair<CallingInfo, String>> mReverseSdkTokens =
- new ArrayMap<>();
-
- /**
- * For the given {callingUid, name} pair, create unique {@code sdkToken} or
- * return existing one.
- */
- public IBinder createOrGetSdkToken(CallingInfo callingInfo, String sdkName) {
- final Pair<CallingInfo, String> pair = Pair.create(callingInfo, sdkName);
- synchronized (mSdkTokens) {
- if (mSdkTokens.containsKey(pair)) {
- return mSdkTokens.get(pair);
- }
- final IBinder sdkToken = new Binder();
- mSdkTokens.put(pair, sdkToken);
- mReverseSdkTokens.put(sdkToken, pair);
- return sdkToken;
- }
- }
-
- @Nullable
- public IBinder getSdkToken(CallingInfo callingInfo, String sdkName) {
- final Pair<CallingInfo, String> pair = Pair.create(callingInfo, sdkName);
- synchronized (mSdkTokens) {
- return mSdkTokens.get(pair);
- }
- }
-
- public void destroy(IBinder sdkToken) {
- synchronized (mSdkTokens) {
- mSdkTokens.remove(mReverseSdkTokens.get(sdkToken));
- mReverseSdkTokens.remove(sdkToken);
- }
- }
-
- void dump(PrintWriter writer) {
- synchronized (mSdkTokens) {
- if (mSdkTokens.isEmpty()) {
- writer.println("mSdkTokens is empty");
- } else {
- writer.print("mSdkTokens size: ");
- writer.println(mSdkTokens.size());
- for (Pair<CallingInfo, String> pair : mSdkTokens.keySet()) {
- writer.printf("caller: %s, sdkName: %s",
- pair.first,
- pair.second);
- writer.println();
- }
- }
- }
- }
- }
-
- private void removePendingCallback(CallingInfo callingInfo, IBinder callbackBinder) {
+ @GuardedBy("mLock")
+ private void removePendingCallbackLocked(CallingInfo callingInfo, IBinder callbackBinder) {
synchronized (mLock) {
if (mPendingCallbacks.containsKey(callingInfo)) {
mPendingCallbacks.get(callingInfo).remove(callbackBinder);
@@ -1560,7 +1524,9 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
int stage,
boolean successAtStage,
IRequestSurfacePackageCallback callback) {
- removePendingCallback(callingInfo, callback.asBinder());
+ synchronized (mLock) {
+ removePendingCallbackLocked(callingInfo, callback.asBinder());
+ }
final long timeSystemServerCalledApp = mInjector.getCurrentTime();
if (stage != SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED) {
SdkSandboxStatsLog.write(
@@ -1568,7 +1534,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
(int) (timeSystemServerCalledApp - startTimeOfStageWhereErrorOccurred),
successAtStage,
- stage);
+ stage,
+ callingInfo.getUid());
}
try {
callback.onSurfacePackageError(errorCode, errorMsg, timeSystemServerCalledApp);
@@ -1581,34 +1548,37 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
* A callback object to establish a link between the sdk in sandbox calling into manager
* service.
*
- * <p>When a sdk is loaded into a sandbox, a callback object of {@link SdkToServiceLink} is
- * passed with the load call as a part of {@link android.app.sdksandbox.SdkSandboxController}.
- * The sdk can call APIs on the link object and invoked the methods here to get data from the
- * manager service.
+ * <p>When a sandbox is initialized, a callback object of {@link SdkToServiceLink} is passed to
+ * be used as a part of {@link SdkSandboxController}. The Controller can then can call APIs on
+ * the link object to get data from the manager service.
*/
private class SdkToServiceLink extends ISdkToServiceCallback.Stub {
/**
- * Fetches information about Sdks that are loaded in the sandbox.
+ * Fetches {@link SandboxedSdk} for all SDKs that are loaded in the sandbox.
+ *
+ * <p>This provides the information on the library that is currently loaded in the sandbox
+ * and also channels to communicate with loaded SDK.
*
* @param clientPackageName package name of the app for which the sdk was loaded in the
* sandbox
- * @return List of {@link SharedLibraryInfo} containing all currently loaded sdks
+ * @return List of {@link SandboxedSdk} containing all currently loaded sdks
*/
@Override
- public List<SharedLibraryInfo> getLoadedSdkLibrariesInfo(String clientPackageName)
+ public List<SandboxedSdk> getSandboxedSdks(String clientPackageName)
throws RemoteException {
// TODO(b/242039497): Add authorisation checks
- final List<SharedLibraryInfo> libraryInfoList = new ArrayList<>();
+ // TODO(b/247592493): Add test
+ final List<SandboxedSdk> sandboxedSdks = new ArrayList<>();
synchronized (mLock) {
for (int i = mAppAndRemoteSdkLinks.size() - 1; i >= 0; i--) {
AppAndRemoteSdkLink link = mAppAndRemoteSdkLinks.valueAt(i);
if (link.mCallingInfo.getPackageName().equals(clientPackageName)) {
- libraryInfoList.add(link.mSdkProviderInfo.mSdkInfo);
+ sandboxedSdks.add(link.mSandboxedSdk);
}
}
}
- return libraryInfoList;
+ return sandboxedSdks;
}
}
@@ -1632,25 +1602,25 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
* callback into the remote SDK.
* </ol>
*
- * <p>We maintain a link for each unique {app, remoteCode} pair, which is identified with {@code
- * codeToken}.
+ * <p>We maintain a link for each unique {app, remoteSdk} pair, which is identified with {@code
+ * sdkName}.
*/
private class AppAndRemoteSdkLink extends ILoadSdkInSandboxCallback.Stub {
private final CallingInfo mCallingInfo;
private final SdkProviderInfo mSdkProviderInfo;
- // The codeToken for which this channel has been created
- private final IBinder mSdkToken;
+ private final String mSdkName;
private final ILoadSdkCallback mManagerToAppCallback;
+ private SandboxedSdk mSandboxedSdk;
@GuardedBy("this")
private ISdkSandboxManagerToSdkSandboxCallback mManagerToCodeCallback;
AppAndRemoteSdkLink(
CallingInfo callingInfo,
- IBinder sdkToken,
+ String sdkName,
ILoadSdkCallback managerToAppCallback,
SdkProviderInfo sdkProviderInfo) {
- mSdkToken = sdkToken;
+ mSdkName = sdkName;
mSdkProviderInfo = sdkProviderInfo;
mCallingInfo = callingInfo;
mManagerToAppCallback = managerToAppCallback;
@@ -1664,15 +1634,22 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
final long timeSystemServerReceivedCallFromSandbox = mInjector.getCurrentTime();
logLatencyMetricsForCallback(
+ mCallingInfo,
timeSystemServerReceivedCallFromSandbox,
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
sandboxLatencyInfo);
- // Keep reference to callback so that manager service can
- // callback to remote code loaded.
synchronized (this) {
+ // Keep reference to callback so that manager service can
+ // callback to remote code loaded.
mManagerToCodeCallback = callback;
+ // attach the SharedLibraryInfo for the loaded SDK to the sandboxedSdk.
+ sandboxedSdk.attachSharedLibraryInfo(mSdkProviderInfo.getSdkInfo());
+ // Keep reference to sandboxedSdk so that manager service can
+ // keep log of all loaded SDKs and their binders for communication.
+ mSandboxedSdk = sandboxedSdk;
}
+
handleLoadSdkSuccess(sandboxedSdk, timeSystemServerReceivedCallFromSandbox);
}
@@ -1682,13 +1659,13 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
final long timeSystemServerReceivedCallFromSandbox = mInjector.getCurrentTime();
logLatencyMetricsForCallback(
+ mCallingInfo,
timeSystemServerReceivedCallFromSandbox,
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
sandboxLatencyInfo);
handleLoadSdkException(
updateLoadSdkErrorCode(exception),
- /*cleanUpInternalState=*/ true,
/*startTimeOfErrorStage=*/ timeSystemServerReceivedCallFromSandbox,
SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP,
/*successAtStage=*/ true);
@@ -1696,14 +1673,26 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private void handleLoadSdkSuccess(
SandboxedSdk sandboxedSdk, long timeSystemServerReceivedCallFromSandbox) {
- removePendingCallback(mCallingInfo, this.asBinder());
+ final Pair<CallingInfo, String> appAndSdkInfo = Pair.create(mCallingInfo, mSdkName);
+ synchronized (mLock) {
+ removePendingCallbackLocked(mCallingInfo, this.asBinder());
+ // The SDK that was in the state of being loaded has now transitioned to the loaded
+ // state. This order of first adding to mAppAndRemoteSdkLinks and then removing from
+ // mSdksBeingLoaded should be maintained (or alternatively update them together in
+ // one synchronized block) as otherwise when loadSdk() is called, the check for
+ // mSdksBeingLoaded could be bypassed as the SDK is no longer being loaded, but
+ // appAndSdkInfo might not exist in mAppAndRemoteSdkLinks yet
+ mAppAndRemoteSdkLinks.put(appAndSdkInfo, this);
+ mSdksBeingLoaded.remove(appAndSdkInfo);
+ }
final long timeSystemServerCalledApp = mInjector.getCurrentTime();
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
(int) (timeSystemServerCalledApp - timeSystemServerReceivedCallFromSandbox),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP);
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP,
+ mCallingInfo.getUid());
try {
mManagerToAppCallback.onLoadSdkSuccess(sandboxedSdk, timeSystemServerCalledApp);
} catch (RemoteException e) {
@@ -1713,17 +1702,14 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
void handleLoadSdkException(
LoadSdkException exception,
- boolean cleanUpInternalState,
long startTimeOfErrorStage,
int stage,
boolean successAtStage) {
- if (cleanUpInternalState) {
- // If an SDK fails to load entirely and does not exist in the sandbox, cleanup
- // might need to occur so that the manager has to no longer concern itself with
- // communication between the app and a non-existing remote code.
- cleanUp(mSdkToken);
+ final Pair<CallingInfo, String> appAndSdkInfo = Pair.create(mCallingInfo, mSdkName);
+ synchronized (mLock) {
+ removePendingCallbackLocked(mCallingInfo, this.asBinder());
+ mSdksBeingLoaded.remove(appAndSdkInfo);
}
- removePendingCallback(mCallingInfo, this.asBinder());
final long timeSystemServerCalledApp = mInjector.getCurrentTime();
if (stage != SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED) {
@@ -1732,7 +1718,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
(int) (timeSystemServerCalledApp - startTimeOfErrorStage),
successAtStage,
- stage);
+ stage,
+ mCallingInfo.getUid());
}
try {
@@ -1748,14 +1735,17 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
Bundle params,
long timeSystemServerReceivedCallFromSandbox,
IRequestSurfacePackageCallback callback) {
- removePendingCallback(mCallingInfo, callback.asBinder());
+ synchronized (mLock) {
+ removePendingCallbackLocked(mCallingInfo, callback.asBinder());
+ }
final long timeSystemServerCalledApp = mInjector.getCurrentTime();
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
+ SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
(int) (timeSystemServerCalledApp - timeSystemServerReceivedCallFromSandbox),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP);
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP,
+ mCallingInfo.getUid());
try {
callback.onSurfacePackageReady(
surfacePackage, surfacePackageId, params, timeSystemServerCalledApp);
@@ -1781,8 +1771,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
() ->
handleSurfacePackageError(
mCallingInfo,
- SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
- SANDBOX_NOT_AVAILABLE_MSG,
+ REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED,
+ "SDK " + mSdkName + " is not loaded",
/*timeSystemServerReceivedCallFromSandbox=*/ -1,
SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED,
/*successAtStage=*/ false,
@@ -1794,7 +1784,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
(int) (timeSystemServerCalledSandbox - timeSystemServerReceivedCallFromApp),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mCallingInfo.getUid());
final SandboxLatencyInfo sandboxLatencyInfo =
new SandboxLatencyInfo(timeSystemServerCalledSandbox);
try {
@@ -1816,7 +1807,10 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
final long timeSystemServerReceivedCallFromSandbox =
mInjector.getCurrentTime();
+ LogUtil.d(TAG, "onSurfacePackageReady received");
+
logLatencyMetricsForCallback(
+ mCallingInfo,
timeSystemServerReceivedCallFromSandbox,
SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
sandboxLatencyInfo);
@@ -1838,6 +1832,7 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
mInjector.getCurrentTime();
logLatencyMetricsForCallback(
+ mCallingInfo,
timeSystemServerReceivedCallFromSandbox,
SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
sandboxLatencyInfo);
@@ -1858,10 +1853,16 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
});
}
} catch (DeadObjectException e) {
+ LogUtil.d(
+ TAG,
+ mCallingInfo
+ + " requested surface package from SDK "
+ + mSdkName
+ + " but sandbox is not alive");
handleSurfacePackageError(
mCallingInfo,
- SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
- SANDBOX_NOT_AVAILABLE_MSG,
+ REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED,
+ "SDK " + mSdkName + " is not loaded",
/*timeSystemServerReceivedCallFromSandbox=*/ -1,
SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED,
/*successAtStage=*/ false,
@@ -1967,22 +1968,27 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
}
private void logLatencyMetricsForCallback(
+ CallingInfo callingInfo,
long timeSystemServerReceivedCallFromSandbox,
int method,
SandboxLatencyInfo sandboxLatencyInfo) {
+ final int appUid = callingInfo.getUid();
+
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
method,
sandboxLatencyInfo.getLatencySystemServerToSandbox(),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX,
+ appUid);
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
method,
sandboxLatencyInfo.getSandboxLatency(),
sandboxLatencyInfo.isSuccessfulAtSandbox(),
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX,
+ appUid);
final int latencySdk = sandboxLatencyInfo.getSdkLatency();
if (latencySdk != -1) {
@@ -1991,7 +1997,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
method,
latencySdk,
sandboxLatencyInfo.isSuccessfulAtSdk(),
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK,
+ appUid);
}
SdkSandboxStatsLog.write(
@@ -2001,7 +2008,8 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
(timeSystemServerReceivedCallFromSandbox
- sandboxLatencyInfo.getTimeSandboxCalledSystemServer()),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER);
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER,
+ appUid);
}
/** @hide */
@@ -2064,9 +2072,15 @@ public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
}
synchronized (mLock) {
for (int i = 0; i < mCallingInfosWithDeathRecipients.size(); i++) {
- final CallingInfo callingInfo = mCallingInfosWithDeathRecipients.valueAt(i);
+ final CallingInfo callingInfo = mCallingInfosWithDeathRecipients.keyAt(i);
if (callingInfo.getUid() == uid) {
- // Stop the sandbox when the app goes to the background.
+ LogUtil.d(
+ TAG,
+ "App with uid "
+ + uid
+ + " has gone to the background, unbinding sandbox");
+ // Unbind the sandbox when the app goes to the background to lower its
+ // priority.
mServiceProvider.unbindService(callingInfo, false);
}
}
diff --git a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxStorageManager.java b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxStorageManager.java
index 6c69f1864..86e1d1990 100644
--- a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxStorageManager.java
+++ b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxStorageManager.java
@@ -46,6 +46,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@@ -92,10 +93,10 @@ class SdkSandboxStorageManager {
/**
* Handle package added or updated event.
*
- * On package added or updated, we need to reconcile sdk subdirectories for the new/updated
+ * <p>On package added or updated, we need to reconcile sdk subdirectories for the new/updated
* package.
*/
- void onPackageAddedOrUpdated(CallingInfo callingInfo) {
+ public void onPackageAddedOrUpdated(CallingInfo callingInfo) {
synchronized (mLock) {
reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/false);
}
@@ -113,13 +114,103 @@ class SdkSandboxStorageManager {
}
}
- void prepareSdkDataOnLoad(CallingInfo callingInfo) {
+ public void prepareSdkDataOnLoad(CallingInfo callingInfo) {
synchronized (mLock) {
reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/false);
}
}
- SdkDataDirInfo getSdkDataDirInfo(CallingInfo callingInfo, String sdkName) {
+ public StorageDirInfo getSdkStorageDirInfo(CallingInfo callingInfo, String sdkName) {
+ final StorageDirInfo packageDirInfo = getSdkDataPackageDirInfo(callingInfo);
+ if (packageDirInfo == null) {
+ // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
+ return new StorageDirInfo(null, null);
+ }
+ // TODO(b/232924025): We should have these information cached, instead of rescanning dirs.
+ synchronized (mLock) {
+ final SubDirectories ceSubDirs = new SubDirectories(packageDirInfo.getCeDataDir());
+ final SubDirectories deSubDirs = new SubDirectories(packageDirInfo.getDeDataDir());
+ final String sdkCeSubDirPath = ceSubDirs.getSdkSubDir(sdkName, /*fullPath=*/ true);
+ final String sdkDeSubDirPath = deSubDirs.getSdkSubDir(sdkName, /*fullPath=*/ true);
+ return new StorageDirInfo(sdkCeSubDirPath, sdkDeSubDirPath);
+ }
+ }
+
+ public List<StorageDirInfo> getSdkStorageDirInfo(CallingInfo callingInfo) {
+ final StorageDirInfo packageDirInfo = getSdkDataPackageDirInfo(callingInfo);
+ if (packageDirInfo == null) {
+ // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
+ return new ArrayList<>();
+ }
+
+ final List<StorageDirInfo> sdkStorageDirInfos = new ArrayList<>();
+
+ synchronized (mLock) {
+ final SubDirectories ceSubDirs = new SubDirectories(packageDirInfo.getCeDataDir());
+ final SubDirectories deSubDirs = new SubDirectories(packageDirInfo.getDeDataDir());
+
+ /**
+ * Getting the SDKs name with deSubDir only assuming that ceSubDirs and deSubDirs have
+ * the same list of SDKs
+ */
+ final ArrayList<String> sdkNames = deSubDirs.getSdkNames();
+ int sdkNamesSize = sdkNames.size();
+
+ for (int i = 0; i < sdkNamesSize; i++) {
+ final String sdkCeSubDirPath =
+ ceSubDirs.getSdkSubDir(sdkNames.get(i), /*fullPath=*/ true);
+ final String sdkDeSubDirPath =
+ deSubDirs.getSdkSubDir(sdkNames.get(i), /*fullPath=*/ true);
+ sdkStorageDirInfos.add(new StorageDirInfo(sdkCeSubDirPath, sdkDeSubDirPath));
+ }
+ return sdkStorageDirInfos;
+ }
+ }
+
+ public StorageDirInfo getInternalStorageDirInfo(CallingInfo callingInfo, String subDirName) {
+ final StorageDirInfo packageDirInfo = getSdkDataPackageDirInfo(callingInfo);
+ if (packageDirInfo == null) {
+ // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
+ return new StorageDirInfo(null, null);
+ }
+ synchronized (mLock) {
+ final SubDirectories ceSubDirs = new SubDirectories(packageDirInfo.getCeDataDir());
+ final SubDirectories deSubDirs = new SubDirectories(packageDirInfo.getDeDataDir());
+ final String ceSubDirPath = ceSubDirs.getInternalSubDir(subDirName, /*fullPath=*/ true);
+ final String deSubDirPath = deSubDirs.getInternalSubDir(subDirName, /*fullPath=*/ true);
+ return new StorageDirInfo(ceSubDirPath, deSubDirPath);
+ }
+ }
+
+ public List<StorageDirInfo> getInternalStorageDirInfo(CallingInfo callingInfo) {
+ final StorageDirInfo packageDirInfo = getSdkDataPackageDirInfo(callingInfo);
+ if (packageDirInfo == null) {
+ // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
+ return new ArrayList<>();
+ }
+
+ final List<StorageDirInfo> internalStorageDirInfos = new ArrayList<>();
+
+ synchronized (mLock) {
+ final SubDirectories ceSubDirs = new SubDirectories(packageDirInfo.getCeDataDir());
+ final SubDirectories deSubDirs = new SubDirectories(packageDirInfo.getDeDataDir());
+
+ List<String> internalSubDirNames =
+ Arrays.asList(SubDirectories.SHARED_DIR, SubDirectories.SANDBOX_DIR);
+
+ for (int i = 0; i < 2; i++) {
+ final String sdkCeSubDirPath =
+ ceSubDirs.getInternalSubDir(internalSubDirNames.get(i), /*fullPath=*/ true);
+ final String sdkDeSubDirPath =
+ deSubDirs.getInternalSubDir(internalSubDirNames.get(i), /*fullPath=*/ true);
+ internalStorageDirInfos.add(new StorageDirInfo(sdkCeSubDirPath, sdkDeSubDirPath));
+ }
+ return internalStorageDirInfos;
+ }
+ }
+
+ @Nullable
+ private StorageDirInfo getSdkDataPackageDirInfo(CallingInfo callingInfo) {
final int uid = callingInfo.getUid();
final String packageName = callingInfo.getPackageName();
String volumeUuid = null;
@@ -127,8 +218,7 @@ class SdkSandboxStorageManager {
volumeUuid = getVolumeUuidForPackage(getUserId(uid), packageName);
} catch (Exception e) {
Log.w(TAG, "Failed to find package " + packageName + " error: " + e.getMessage());
- // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
- return new SdkDataDirInfo(null, null);
+ return null;
}
final String cePackagePath =
getSdkDataPackageDirectory(
@@ -136,14 +226,7 @@ class SdkSandboxStorageManager {
final String dePackagePath =
getSdkDataPackageDirectory(
volumeUuid, getUserId(uid), packageName, /*isCeData=*/ false);
- // TODO(b/232924025): We should have these information cached, instead of rescanning dirs.
- synchronized (mLock) {
- final SubDirectories ceSubDirs = new SubDirectories(cePackagePath);
- final String sdkCeSubDirPath = ceSubDirs.getSdkSubDir(sdkName, /*fullPath=*/ true);
- final SubDirectories deSubDirs = new SubDirectories(dePackagePath);
- final String sdkDeSubDirPath = deSubDirs.getSdkSubDir(sdkName, /*fullPath=*/ true);
- return new SdkDataDirInfo(sdkCeSubDirPath, sdkDeSubDirPath);
- }
+ return new StorageDirInfo(cePackagePath, dePackagePath);
}
private int getUserId(int uid) {
@@ -179,17 +262,7 @@ class SdkSandboxStorageManager {
final String deSdkDataPackagePath =
getSdkDataPackageDirectory(volumeUuid, userId, packageName, /*isCeData=*/ false);
final SubDirectories existingDeSubDirs = new SubDirectories(deSdkDataPackagePath);
- final List<String> subDirNames = new ArrayList<>();
- subDirNames.addAll(SubDirectories.NON_SDK_SUBDIRS);
- for (int i = 0; i < sdksUsed.size(); i++) {
- final String sdk = sdksUsed.get(i);
- final String sdkSubDir = existingDeSubDirs.getSdkSubDir(sdk);
- if (sdkSubDir == null) {
- subDirNames.add(sdk + "@" + getRandomString());
- } else {
- subDirNames.add(sdkSubDir);
- }
- }
+
final int appId = UserHandle.getAppId(uid);
final UserManager um = mContext.getSystemService(UserManager.class);
int flags = 0;
@@ -209,7 +282,11 @@ class SdkSandboxStorageManager {
flags = PackageManagerLocal.FLAG_STORAGE_DE;
doesDeNeedReconcile = !existingDeSubDirs.isValid(expectedSdkNames);
}
+
+ // Reconcile only if ce or de subdirs are different than expectation
if (doesCeNeedReconcile || doesDeNeedReconcile) {
+ // List of all the sub-directories we need to create
+ final List<String> subDirNames = existingDeSubDirs.generateSubDirNames(sdksUsed);
try {
// TODO(b/224719352): Pass actual seinfo from here
mPackageManagerLocal.reconcileSdkData(
@@ -229,14 +306,6 @@ class SdkSandboxStorageManager {
}
}
- // Returns a random string.
- private static String getRandomString() {
- SecureRandom random = new SecureRandom();
- byte[] bytes = new byte[16];
- random.nextBytes(bytes);
- return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
- }
-
/**
* Returns list of sdks {@code packageName} uses
*/
@@ -331,7 +400,7 @@ class SdkSandboxStorageManager {
continue;
}
final CallingInfo callingInfo = new CallingInfo(uid, packageName);
- reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/false);
+ reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/ false);
}
}
@@ -406,7 +475,7 @@ class SdkSandboxStorageManager {
*
* <ul>
* <li>Sdk sub-directory: belongs exclusively to individual sdk and has name <sdk>@random
- * <li>Non-sdk sub-directory: not specific to a particular sdk. Can belong to other entities.
+ * <li>Internal sub-directory: not specific to a particular sdk. Can belong to other entities.
* Typically has structure <name>#random. The only exception being shared storage which is
* just named "shared".
* </ul>
@@ -417,22 +486,23 @@ class SdkSandboxStorageManager {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static class SubDirectories {
- private static final String SHARED_DIR = "shared";
- private static final ArraySet<String> NON_SDK_SUBDIRS =
- new ArraySet(Arrays.asList(SHARED_DIR));
+ static final String SHARED_DIR = "shared";
+ static final String SANDBOX_DIR = "sandbox";
+ static final ArraySet<String> INTERNAL_SUBDIRS =
+ new ArraySet(Arrays.asList(SHARED_DIR, SANDBOX_DIR));
private final String mBaseDir;
private final ArrayMap<String, String> mSdkSubDirs;
- private final ArrayMap<String, String> mNonSdkSubDirs;
+ private final ArrayMap<String, String> mInternalSubDirs;
private boolean mHasUnknownSubDirs = false;
/**
- * Lists all the children of provided path and organizes them into sdk and non-sdk group.
+ * Lists all the children of provided path and organizes them into sdk and internal group.
*/
SubDirectories(String path) {
mBaseDir = path;
mSdkSubDirs = new ArrayMap<>();
- mNonSdkSubDirs = new ArrayMap<>();
+ mInternalSubDirs = new ArrayMap<>();
final File parent = new File(path);
final String[] children = parent.list();
@@ -444,8 +514,11 @@ class SdkSandboxStorageManager {
if (child.indexOf("@") != -1) {
final String[] tokens = child.split("@");
mSdkSubDirs.put(tokens[0], child);
+ } else if (child.indexOf("#") != -1) {
+ final String[] tokens = child.split("#");
+ mInternalSubDirs.put(tokens[0], child);
} else if (child.equals(SHARED_DIR)) {
- mNonSdkSubDirs.put(SHARED_DIR, SHARED_DIR);
+ mInternalSubDirs.put(SHARED_DIR, SHARED_DIR);
} else {
mHasUnknownSubDirs = true;
}
@@ -466,14 +539,79 @@ class SdkSandboxStorageManager {
return Paths.get(mBaseDir, subDir).toString();
}
+ /** Gets the full path of internal storage directory with random suffix */
+ @Nullable
+ public String getInternalSubDir(String subDirName, boolean fullPath) {
+ final String subDir = mInternalSubDirs.getOrDefault(subDirName, null);
+ if (subDir == null || !fullPath) return subDir;
+ return Paths.get(mBaseDir, subDir).toString();
+ }
+
/**
* Provided a list of sdk names, verifies if the current collection of directories satisfies
- * per-sdk and non-sdk sub-directory requirements.
+ * per-sdk and internal sub-directory requirements.
*/
public boolean isValid(Set<String> expectedSdkNames) {
final boolean hasCorrectSdkSubDirs = mSdkSubDirs.keySet().equals(expectedSdkNames);
- final boolean hasCorrectNonSdkSubDirs = mNonSdkSubDirs.keySet().equals(NON_SDK_SUBDIRS);
- return hasCorrectSdkSubDirs && hasCorrectNonSdkSubDirs && !mHasUnknownSubDirs;
+ final boolean hasCorrectInternalSubDirs =
+ mInternalSubDirs.keySet().equals(INTERNAL_SUBDIRS);
+ return hasCorrectSdkSubDirs && hasCorrectInternalSubDirs && !mHasUnknownSubDirs;
+ }
+
+ /**
+ * Give the sdk names, generate sub-dir names for these sdks and sub-dirs for internal use.
+ *
+ * <p>Random suffix for existing directories are re-used.
+ */
+ public List<String> generateSubDirNames(List<String> sdkNames) {
+ final List<String> result = new ArrayList<>();
+
+ // Populate sub-dirs for internal use
+ for (int i = 0; i < INTERNAL_SUBDIRS.size(); i++) {
+ final String subDirValue = INTERNAL_SUBDIRS.valueAt(i);
+ final String subDirName = getOrGenerateInternalSubDir(subDirValue);
+ result.add(subDirName);
+ }
+
+ // Populate sub-dirs for per-sdk usage
+ for (int i = 0; i < sdkNames.size(); i++) {
+ final String sdkName = sdkNames.get(i);
+ final String subDirName = getOrGenerateSdkSubDir(sdkName);
+ result.add(subDirName);
+ }
+
+ return result;
+ }
+
+ public ArrayList<String> getSdkNames() {
+ ArrayList<String> sdkNames = new ArrayList<>();
+ for (int i = 0; i < mSdkSubDirs.size(); i++) {
+ sdkNames.add(mSdkSubDirs.keyAt(i));
+ }
+ return sdkNames;
+ }
+
+ private String getOrGenerateSdkSubDir(String sdkName) {
+ final String subDir = getSdkSubDir(sdkName);
+ if (subDir != null) return subDir;
+ return sdkName + "@" + getRandomString();
+ }
+
+ private String getOrGenerateInternalSubDir(String internalDirName) {
+ if (internalDirName.equals(SHARED_DIR)) {
+ return SHARED_DIR;
+ }
+ final String subDir = mInternalSubDirs.getOrDefault(internalDirName, null);
+ if (subDir != null) return subDir;
+ return internalDirName + "#" + getRandomString();
+ }
+
+ // Returns a random string.
+ private static String getRandomString() {
+ SecureRandom random = new SecureRandom();
+ byte[] bytes = new byte[16];
+ random.nextBytes(bytes);
+ return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
}
}
@@ -611,16 +749,16 @@ class SdkSandboxStorageManager {
}
/**
- * Sdk data directories for a particular sdk.
+ * Sdk data directories for a particular sdk or internal usage.
*
- * Every sdk has two data directories. One is credentially encrypted storage and another is
- * device encrypted.
+ * <p>Every sdk sub-directory has two data directories. One is credentially encrypted storage
+ * and another is device encrypted.
*/
- static class SdkDataDirInfo {
+ static class StorageDirInfo {
@Nullable final String mCeData;
@Nullable final String mDeData;
- SdkDataDirInfo(@Nullable String ceDataPath, @Nullable String deDataPath) {
+ StorageDirInfo(@Nullable String ceDataPath, @Nullable String deDataPath) {
mCeData = ceDataPath;
mDeData = deDataPath;
}
@@ -632,5 +770,18 @@ class SdkSandboxStorageManager {
@Nullable String getDeDataDir() {
return mDeData;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof StorageDirInfo)) return false;
+ StorageDirInfo that = (StorageDirInfo) o;
+ return mCeData.equals(that.mCeData) && mDeData.equals(that.mDeData);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCeData, mDeData);
+ }
}
}
diff --git a/sdksandbox/tests/cts/endtoendtests/Android.bp b/sdksandbox/tests/cts/endtoendtests/Android.bp
index 6a3724fd6..d7bdee90e 100644
--- a/sdksandbox/tests/cts/endtoendtests/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/Android.bp
@@ -34,6 +34,7 @@ android_test {
],
data: [
":LoadSdkSuccessfullySdkProvider",
+ ":LoadSdkAndCheckClassloaderSdkProvider",
":GetLoadedSdkLibInfoSuccessfully",
":LoadSdkSuccessfullySdkProviderTwo",
":LoadSdkWithInternalErrorSdkProvider",
@@ -42,7 +43,7 @@ android_test {
":CodeProviderWithResources",
],
min_sdk_version: "Tiramisu",
- target_sdk_version: "current",
+ target_sdk_version: "Tiramisu",
test_suites: [
"cts",
"mts-adservices",
diff --git a/sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml b/sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml
index 995a6ae69..9602549ea 100644
--- a/sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml
+++ b/sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml
@@ -21,20 +21,23 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX" />
- <application android:debuggable="true">
- <uses-library android:name="android.test.runner" />
+ <application>
+ <uses-library android:name="android.test.runner"/>
<uses-sdk-library android:name="com.android.loadSdkSuccessfullySdkProvider"
android:versionMajor="1"
- android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99"/>
+ <uses-sdk-library android:name="com.android.loadSdkAndCheckClassloader"
+ android:versionMajor="1"
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99"/>
<uses-sdk-library android:name="com.android.getLoadedSdkLibInfoSuccessfully"
android:versionMajor="1"
- android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99"/>
<uses-sdk-library android:name="com.android.loadSdkSuccessfullySdkProviderTwo"
android:versionMajor="1"
- android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99"/>
<uses-sdk-library android:name="com.android.loadSdkWithInternalErrorSdkProvider"
android:versionMajor="1"
- android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99"/>
<uses-sdk-library android:name="com.android.requestSurfacePackageSuccessfullySdkProvider"
android:versionMajor="1"
android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
diff --git a/sdksandbox/tests/cts/endtoendtests/AndroidTest.xml b/sdksandbox/tests/cts/endtoendtests/AndroidTest.xml
index 5ecaadb1d..78c5ae1c2 100644
--- a/sdksandbox/tests/cts/endtoendtests/AndroidTest.xml
+++ b/sdksandbox/tests/cts/endtoendtests/AndroidTest.xml
@@ -20,9 +20,16 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app"/>
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi"/>
<option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="LoadSdkSuccessfullySdkProvider.apk"/>
+ <option name="test-file-name" value="LoadSdkAndCheckClassloaderSdkProvider.apk"/>
<option name="test-file-name" value="GetLoadedSdkLibInfoSuccessfully.apk"/>
<option name="test-file-name" value="LoadSdkSuccessfullySdkProviderTwo.apk"/>
<option name="test-file-name" value="LoadSdkWithInternalErrorSdkProvider.apk"/>
diff --git a/sdksandbox/tests/cts/endtoendtests/DisabledAndroidManifest.xml b/sdksandbox/tests/cts/endtoendtests/DisabledAndroidManifest.xml
index 24d32fb63..887444961 100644
--- a/sdksandbox/tests/cts/endtoendtests/DisabledAndroidManifest.xml
+++ b/sdksandbox/tests/cts/endtoendtests/DisabledAndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <application android:debuggable="true">
+ <application>
<uses-library android:name="android.test.runner" />
<uses-sdk-library android:name="com.android.loadSdkSuccessfullySdkProvider"
android:versionMajor="1"
diff --git a/sdksandbox/tests/cts/endtoendtests/DisabledAndroidTest.xml b/sdksandbox/tests/cts/endtoendtests/DisabledAndroidTest.xml
index 72a47d038..3222d1b49 100644
--- a/sdksandbox/tests/cts/endtoendtests/DisabledAndroidTest.xml
+++ b/sdksandbox/tests/cts/endtoendtests/DisabledAndroidTest.xml
@@ -20,6 +20,12 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app"/>
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi"/>
<option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="LoadSdkSuccessfullySdkProvider.apk"/>
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/getLoadedSdkLibInfoSuccessfully/Android.bp b/sdksandbox/tests/cts/endtoendtests/providers/getLoadedSdkLibInfoSuccessfully/Android.bp
index a1d970af3..21ce59d1c 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/getLoadedSdkLibInfoSuccessfully/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/providers/getLoadedSdkLibInfoSuccessfully/Android.bp
@@ -23,5 +23,6 @@ android_test_helper_app {
srcs: [
"src/**/*.java",
],
- platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/Android.bp b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/Android.bp
new file mode 100644
index 000000000..12ee53de0
--- /dev/null
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/Android.bp
@@ -0,0 +1,31 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "LoadSdkAndCheckClassloaderSdkProvider",
+ defaults: ["platform_app_defaults",],
+ certificate: ":sdksandbox-test",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "SdkSandbox-java-lib",
+ ],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/AndroidManifest.xml b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/AndroidManifest.xml
new file mode 100644
index 000000000..922bf0be2
--- /dev/null
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.loadsdkandcheckclassloadersdkprovider">
+
+ <application>
+ <sdk-library android:name="com.android.loadSdkAndCheckClassloader"
+ android:versionMajor="1"/>
+ <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
+ android:value="com.android.loadsdkandcheckclassloadersdkprovider.SdkProvider"/>
+ </application>
+</manifest>
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/src/com/android/loadsdkandcheckclassloadersdkprovider/SdkProvider.java b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/src/com/android/loadsdkandcheckclassloadersdkprovider/SdkProvider.java
new file mode 100644
index 000000000..e4fdd7dce
--- /dev/null
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkAndCheckClassloader/src/com/android/loadsdkandcheckclassloadersdkprovider/SdkProvider.java
@@ -0,0 +1,58 @@
+/*
+ * 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.loadsdkandcheckclassloadersdkprovider;
+
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.sdksandbox.SdkSandboxServiceImpl;
+
+public class SdkProvider extends SandboxedSdkProvider {
+
+ @Override
+ public SandboxedSdk onLoadSdk(Bundle params) {
+ final ClassLoader ownClassloader = getClass().getClassLoader();
+ if (ownClassloader == null) {
+ throw new RuntimeException("SdkProvider loaded in top-level classloader");
+ }
+
+ final ClassLoader contextClassloader = getContext().getClassLoader();
+ if (!ownClassloader.equals(contextClassloader)) {
+ throw new RuntimeException("Different SdkProvider and Context classloaders");
+ }
+
+ try {
+ Class<?> loadedClazz = ownClassloader.loadClass(SdkSandboxServiceImpl.class.getName());
+ if (!ownClassloader.equals(loadedClazz.getClassLoader())) {
+ throw new RuntimeException("SdkSandboxServiceImpl loaded with wrong classloader");
+ }
+ } catch (ClassNotFoundException ex) {
+ throw new RuntimeException("Couldn't find class bundled with SdkProvider", ex);
+ }
+
+ return new SandboxedSdk(new Binder());
+ }
+
+ @Override
+ public View getView(Context windowContext, Bundle params, int width, int height) {
+ return null;
+ }
+}
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfully/Android.bp b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfully/Android.bp
index 983e909b0..96bef68b8 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfully/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfully/Android.bp
@@ -23,5 +23,6 @@ android_test_helper_app {
srcs: [
"src/**/*.java",
],
- platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/Android.bp b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/Android.bp
index 95b278523..5689a8696 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/Android.bp
@@ -23,5 +23,6 @@ android_test_helper_app {
srcs: [
"src/**/*.java",
],
- platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkWithInternalError/Android.bp b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkWithInternalError/Android.bp
index ec948a6f9..073c7deef 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/loadSdkWithInternalError/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkWithInternalError/Android.bp
@@ -23,5 +23,6 @@ android_test_helper_app {
srcs: [
"src/**/*.java",
],
- platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageSuccessfully/Android.bp b/sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageSuccessfully/Android.bp
index 4b0b7f981..dcb0a31a1 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageSuccessfully/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageSuccessfully/Android.bp
@@ -23,5 +23,6 @@ android_test_helper_app {
srcs: [
"src/**/*.java",
],
- platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageWithInternalError/Android.bp b/sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageWithInternalError/Android.bp
index 9a7a22117..3cdffc0ac 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageWithInternalError/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/providers/requestSurfacePackageWithInternalError/Android.bp
@@ -23,5 +23,6 @@ android_test_helper_app {
srcs: [
"src/**/*.java",
],
- platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/disablede2e/SdkSandboxManagerDisabledTest.java b/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/disablede2e/SdkSandboxManagerDisabledTest.java
index dcd642336..850e69923 100644
--- a/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/disablede2e/SdkSandboxManagerDisabledTest.java
+++ b/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/disablede2e/SdkSandboxManagerDisabledTest.java
@@ -59,7 +59,7 @@ public class SdkSandboxManagerDisabledTest {
@Test
public void testSdkSandboxDisabledErrorCode() throws Exception {
DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_SDK_SANDBOX, "disable_sdk_sandbox", "true", false);
+ DeviceConfig.NAMESPACE_ADSERVICES, "disable_sdk_sandbox", "true", false);
// Allow time for DeviceConfig change to propagate
Thread.sleep(1000);
final String sdkName = "com.android.loadSdkSuccessfullySdkProvider";
diff --git a/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java b/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java
index b882c6fa7..4e85e8cfe 100644
--- a/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java
+++ b/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java
@@ -53,6 +53,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.List;
+
/*
* TODO(b/215372846): These providers
* (RequestSurfacePackageSuccessfullySdkProvider, RetryLoadSameSdkShouldFailSdkProvider) could be
@@ -98,6 +100,39 @@ public class SdkSandboxManagerTest {
}
@Test
+ public void getSandboxedSdkSuccessfully() {
+ final String sdkName = "com.android.loadSdkSuccessfullySdkProvider";
+ final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+ mSdkSandboxManager.loadSdk(sdkName, new Bundle(), Runnable::run, callback);
+ assertThat(callback.isLoadSdkSuccessful(/*ignoreSdkAlreadyLoadedError=*/ true)).isTrue();
+
+ List<SandboxedSdk> sandboxedSdks = mSdkSandboxManager.getSandboxedSdks();
+
+ int nLoadedSdks = sandboxedSdks.size();
+ assertThat(nLoadedSdks).isGreaterThan(0);
+ assertThat(
+ sandboxedSdks.stream()
+ .filter(s -> s.getSharedLibraryInfo().getName().equals(sdkName))
+ .count())
+ .isEqualTo(1);
+
+ mSdkSandboxManager.unloadSdk(sdkName);
+ List<SandboxedSdk> sandboxedSdksAfterUnload = mSdkSandboxManager.getSandboxedSdks();
+ assertThat(sandboxedSdksAfterUnload.size()).isEqualTo(nLoadedSdks - 1);
+ }
+
+ @Test
+ public void loadSdkAndCheckClassloader() {
+ final String sdkName = "com.android.loadSdkAndCheckClassloader";
+ final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+ mSdkSandboxManager.loadSdk(sdkName, new Bundle(), Runnable::run, callback);
+ assertThat(callback.isLoadSdkSuccessful()).isTrue();
+ assertNotNull(callback.getSandboxedSdk());
+ assertNotNull(callback.getSandboxedSdk().getInterface());
+ mSdkSandboxManager.unloadSdk(sdkName);
+ }
+
+ @Test
public void retryLoadSameSdkShouldFail() {
final String sdkName = "com.android.loadSdkSuccessfullySdkProviderTwo";
FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
@@ -143,23 +178,28 @@ public class SdkSandboxManagerTest {
}
@Test
- public void unloadAndReloadSdk() {
+ public void unloadAndReloadSdk() throws Exception {
final String sdkName = "com.android.loadSdkSuccessfullySdkProvider";
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(sdkName, new Bundle(), Runnable::run, callback);
assertThat(callback.isLoadSdkSuccessful()).isTrue();
mSdkSandboxManager.unloadSdk(sdkName);
+ // Wait till SDK is unloaded.
+ Thread.sleep(2000);
- // Calls to an unloaded SDK should throw an exception.
+ // Calls to an unloaded SDK should fail.
final FakeRequestSurfacePackageCallback requestSurfacePackageCallback =
new FakeRequestSurfacePackageCallback();
- Bundle params = getRequestSurfacePackageParams();
- assertThrows(
- IllegalArgumentException.class,
- () ->
- mSdkSandboxManager.requestSurfacePackage(
- sdkName, params, Runnable::run, requestSurfacePackageCallback));
+ mSdkSandboxManager.requestSurfacePackage(
+ sdkName,
+ getRequestSurfacePackageParams(),
+ Runnable::run,
+ requestSurfacePackageCallback);
+
+ assertThat(requestSurfacePackageCallback.isRequestSurfacePackageSuccessful()).isFalse();
+ assertThat(requestSurfacePackageCallback.getSurfacePackageErrorCode())
+ .isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED);
// SDK can be reloaded after being unloaded.
final FakeLoadSdkCallback callback2 = new FakeLoadSdkCallback();
@@ -168,7 +208,7 @@ public class SdkSandboxManagerTest {
}
@Test
- public void unloadingNonexistentSdkThrowsException() {
+ public void unloadNonexistentSdk() {
final String sdkName1 = "com.android.loadSdkSuccessfullySdkProvider";
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(sdkName1, new Bundle(), Runnable::run, callback);
@@ -176,7 +216,8 @@ public class SdkSandboxManagerTest {
assertThat(callback.isLoadSdkSuccessful(/*ignoreSdkAlreadyLoadedError=*/ true)).isTrue();
final String sdkName2 = "com.android.nonexistent";
- assertThrows(IllegalArgumentException.class, () -> mSdkSandboxManager.unloadSdk(sdkName2));
+ // Unloading does nothing - call should go throw without error.
+ mSdkSandboxManager.unloadSdk(sdkName2);
}
@Test
@@ -362,7 +403,7 @@ public class SdkSandboxManagerTest {
sdkName, getRequestSurfacePackageParams(), Runnable::run, surfacePackageCallback);
assertThat(surfacePackageCallback.isRequestSurfacePackageSuccessful()).isFalse();
assertThat(surfacePackageCallback.getSurfacePackageErrorCode())
- .isEqualTo(SdkSandboxManager.SDK_SANDBOX_PROCESS_NOT_AVAILABLE);
+ .isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED);
}
@Test
diff --git a/sdksandbox/tests/cts/hostside/Android.bp b/sdksandbox/tests/cts/hostside/Android.bp
new file mode 100644
index 000000000..5f96bba5f
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/Android.bp
@@ -0,0 +1,116 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsSdkSandboxHostSideTests",
+ srcs: [
+ "src/**/*.java",
+ ":CtsHostsideTestsAppSecurityUtil",
+ ],
+ libs: ["tradefed"],
+ static_libs: [
+ "SdkSandboxHostTestUtils",
+ ],
+ test_suites: [
+ "cts",
+ "mts-adservices",
+ "general-tests",
+ ],
+ data: [
+ ":CtsSdkSandboxHostTestApp",
+ ":CtsSdkSandboxHostTestApp2",
+ ":SdkSandboxDataIsolationTestProvider",
+ ":SdkSandboxStorageTestProvider",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSdkSandboxHostTestApp",
+ manifest: "app/AndroidManifest.xml",
+ defaults: ["platform_app_defaults"],
+ srcs: [
+ "app/src/**/*.java",
+ ],
+ libs: [
+ "framework-sdksandbox.impl",
+ ],
+ static_libs: [
+ "CtsStorageTestSdkApi",
+ "CtsDataIsolationTestSdkApi",
+ "SdkSandboxTestUtils",
+ "androidx.core_core",
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ ],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
+
+android_test_helper_app {
+ name: "CtsSdkSandboxHostTestApp2",
+ manifest: "app/AndroidManifest2.xml",
+ defaults: ["platform_app_defaults"],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
+
+android_test_helper_app {
+ name: "SdkSandboxDataIsolationTestProvider",
+ manifest: "provider/AndroidManifest.xml",
+ defaults: ["platform_app_defaults"],
+ certificate: ":sdksandbox-test",
+ srcs: [
+ "provider/src/**/dataisolationtest/*.java",
+ ],
+ static_libs: [
+ "CtsDataIsolationTestSdkApi",
+ "compatibility-device-util-axt",
+ ],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
+
+java_library {
+ name: "CtsDataIsolationTestSdkApi",
+ srcs: [
+ "provider/src/**/dataisolationtest/*.aidl",
+ ],
+}
+
+android_test_helper_app {
+ name: "SdkSandboxStorageTestProvider",
+ manifest: "provider/StorageTestManifest.xml",
+ defaults: ["platform_app_defaults"],
+ certificate: ":sdksandbox-test",
+ srcs: [
+ "provider/src/**/storagetest/*.java",
+ ],
+ static_libs: [
+ "CtsStorageTestSdkApi",
+ "compatibility-device-util-axt",
+ ],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
+
+java_library {
+ name: "CtsStorageTestSdkApi",
+ srcs: [
+ "provider/src/**/storagetest/*.aidl",
+ ],
+}
diff --git a/sdksandbox/tests/cts/hostside/AndroidTest.xml b/sdksandbox/tests/cts/hostside/AndroidTest.xml
new file mode 100644
index 000000000..51dec44ba
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/AndroidTest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Runs sdk sandbox cts host tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi"/>
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="SdkSandboxDataIsolationTestProvider.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="SdkSandboxStorageTestProvider.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="cmd sdk_sandbox set-state --enabled" />
+ <option name="teardown-command" value="cmd sdk_sandbox set-state --reset" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class"
+ value="com.android.sdksandbox.cts.host.SdkSandboxDataIsolationHostTest" />
+ </test>
+
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class"
+ value="com.android.sdksandbox.cts.host.SdkSandboxStorageHostTest" />
+ </test>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController" >
+ <option name="mainline-module-package-name" value="com.google.android.adservices" />
+ </object>
+
+ <option name="config-descriptor:metadata" key="mainline-param"
+ value="com.google.android.adservices.apex" />
+</configuration>
diff --git a/sdksandbox/tests/cts/hostside/app/AndroidManifest.xml b/sdksandbox/tests/cts/hostside/app/AndroidManifest.xml
new file mode 100644
index 000000000..525f87b88
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/app/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.sdksandbox.cts.app">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+ <application>
+
+ <uses-sdk-library
+ android:name="com.android.sdksandbox.cts.provider.dataisolationtest"
+ android:versionMajor="1"
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99"
+ />
+
+ <uses-sdk-library android:name="com.android.sdksandbox.cts.provider.storagetest"
+ android:versionMajor="1"
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99"
+ />
+
+ <activity android:name=".TestActivity" android:exported="true"
+ android:turnScreenOn="true"
+ android:keepScreenOn="true"
+ android:showWhenLocked="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.sdksandbox.cts.app"
+ android:label="CtsSdkSandboxHostTestApp"/>
+</manifest>
diff --git a/sdksandbox/tests/cts/hostside/app/AndroidManifest2.xml b/sdksandbox/tests/cts/hostside/app/AndroidManifest2.xml
new file mode 100644
index 000000000..3f2af2ecc
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/app/AndroidManifest2.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.sdksandbox.cts.app2">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <application>
+ <uses-sdk-library
+ android:name="com.android.sdksandbox.cts.provider.dataisolationtest"
+ android:versionMajor="1"
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99"
+ />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.sdksandbox.cts.app2"
+ android:label="CtsSdkSandboxHostTestApp2"/>
+</manifest>
diff --git a/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/SdkSandboxDataIsolationTestApp.java b/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/SdkSandboxDataIsolationTestApp.java
new file mode 100644
index 000000000..18a1f9312
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/SdkSandboxDataIsolationTestApp.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.sdksandbox.cts.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.sdksandbox.SdkSandboxManager;
+import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
+import android.os.Bundle;
+import android.os.Process;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.sdksandbox.cts.provider.dataisolationtest.IDataIsolationTestSdkApi;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+@RunWith(JUnit4.class)
+public class SdkSandboxDataIsolationTestApp {
+
+ private static final String APP_PKG = "com.android.sdksandbox.cts.app";
+ private static final String APP_2_PKG = "com.android.sdksandbox.cts.app2";
+
+ private static final String CURRENT_USER_ID =
+ String.valueOf(Process.myUserHandle().getUserId(Process.myUid()));
+
+ private static final String SDK_NAME = "com.android.sdksandbox.cts.provider.dataisolationtest";
+
+ private static final String BUNDLE_KEY_PHASE_NAME = "phase-name";
+
+ private static final String JAVA_FILE_PERMISSION_DENIED_MSG =
+ "open failed: EACCES (Permission denied)";
+ private static final String JAVA_FILE_NOT_FOUND_MSG =
+ "open failed: ENOENT (No such file or directory)";
+
+ private SdkSandboxManager mSdkSandboxManager;
+
+ private IDataIsolationTestSdkApi mSdk;
+
+ @Rule public final ActivityScenarioRule mRule = new ActivityScenarioRule<>(TestActivity.class);
+
+ @Before
+ public void setup() {
+ mRule.getScenario();
+ mSdkSandboxManager =
+ ApplicationProvider.getApplicationContext()
+ .getSystemService(SdkSandboxManager.class);
+ assertThat(mSdkSandboxManager).isNotNull();
+ }
+
+ @Test
+ public void testAppCannotAccessAnySandboxDirectories() throws Exception {
+ assertFileAccessIsDenied("/data/misc_ce/" + CURRENT_USER_ID + "/sdksandbox/" + APP_PKG);
+ assertFileAccessIsDenied("/data/misc_ce/" + CURRENT_USER_ID + "/sdksandbox/" + APP_2_PKG);
+ assertFileAccessIsDenied("/data/misc_ce/" + CURRENT_USER_ID + "/sdksandbox/does.not.exist");
+
+ assertFileAccessIsDenied("/data/misc_de/" + CURRENT_USER_ID + "/sdksandbox/" + APP_PKG);
+ assertFileAccessIsDenied("/data/misc_de/" + CURRENT_USER_ID + "/sdksandbox/" + APP_2_PKG);
+ assertFileAccessIsDenied("/data/misc_de/" + CURRENT_USER_ID + "/sdksandbox/does.not.exist");
+ }
+
+ @Test
+ public void testSdkSandboxDataIsolation_SandboxCanAccessItsDirectory() throws Exception {
+ loadSdk();
+ mSdk.testSdkSandboxDataIsolation_SandboxCanAccessItsDirectory();
+ }
+
+ private void loadSdk() {
+ FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+ mSdkSandboxManager.loadSdk(SDK_NAME, new Bundle(), Runnable::run, callback);
+ assertThat(callback.isLoadSdkSuccessful()).isTrue();
+
+ // Store the returned SDK interface so that we can interact with it later.
+ mSdk = IDataIsolationTestSdkApi.Stub.asInterface(callback.getSandboxedSdk().getInterface());
+ }
+
+ private static void assertFileAccessIsDenied(String path) {
+ File file = new File(path);
+
+ // Trying to access a file that does not exist in that directory, it should return
+ // permission denied file not found.
+ Exception exception =
+ assertThrows(
+ FileNotFoundException.class,
+ () -> {
+ new FileInputStream(file);
+ });
+ assertThat(exception.getMessage()).contains(JAVA_FILE_PERMISSION_DENIED_MSG);
+ assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_NOT_FOUND_MSG);
+
+ assertThat(file.canExecute()).isFalse();
+ }
+}
diff --git a/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/SdkSandboxStorageTestApp.java b/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/SdkSandboxStorageTestApp.java
new file mode 100644
index 000000000..bef1e590e
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/SdkSandboxStorageTestApp.java
@@ -0,0 +1,166 @@
+/*
+ * 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.sdksandbox.cts.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.sdksandbox.SdkSandboxManager;
+import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.sdksandbox.cts.provider.storagetest.IStorageTestSdkApi;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class SdkSandboxStorageTestApp {
+
+ private static final String SDK_NAME = "com.android.sdksandbox.cts.provider.storagetest";
+
+ @Rule public final ActivityScenarioRule mRule = new ActivityScenarioRule<>(TestActivity.class);
+
+ private static final String KEY_TO_SYNC = "hello";
+ private static final String BULK_SYNC_VALUE = "bulksync";
+ private static final String UPDATE_VALUE = "update";
+
+ private Context mContext;
+ private SdkSandboxManager mSdkSandboxManager;
+ private IStorageTestSdkApi mSdk;
+
+ @Before
+ public void setup() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mSdkSandboxManager = mContext.getSystemService(SdkSandboxManager.class);
+ assertThat(mSdkSandboxManager).isNotNull();
+ mRule.getScenario();
+ }
+
+ @Test
+ public void loadSdk() throws Exception {
+ FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+ mSdkSandboxManager.loadSdk(SDK_NAME, new Bundle(), Runnable::run, callback);
+ assertThat(callback.isLoadSdkSuccessful()).isTrue();
+
+ // Store the returned SDK interface so that we can interact with it later.
+ mSdk = IStorageTestSdkApi.Stub.asInterface(callback.getSandboxedSdk().getInterface());
+ }
+
+ @Test
+ public void testSharedPreferences_IsSyncedFromAppToSandbox() throws Exception {
+ loadSdk();
+
+ // Write to default shared preference
+ final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
+ pref.edit().putString(KEY_TO_SYNC, BULK_SYNC_VALUE).commit();
+
+ // Start syncing keys
+ mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(KEY_TO_SYNC));
+ // Allow some time for data to sync
+ Thread.sleep(1000);
+
+ // Verify same key can be read from the sandbox
+ final String syncedValueInSandbox = mSdk.getSyncedSharedPreferencesString(KEY_TO_SYNC);
+ assertThat(syncedValueInSandbox).isEqualTo(BULK_SYNC_VALUE);
+ }
+
+ @Test
+ public void testSharedPreferences_SyncPropagatesUpdates() throws Exception {
+ loadSdk();
+
+ // Start syncing keys
+ mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(KEY_TO_SYNC));
+
+ // Update the default SharedPreferences
+ final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
+ pref.edit().putString(KEY_TO_SYNC, UPDATE_VALUE).commit();
+ // Allow some time for data to sync
+ for (int i = 0; i < 10; i++) {
+ Thread.sleep(500);
+
+ // Verify update was propagated
+ final String syncedValueInSandbox = mSdk.getSyncedSharedPreferencesString(KEY_TO_SYNC);
+ if (syncedValueInSandbox.equals(UPDATE_VALUE)) {
+ return;
+ }
+ }
+ fail("failed to sync value in 5 seconds");
+ }
+
+ @Test
+ public void testSharedPreferences_SyncStartedBeforeLoadingSdk() throws Exception {
+ // Write to default shared preference
+ final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
+ pref.edit().putString(KEY_TO_SYNC, BULK_SYNC_VALUE).commit();
+
+ // Start syncing keys
+ mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(KEY_TO_SYNC));
+
+ // Load Sdk so that sandbox is started
+ loadSdk();
+ // Allow some time for data to sync
+ Thread.sleep(1000);
+
+ // Verify same key can be read from the sandbox
+ final String syncedValueInSandbox = mSdk.getSyncedSharedPreferencesString(KEY_TO_SYNC);
+ assertThat(syncedValueInSandbox).isEqualTo(BULK_SYNC_VALUE);
+ }
+
+ @Test
+ public void testSharedPreferences_SyncRemoveKeys() throws Exception {
+ loadSdk();
+
+ // Write to default shared preference
+ final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
+ pref.edit().putString(KEY_TO_SYNC, BULK_SYNC_VALUE).commit();
+
+ // Start syncing keys
+ mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(KEY_TO_SYNC));
+
+ // Remove the key
+ mSdkSandboxManager.removeSyncedSharedPreferencesKeys(Set.of(KEY_TO_SYNC));
+
+ // Allow some time for data to sync
+ Thread.sleep(1000);
+
+ // Verify key has been removed from the sandbox
+ final String syncedValueInSandbox = mSdk.getSyncedSharedPreferencesString(KEY_TO_SYNC);
+ assertThat(syncedValueInSandbox).isEmpty();
+ }
+
+ @Test
+ public void testSharedPreferences_SyncedDataClearedOnSandboxRestart() throws Exception {
+ loadSdk();
+
+ // Verify previously synced keys are not available in sandbox anymore
+ final String syncedValueInSandbox = mSdk.getSyncedSharedPreferencesString(KEY_TO_SYNC);
+ assertThat(syncedValueInSandbox).isEmpty();
+ }
+}
diff --git a/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/TestActivity.java b/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/TestActivity.java
new file mode 100644
index 000000000..f793e7f87
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/app/src/com/android/sdksandbox/cts/app/TestActivity.java
@@ -0,0 +1,21 @@
+/*
+ * 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.sdksandbox.cts.app;
+
+import android.app.Activity;
+
+public class TestActivity extends Activity {}
diff --git a/sdksandbox/tests/cts/hostside/provider/AndroidManifest.xml b/sdksandbox/tests/cts/hostside/provider/AndroidManifest.xml
new file mode 100644
index 000000000..e7a7dd998
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/provider/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.sdksandbox.cts.provider.dataisolationtest">
+
+ <application>
+ <sdk-library android:name="com.android.sdksandbox.cts.provider.dataisolationtest" android:versionMajor="1" />
+ <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
+ android:value="com.android.sdksandbox.cts.provider.dataisolationtest.SdkSandboxDataIsolationTestProvider" />
+ </application>
+</manifest> \ No newline at end of file
diff --git a/sdksandbox/tests/cts/hostside/provider/StorageTestManifest.xml b/sdksandbox/tests/cts/hostside/provider/StorageTestManifest.xml
new file mode 100644
index 000000000..14b1d06b6
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/provider/StorageTestManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.sdksandbox.cts.provider.storagetest">
+
+ <application>
+ <sdk-library android:name="com.android.sdksandbox.cts.provider.storagetest" android:versionMajor="1" />
+ <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
+ android:value="com.android.sdksandbox.cts.provider.storagetest.SdkSandboxStorageTestProvider" />
+ </application>
+</manifest>
diff --git a/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/DataIsolationTestSdkApiImpl.java b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/DataIsolationTestSdkApiImpl.java
new file mode 100644
index 000000000..273ee0042
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/DataIsolationTestSdkApiImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.sdksandbox.cts.provider.dataisolationtest;
+
+import android.content.Context;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+public class DataIsolationTestSdkApiImpl extends IDataIsolationTestSdkApi.Stub {
+ private final Context mContext;
+
+ private static final String TAG = "SdkSandboxDataIsolationTestProvider";
+
+ private static final String JAVA_FILE_PERMISSION_DENIED_MSG =
+ "open failed: EACCES (Permission denied)";
+ private static final String JAVA_FILE_NOT_FOUND_MSG =
+ "open failed: ENOENT (No such file or directory)";
+ private static final String JAVA_IS_A_DIRECTORY_ERROR_MSG =
+ "open failed: EISDIR (Is a directory)";
+
+ public DataIsolationTestSdkApiImpl(Context sdkContext) {
+ mContext = sdkContext;
+ }
+
+ @Override
+ public void testSdkSandboxDataIsolation_SandboxCanAccessItsDirectory() {
+ verifyDirectoryAccess(mContext.getDataDir().toString(), true);
+ }
+
+ private void verifyDirectoryAccess(String path, boolean shouldBeAccessible) {
+ File file = new File(path);
+ try {
+ new FileInputStream(file);
+ } catch (FileNotFoundException exception) {
+ String exceptionMsg = exception.getMessage();
+ if (shouldBeAccessible) {
+ if (!exceptionMsg.contains(JAVA_IS_A_DIRECTORY_ERROR_MSG)) {
+ throw new IllegalStateException(
+ path + " should be accessible, but received error: " + exceptionMsg);
+ }
+ } else if (!exceptionMsg.contains(JAVA_FILE_NOT_FOUND_MSG)
+ || exceptionMsg.contains(JAVA_FILE_PERMISSION_DENIED_MSG)
+ || exceptionMsg.contains(JAVA_IS_A_DIRECTORY_ERROR_MSG)) {
+ throw new IllegalStateException(
+ "Accessing "
+ + path
+ + " should have shown ENOENT error, but received error: "
+ + exceptionMsg);
+ }
+ }
+ }
+}
diff --git a/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/IDataIsolationTestSdkApi.aidl b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/IDataIsolationTestSdkApi.aidl
new file mode 100644
index 000000000..4c102ea25
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/IDataIsolationTestSdkApi.aidl
@@ -0,0 +1,19 @@
+// 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.sdksandbox.cts.provider.dataisolationtest;
+
+interface IDataIsolationTestSdkApi {
+ void testSdkSandboxDataIsolation_SandboxCanAccessItsDirectory();
+}
diff --git a/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/SdkSandboxDataIsolationTestProvider.java b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/SdkSandboxDataIsolationTestProvider.java
new file mode 100644
index 000000000..9280249cc
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/dataisolationtest/SdkSandboxDataIsolationTestProvider.java
@@ -0,0 +1,36 @@
+/*
+ * 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.sdksandbox.cts.provider.dataisolationtest;
+
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+
+public class SdkSandboxDataIsolationTestProvider extends SandboxedSdkProvider {
+
+ @Override
+ public SandboxedSdk onLoadSdk(Bundle params) {
+ return new SandboxedSdk(new DataIsolationTestSdkApiImpl(getContext()));
+ }
+
+ @Override
+ public View getView(Context windowContext, Bundle params, int width, int height) {
+ return null;
+ }
+}
diff --git a/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/IStorageTestSdkApi.aidl b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/IStorageTestSdkApi.aidl
new file mode 100644
index 000000000..64b1697dc
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/IStorageTestSdkApi.aidl
@@ -0,0 +1,19 @@
+// 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.sdksandbox.cts.provider.storagetest;
+
+interface IStorageTestSdkApi {
+ String getSyncedSharedPreferencesString(in String key);
+}
diff --git a/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/SdkSandboxStorageTestProvider.java b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/SdkSandboxStorageTestProvider.java
new file mode 100644
index 000000000..2863a9550
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/SdkSandboxStorageTestProvider.java
@@ -0,0 +1,36 @@
+/*
+ * 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.sdksandbox.cts.provider.storagetest;
+
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+
+public class SdkSandboxStorageTestProvider extends SandboxedSdkProvider {
+
+ @Override
+ public SandboxedSdk onLoadSdk(Bundle params) {
+ return new SandboxedSdk(new StorageTestSdkApiImpl(getContext()));
+ }
+
+ @Override
+ public View getView(Context windowContext, Bundle params, int width, int height) {
+ return null;
+ }
+}
diff --git a/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/StorageTestSdkApiImpl.java b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/StorageTestSdkApiImpl.java
new file mode 100644
index 000000000..bc1387a97
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/provider/src/com/android/sdksandbox/cts/provider/storagetest/StorageTestSdkApiImpl.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sdksandbox.cts.provider.storagetest;
+
+import android.app.sdksandbox.sdkprovider.SdkSandboxController;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class StorageTestSdkApiImpl extends IStorageTestSdkApi.Stub {
+ private final Context mContext;
+
+ public StorageTestSdkApiImpl(Context sdkContext) {
+ mContext = sdkContext;
+ }
+
+ @Override
+ public String getSyncedSharedPreferencesString(String key) {
+ return getClientSharedPreferences().getString(key, "");
+ }
+
+ private SharedPreferences getClientSharedPreferences() {
+ return mContext.getSystemService(SdkSandboxController.class).getClientSharedPreferences();
+ }
+}
diff --git a/sdksandbox/tests/cts/hostside/src/com/android/sdksandbox/cts/host/SdkSandboxDataIsolationHostTest.java b/sdksandbox/tests/cts/hostside/src/com/android/sdksandbox/cts/host/SdkSandboxDataIsolationHostTest.java
new file mode 100644
index 000000000..0f35b4628
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/src/com/android/sdksandbox/cts/host/SdkSandboxDataIsolationHostTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.sdksandbox.cts.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SdkSandboxDataIsolationHostTest extends BaseHostJUnit4Test {
+
+ private static final String APP_PACKAGE = "com.android.sdksandbox.cts.app";
+ private static final String APP_APK = "CtsSdkSandboxHostTestApp.apk";
+ private static final String APP_TEST_CLASS = APP_PACKAGE + ".SdkSandboxDataIsolationTestApp";
+
+ private static final String APP_2_PACKAGE = "com.android.sdksandbox.cts.app2";
+ private static final String APP_2_APK = "CtsSdkSandboxHostTestApp2.apk";
+
+ /**
+ * Runs the given phase of a test by calling into the device. Throws an exception if the test
+ * phase fails.
+ *
+ * <p>For example, <code>runPhase("testExample");</code>
+ */
+ private void runPhase(String phase) throws Exception {
+ assertThat(runDeviceTests(APP_PACKAGE, APP_TEST_CLASS, phase)).isTrue();
+ }
+
+ private void runPhase(
+ String phase, String instrumentationArgKey, String instrumentationArgValue)
+ throws Exception {
+ runDeviceTests(
+ new DeviceTestRunOptions(APP_PACKAGE)
+ .setDevice(getDevice())
+ .setTestClassName(APP_TEST_CLASS)
+ .setTestMethodName(phase)
+ .addInstrumentationArg(instrumentationArgKey, instrumentationArgValue));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // These tests run on system user
+ uninstallPackage(APP_PACKAGE);
+ uninstallPackage(APP_2_PACKAGE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ uninstallPackage(APP_PACKAGE);
+ uninstallPackage(APP_2_PACKAGE);
+ }
+
+ @Test
+ public void testAppCannotAccessAnySandboxDirectories() throws Exception {
+ installPackage(APP_APK);
+ installPackage(APP_2_APK);
+
+ runPhase("testAppCannotAccessAnySandboxDirectories");
+ }
+
+ /** Test whether an SDK can access its provided data directories after data isolation. */
+ @Test
+ public void testSdkSandboxDataIsolation_SandboxCanAccessItsDirectory() throws Exception {
+ installPackage(APP_APK);
+ runPhase("testSdkSandboxDataIsolation_SandboxCanAccessItsDirectory");
+ }
+}
diff --git a/sdksandbox/tests/cts/hostside/src/com/android/sdksandbox/cts/host/SdkSandboxStorageHostTest.java b/sdksandbox/tests/cts/hostside/src/com/android/sdksandbox/cts/host/SdkSandboxStorageHostTest.java
new file mode 100644
index 000000000..0e0fd4b0a
--- /dev/null
+++ b/sdksandbox/tests/cts/hostside/src/com/android/sdksandbox/cts/host/SdkSandboxStorageHostTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.sdksandbox.cts.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
+
+ private static final String TEST_APP_PACKAGE_NAME = "com.android.sdksandbox.cts.app";
+ private static final String TEST_APP_APK_NAME = "CtsSdkSandboxHostTestApp.apk";
+
+ /**
+ * Runs the given phase of a test by calling into the device. Throws an exception if the test
+ * phase fails.
+ *
+ * <p>For example, <code>runPhase("testExample");</code>
+ */
+ private void runPhase(String phase) throws Exception {
+ assertThat(
+ runDeviceTests(
+ TEST_APP_PACKAGE_NAME,
+ TEST_APP_PACKAGE_NAME + ".SdkSandboxStorageTestApp",
+ phase))
+ .isTrue();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ uninstallPackage(TEST_APP_PACKAGE_NAME);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ uninstallPackage(TEST_APP_PACKAGE_NAME);
+ }
+
+ @Test
+ public void testSharedPreferences_IsSyncedFromAppToSandbox() throws Exception {
+ installPackage(TEST_APP_APK_NAME);
+ runPhase("testSharedPreferences_IsSyncedFromAppToSandbox");
+ }
+
+ @Test
+ public void testSharedPreferences_SyncPropagatesUpdates() throws Exception {
+ installPackage(TEST_APP_APK_NAME);
+ runPhase("testSharedPreferences_SyncPropagatesUpdates");
+ }
+
+ @Test
+ public void testSharedPreferences_SyncStartedBeforeLoadingSdk() throws Exception {
+ installPackage(TEST_APP_APK_NAME);
+ runPhase("testSharedPreferences_SyncStartedBeforeLoadingSdk");
+ }
+
+ @Test
+ public void testSharedPreferences_SyncRemoveKeys() throws Exception {
+ installPackage(TEST_APP_APK_NAME);
+ runPhase("testSharedPreferences_SyncRemoveKeys");
+ }
+
+ @Test
+ public void testSharedPreferences_SyncedDataClearedOnSandboxRestart() throws Exception {
+ installPackage(TEST_APP_APK_NAME);
+ runPhase("testSharedPreferences_IsSyncedFromAppToSandbox");
+ runPhase("testSharedPreferences_SyncedDataClearedOnSandboxRestart");
+ }
+}
diff --git a/sdksandbox/tests/cts/inprocess/src/com/android/sdksandbox/tests/cts/inprocess/SdkSandboxConfigurationTest.java b/sdksandbox/tests/cts/inprocess/src/com/android/sdksandbox/tests/cts/inprocess/SdkSandboxConfigurationTest.java
index c8a427c86..f9223d486 100644
--- a/sdksandbox/tests/cts/inprocess/src/com/android/sdksandbox/tests/cts/inprocess/SdkSandboxConfigurationTest.java
+++ b/sdksandbox/tests/cts/inprocess/src/com/android/sdksandbox/tests/cts/inprocess/SdkSandboxConfigurationTest.java
@@ -62,6 +62,8 @@ import java.util.concurrent.TimeUnit;
public class SdkSandboxConfigurationTest {
private static final String TEST_PKG = "com.android.sdksandbox.tests.cts.inprocesstests";
+ private static final String CURRENT_USER_ID =
+ String.valueOf(Process.myUserHandle().getUserId(Process.myUid()));
/**
* Tests that uid belongs to the sdk sandbox processes uid range.
@@ -92,7 +94,7 @@ public class SdkSandboxConfigurationTest {
assertThat(minSdkVersion).isEqualTo(33);
int targetSdkVersion = info.applicationInfo.targetSdkVersion;
- assertThat(targetSdkVersion).isEqualTo(33);
+ assertThat(targetSdkVersion).isAtLeast(33);
}
/**
@@ -115,8 +117,9 @@ public class SdkSandboxConfigurationTest {
public void testGetDataDir_CE() throws Exception {
final Context ctx = InstrumentationRegistry.getInstrumentation().getTargetContext();
final File dir = ctx.getDataDir();
- assertThat(dir.getAbsolutePath()).isEqualTo(
- "/data/misc_ce/0/sdksandbox/" + TEST_PKG + "/shared");
+ assertThat(dir.getAbsolutePath())
+ .isEqualTo(
+ "/data/misc_ce/" + CURRENT_USER_ID + "/sdksandbox/" + TEST_PKG + "/shared");
}
/**
@@ -130,8 +133,9 @@ public class SdkSandboxConfigurationTest {
.getTargetContext()
.createDeviceProtectedStorageContext();
final File dir = ctx.getDataDir();
- assertThat(dir.getAbsolutePath()).isEqualTo(
- "/data/misc_de/0/sdksandbox/" + TEST_PKG + "/shared");
+ assertThat(dir.getAbsolutePath())
+ .isEqualTo(
+ "/data/misc_de/" + CURRENT_USER_ID + "/sdksandbox/" + TEST_PKG + "/shared");
}
/** Tests that sdk sandbox process can write to it's CE storage. */
diff --git a/sdksandbox/tests/hostsidetests/Android.bp b/sdksandbox/tests/hostsidetests/Android.bp
index 3f8a0a91d..e66726f8e 100644
--- a/sdksandbox/tests/hostsidetests/Android.bp
+++ b/sdksandbox/tests/hostsidetests/Android.bp
@@ -70,6 +70,8 @@ android_test_helper_app {
"androidx.test.ext.junit",
"SdkSandboxTestUtils",
],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
android_test_helper_app {
@@ -87,6 +89,8 @@ android_test_helper_app {
"androidx.core_core",
"SdkSandboxTestUtils",
],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
android_test_helper_app {
@@ -104,6 +108,8 @@ android_test_helper_app {
"androidx.core_core",
"SdkSandboxTestUtils",
],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
android_test_helper_app {
@@ -122,6 +128,8 @@ android_test_helper_app {
"SdkSandboxTestUtils",
],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
android_test_helper_app {
@@ -142,6 +150,8 @@ android_test_helper_app {
"SdkSandboxTestUtils",
],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
android_test_helper_app {
@@ -154,6 +164,8 @@ android_test_helper_app {
":framework-sdksandbox-sources",
],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
android_test_helper_app {
@@ -166,4 +178,6 @@ android_test_helper_app {
":framework-sdksandbox-sources",
],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxLifecycleHostTest.xml b/sdksandbox/tests/hostsidetests/SdkSandboxLifecycleHostTest.xml
index a0b6b36fe..031c832d7 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxLifecycleHostTest.xml
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxLifecycleHostTest.xml
@@ -17,6 +17,11 @@
<configuration description="Runs sdk sandbox lifecycle host tests">
<option name="test-suite-tag" value="SdkSandboxLifecycleHostTest" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="TestCodeProvider.apk" />
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/Android.bp b/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/Android.bp
index 542200ecd..28c7f0af5 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/Android.bp
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/Android.bp
@@ -49,6 +49,8 @@ java_defaults {
],
test_suites: ["general-tests"],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
android_test_helper_app {
@@ -67,4 +69,6 @@ android_test_helper_app {
":framework-sdksandbox-sources",
],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/AndroidTest.xml b/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/AndroidTest.xml
index 14de86a69..50f7fd26f 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/AndroidTest.xml
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/AndroidTest.xml
@@ -20,6 +20,11 @@
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="RestrictionsTestSdkProvider.apk" />
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxRestrictionsTestApp.java b/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxRestrictionsTestApp.java
index cd8e6ff5c..f7c072a99 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxRestrictionsTestApp.java
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxRestrictionsHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxRestrictionsTestApp.java
@@ -89,6 +89,8 @@ public class SdkSandboxRestrictionsTestApp {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SDK_SANDBOX,
ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS, "true", false);
+ // Allow time for DeviceConfig change to propagate.
+ Thread.sleep(1000);
FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_PACKAGE, new Bundle(), Runnable::run, callback);
assertThat(callback.isLoadSdkSuccessful()).isTrue();
@@ -100,6 +102,8 @@ public class SdkSandboxRestrictionsTestApp {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SDK_SANDBOX,
ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS, "false", false);
+ // Allow time for DeviceConfig change to propagate.
+ Thread.sleep(1000);
FakeRequestSurfacePackageCallback surfacePackageCallback2 =
new FakeRequestSurfacePackageCallback();
runPhaseInsideSdk("testSdkSandboxBroadcastRestrictions", surfacePackageCallback2);
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxShellHostTest.xml b/sdksandbox/tests/hostsidetests/SdkSandboxShellHostTest.xml
index 104ebc091..929140b61 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxShellHostTest.xml
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxShellHostTest.xml
@@ -17,6 +17,11 @@
<configuration description="Runs sdk sandbox shell command host tests">
<option name="test-suite-tag" value="SdkSandboxShellHostTest" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="TestCodeProvider.apk" />
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/Android.bp b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/Android.bp
index 356b5a4e6..5ac0c153b 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/Android.bp
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/Android.bp
@@ -28,6 +28,7 @@ java_test_host {
],
static_libs: [
"cts-install-lib-host",
+ "SdkSandboxHostTestUtils",
],
test_suites: ["general-tests"],
data: [
@@ -52,11 +53,11 @@ java_defaults {
"androidx.test.ext.junit",
"compatibility-device-util-axt",
"SdkSandboxTestUtils",
+ "StorageTestSdk1Api",
],
min_sdk_version: "Tiramisu",
- target_sdk_version: "current",
+ target_sdk_version: "Tiramisu",
test_suites: ["general-tests"],
- platform_apis: true,
}
android_test_helper_app {
@@ -77,11 +78,19 @@ android_test_helper_app {
defaults: ["platform_app_defaults"],
srcs: [
"codeprovider/src/**/*.java",
- ":framework-sdksandbox-sources",
],
static_libs: [
"compatibility-device-util-axt",
+ "StorageTestSdk1Api",
],
- platform_apis: true,
certificate: ":sdksandbox-test",
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
+
+java_library {
+ name: "StorageTestSdk1Api",
+ srcs: [
+ "codeprovider/src/**/*.aidl",
+ ],
}
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/SdkSandboxStorageHostTest.xml b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/SdkSandboxStorageHostTest.xml
index 0459bb20f..fd421013e 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/SdkSandboxStorageHostTest.xml
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/SdkSandboxStorageHostTest.xml
@@ -17,6 +17,11 @@
<configuration description="Runs sdk sandbox storage host tests">
<option name="test-suite-tag" value="SdkSandboxStorageHostTest" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="StorageTestCodeProvider.apk" />
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxStorageTestApp.java b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxStorageTestApp.java
index 0cfa061ed..00b4089f7 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxStorageTestApp.java
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/app/src/com/android/tests/sdksandbox/SdkSandboxStorageTestApp.java
@@ -21,24 +21,23 @@ import static android.os.storage.StorageManager.UUID_DEFAULT;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import android.app.sdksandbox.SdkSandboxManager;
import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
-import android.app.sdksandbox.testutils.FakeRequestSurfacePackageCallback;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
-import android.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.tests.codeprovider.storagetest_1.IStorageTestSdk1Api;
+
import junit.framework.AssertionFailedError;
import org.junit.Before;
@@ -50,7 +49,6 @@ import org.junit.runners.JUnit4;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.util.Set;
@RunWith(JUnit4.class)
public class SdkSandboxStorageTestApp {
@@ -67,12 +65,9 @@ public class SdkSandboxStorageTestApp {
@Rule public final ActivityScenarioRule mRule = new ActivityScenarioRule<>(TestActivity.class);
- private static final String KEY_TO_SYNC = "hello";
- private static final String BULK_SYNC_VALUE = "bulksync";
- private static final String UPDATE_VALUE = "update";
-
private Context mContext;
private SdkSandboxManager mSdkSandboxManager;
+ private IStorageTestSdk1Api mSdk;
@Before
public void setup() {
@@ -82,27 +77,20 @@ public class SdkSandboxStorageTestApp {
mRule.getScenario();
}
- // Run a phase of the test inside the code loaded for this app
- // TODO(b/242678799): We want to use interface provided by loadSdk to perform the communication
- // i.e. use the correct approach
- private void runPhaseInsideCode(String phaseName) {
- FakeRequestSurfacePackageCallback callback = new FakeRequestSurfacePackageCallback();
- Bundle params = new Bundle();
- params.putInt(mSdkSandboxManager.EXTRA_WIDTH_IN_PIXELS, 500);
- params.putInt(mSdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS, 500);
- params.putInt(mSdkSandboxManager.EXTRA_DISPLAY_ID, 0);
- params.putBinder(mSdkSandboxManager.EXTRA_HOST_TOKEN, new Binder());
- params.putString(BUNDLE_KEY_PHASE_NAME, phaseName);
- mSdkSandboxManager.requestSurfacePackage(SDK_NAME, params, Runnable::run, callback);
- // Wait for SDK to finish handling the request
- assertThat(callback.isRequestSurfacePackageSuccessful()).isFalse();
- }
-
@Test
public void loadSdk() throws Exception {
FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME, new Bundle(), Runnable::run, callback);
- assertThat(callback.isLoadSdkSuccessful()).isTrue();
+ if (!callback.isLoadSdkSuccessful()) {
+ fail(
+ "Load SDK was not successful. errorCode: "
+ + callback.getLoadSdkErrorCode()
+ + ", errorMsg: "
+ + callback.getLoadSdkErrorMsg());
+ }
+
+ // Store the returned SDK interface so that we can interact with it later.
+ mSdk = IStorageTestSdk1Api.Stub.asInterface(callback.getSandboxedSdk().getInterface());
}
@Test
@@ -115,16 +103,14 @@ public class SdkSandboxStorageTestApp {
public void testSdkDataPackageDirectory_SharedStorageIsUsable() throws Exception {
loadSdk();
- // Run phase inside the SDK
- runPhaseInsideCode("testSdkDataPackageDirectory_SharedStorageIsUsable");
+ mSdk.verifySharedStorageIsUsable();
}
@Test
public void testSdkDataSubDirectory_PerSdkStorageIsUsable() throws Exception {
loadSdk();
- // Run phase inside the SDK
- runPhaseInsideCode("testSdkDataSubDirectory_PerSdkStorageIsUsable");
+ mSdk.verifyPerSdkStorageIsUsable();
}
@Test
@@ -140,73 +126,30 @@ public class SdkSandboxStorageTestApp {
final StorageStats initialAppStats = stats.queryStatsForUid(UUID_DEFAULT, uid);
final StorageStats initialUserStats = stats.queryStatsForUser(UUID_DEFAULT, user);
- runPhaseInsideCode("testSdkDataIsAttributedToApp");
+ final int sizeInBytes = 10000000; // 10 MB
+ mSdk.createFilesInSharedStorage(sizeInBytes, /*inCacheDir*/ false);
+ mSdk.createFilesInSharedStorage(sizeInBytes, /*inCacheDir*/ true);
final StorageStats finalAppStats = stats.queryStatsForUid(UUID_DEFAULT, uid);
final StorageStats finalUserStats = stats.queryStatsForUser(UUID_DEFAULT, user);
- // Verify the space used with a few hundred kilobytes error margin
- long deltaAppSize = 2000000;
- long deltaCacheSize = 1000000;
- long errorMarginSize = 100000;
- assertMostlyEquals(deltaAppSize,
- finalAppStats.getDataBytes() - initialAppStats.getDataBytes(),
- errorMarginSize);
- assertMostlyEquals(deltaAppSize,
- finalUserStats.getDataBytes() - initialUserStats.getDataBytes(),
- errorMarginSize);
- assertMostlyEquals(deltaCacheSize,
- finalAppStats.getCacheBytes() - initialAppStats.getCacheBytes(),
- errorMarginSize);
- assertMostlyEquals(deltaCacheSize,
- finalUserStats.getCacheBytes() - initialUserStats.getCacheBytes(),
- errorMarginSize);
- }
-
- @Test
- public void testSharedPreferences_IsSyncedFromAppToSandbox() throws Exception {
- loadSdk();
-
- // Write to default shared preference
- final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
- pref.edit().putString(KEY_TO_SYNC, BULK_SYNC_VALUE).commit();
-
- // Start syncing keys
- mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(KEY_TO_SYNC));
-
- // Verify same key can be read from the sandbox
- runPhaseInsideCode("testSharedPreferences_VerifyBulkSyncReceived");
- }
-
- @Test
- public void testSharedPreferences_SyncPropagatesUpdates() throws Exception {
- loadSdk();
-
- // Start syncing keys
- mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(KEY_TO_SYNC));
-
- // Update the default SharedPreferences
- final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
- pref.edit().putString(KEY_TO_SYNC, UPDATE_VALUE).commit();
-
- // Verify update was propagated
- runPhaseInsideCode("testSharedPreferences_VerifyUpdateReceived");
- }
-
- @Test
- public void testSharedPreferences_SyncStartedBeforeLoadingSdk() throws Exception {
- // Write to default shared preference
- final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
- pref.edit().putString(KEY_TO_SYNC, BULK_SYNC_VALUE).commit();
-
- // Start syncing keys
- mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(KEY_TO_SYNC));
-
- // Load Sdk so that sandbox is started
- loadSdk();
-
- // Verify same key can be read from the sandbox
- runPhaseInsideCode("testSharedPreferences_VerifyBulkSyncReceived");
+ long deltaAppSize = 2 * sizeInBytes;
+ long deltaCacheSize = sizeInBytes;
+
+ // Assert app size is same
+ final long appSizeAppStats = finalAppStats.getDataBytes() - initialAppStats.getDataBytes();
+ final long appSizeUserStats =
+ finalUserStats.getDataBytes() - initialUserStats.getDataBytes();
+ assertMostlyEquals(deltaAppSize, appSizeAppStats);
+ assertMostlyEquals(deltaAppSize, appSizeUserStats);
+
+ // Assert cache size is same
+ final long cacheSizeAppStats =
+ finalAppStats.getCacheBytes() - initialAppStats.getCacheBytes();
+ final long cacheSizeUserStats =
+ finalUserStats.getCacheBytes() - initialUserStats.getCacheBytes();
+ assertMostlyEquals(deltaCacheSize, cacheSizeAppStats);
+ assertMostlyEquals(deltaCacheSize, cacheSizeUserStats);
}
private static void assertDirIsNotAccessible(String path) {
@@ -221,8 +164,9 @@ public class SdkSandboxStorageTestApp {
assertThat(new File(path).canExecute()).isFalse();
}
- private static void assertMostlyEquals(long expected, long actual, long delta) {
- if (Math.abs(expected - actual) > delta) {
+ private static void assertMostlyEquals(long expected, long actual) {
+ final long errorMarginSize = expected / 20; // 5%
+ if (Math.abs(expected - actual) > errorMarginSize) {
throw new AssertionFailedError("Expected roughly " + expected + " but was " + actual);
}
}
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/IStorageTestSdk1Api.aidl b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/IStorageTestSdk1Api.aidl
new file mode 100644
index 000000000..ae7cf9f67
--- /dev/null
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/IStorageTestSdk1Api.aidl
@@ -0,0 +1,21 @@
+// 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.tests.codeprovider.storagetest_1;
+
+interface IStorageTestSdk1Api {
+ void verifySharedStorageIsUsable();
+ void verifyPerSdkStorageIsUsable();
+ void createFilesInSharedStorage(int sizeInBytes, boolean inCacheDir);
+}
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSandboxedSdkProvider.java b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSandboxedSdkProvider.java
index c079cd339..ee41fa37a 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSandboxedSdkProvider.java
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSandboxedSdkProvider.java
@@ -16,135 +16,22 @@
package com.android.tests.codeprovider.storagetest_1;
-import static com.google.common.truth.Truth.assertThat;
import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SandboxedSdkProvider;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Binder;
import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.util.Log;
import android.view.View;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
public class StorageTestSandboxedSdkProvider extends SandboxedSdkProvider {
- private static final String TAG = "SdkSandboxStorageTestProvider";
- private static final String BUNDLE_KEY_PHASE_NAME = "phase-name";
-
@Override
public SandboxedSdk onLoadSdk(Bundle params) {
- return new SandboxedSdk(new Binder());
+ return new SandboxedSdk(new StorageTestSdk1ApiImpl(getContext()));
}
@Override
public View getView(Context windowContext, Bundle params, int width, int height) {
- try {
- handlePhase(params);
- } catch (Throwable e) {
- Log.e(TAG, e.getMessage(), e);
- throw new RuntimeException();
- }
return null;
}
-
- private void handlePhase(Bundle params) throws Exception {
- String phaseName = params.getString(BUNDLE_KEY_PHASE_NAME, "");
- Log.i(TAG, "Handling phase: " + phaseName);
- switch (phaseName) {
- case "testSdkDataPackageDirectory_SharedStorageIsUsable":
- testSdkDataPackageDirectory_SharedStorageIsUsable();
- break;
- case "testSdkDataSubDirectory_PerSdkStorageIsUsable":
- testSdkDataSubDirectory_PerSdkStorageIsUsable();
- break;
- case "testSdkDataIsAttributedToApp":
- testSdkDataIsAttributedToApp();
- break;
- case "testSharedPreferences_BulkSyncReceived":
- testSharedPreferences_BulkSyncReceived();
- break;
- case "testSharedPreferences_UpdateReceived":
- testSharedPreferences_UpdateReceived();
- break;
- case "testSharedPreferences_UpdateNotReceived":
- testSharedPreferences_UpdateNotReceived();
- break;
- default:
- }
- }
-
- private void testSdkDataPackageDirectory_SharedStorageIsUsable() throws Exception {
- String sharedPath = getSharedStoragePath();
- // Read the file
- String input = Files.readAllLines(Paths.get(sharedPath, "readme.txt")).get(0);
-
- // Create a dir
- Files.createDirectory(Paths.get(sharedPath, "dir"));
- // Write to a file
- Path filepath = Paths.get(sharedPath, "dir", "file");
- Files.createFile(filepath);
- Files.write(filepath, input.getBytes());
- }
-
- private void testSdkDataSubDirectory_PerSdkStorageIsUsable() throws Exception {
- String sdkDataPath = getContext().getDataDir().toString();
- // Read the file
- String input = Files.readAllLines(Paths.get(sdkDataPath, "readme.txt")).get(0);
-
- // Create a dir
- Files.createDirectory(Paths.get(sdkDataPath, "dir"));
- // Write to a file
- Path filepath = Paths.get(sdkDataPath, "dir", "file");
- Files.createFile(filepath);
- Files.write(filepath, input.getBytes());
- }
-
- private void testSdkDataIsAttributedToApp() throws Exception {
- final byte[] buffer = new byte[1000000];
- String sharedPath = getSharedStoragePath();
- String sharedCachePath = getSharedStorageCachePath();
-
- Files.createDirectory(Paths.get(sharedPath, "attribution"));
- Path filepath = Paths.get(sharedPath, "attribution", "file");
- Files.createFile(filepath);
- Files.write(filepath, buffer);
-
- Files.createDirectory(Paths.get(sharedCachePath, "attribution"));
- Path cacheFilepath = Paths.get(sharedCachePath, "attribution", "file");
- Files.createFile(cacheFilepath);
- Files.write(cacheFilepath, buffer);
- }
-
- private void testSharedPreferences_BulkSyncReceived() throws Exception {
- SharedPreferences pref = getClientSharedPreferences();
- assertThat(pref.getString("hello", "")).isEqualTo("world");
- }
-
- private void testSharedPreferences_UpdateReceived() throws Exception {
- SharedPreferences pref = getClientSharedPreferences();
- assertThat(pref.getString("hello", "")).isEqualTo("update");
- }
-
- private void testSharedPreferences_UpdateNotReceived() throws Exception {
- SharedPreferences pref = getClientSharedPreferences();
- assertThat(pref.getString("hello", "")).isNotEqualTo("update");
- }
-
- // TODO(b/237410689): Replace with real getClientSharedPreferences
- private SharedPreferences getClientSharedPreferences() throws Exception {
- return PreferenceManager.getDefaultSharedPreferences(getContext().getApplicationContext());
- }
-
- private String getSharedStoragePath() {
- return getContext().getApplicationContext().getDataDir().toString();
- }
-
- private String getSharedStorageCachePath() {
- return getContext().getApplicationContext().getCacheDir().toString();
- }
}
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSdk1ApiImpl.java b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSdk1ApiImpl.java
new file mode 100644
index 000000000..552a915fb
--- /dev/null
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/codeprovider/src/com/android/tests/codeprovider/storagetest_1/StorageTestSdk1ApiImpl.java
@@ -0,0 +1,90 @@
+/*
+ * 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.tests.codeprovider.storagetest_1;
+
+import android.content.Context;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class StorageTestSdk1ApiImpl extends IStorageTestSdk1Api.Stub {
+ private final Context mContext;
+
+ public StorageTestSdk1ApiImpl(Context sdkContext) {
+ mContext = sdkContext;
+ }
+
+ @Override
+ public void verifySharedStorageIsUsable() {
+ String sharedPath = getSharedStoragePath();
+ try {
+ // Read the file
+ String input = Files.readAllLines(Paths.get(sharedPath, "readme.txt")).get(0);
+
+ // Create a dir
+ Files.createDirectory(Paths.get(sharedPath, "dir"));
+ // Write to a file
+ Path filepath = Paths.get(sharedPath, "dir", "file");
+ Files.createFile(filepath);
+ Files.write(filepath, input.getBytes());
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void verifyPerSdkStorageIsUsable() {
+ try {
+ String sdkDataPath = mContext.getDataDir().toString();
+ // Read the file
+ String input = Files.readAllLines(Paths.get(sdkDataPath, "readme.txt")).get(0);
+
+ // Create a dir
+ Files.createDirectory(Paths.get(sdkDataPath, "dir"));
+ // Write to a file
+ Path filepath = Paths.get(sdkDataPath, "dir", "file");
+ Files.createFile(filepath);
+ Files.write(filepath, input.getBytes());
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void createFilesInSharedStorage(int sizeInBytes, boolean inCacheDir) {
+ try {
+ final byte[] buffer = new byte[sizeInBytes];
+ final String path = inCacheDir ? getSharedStorageCachePath() : getSharedStoragePath();
+
+ Files.createDirectory(Paths.get(path, "attribution"));
+ final Path filepath = Paths.get(path, "attribution", "file");
+ Files.createFile(filepath);
+ Files.write(filepath, buffer);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private String getSharedStoragePath() {
+ return mContext.getApplicationContext().getDataDir().toString();
+ }
+
+ private String getSharedStorageCachePath() {
+ return mContext.getApplicationContext().getCacheDir().toString();
+ }
+}
diff --git a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/src/com/android/tests/sdksandbox/host/SdkSandboxStorageHostTest.java b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/src/com/android/tests/sdksandbox/host/SdkSandboxStorageHostTest.java
index 11bc14aa2..db87ba6d5 100644
--- a/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/src/com/android/tests/sdksandbox/host/SdkSandboxStorageHostTest.java
+++ b/sdksandbox/tests/hostsidetests/SdkSandboxStorageHostTest/src/com/android/tests/sdksandbox/host/SdkSandboxStorageHostTest.java
@@ -26,9 +26,10 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import static org.junit.Assume.assumeTrue;
+import android.app.sdksandbox.hosttestutils.AdoptableStorageUtils;
+import android.app.sdksandbox.hosttestutils.SecondaryUserUtils;
import android.platform.test.annotations.LargeTest;
-import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -38,6 +39,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -48,8 +50,6 @@ import javax.annotation.Nullable;
@RunWith(DeviceJUnit4ClassRunner.class)
public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
- private int mOriginalUserId;
- private int mSecondaryUserId = -1;
private boolean mWasRoot;
private static final String CODE_PROVIDER_APK = "StorageTestCodeProvider.apk";
@@ -59,11 +59,13 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
"SdkSandboxStorageTestAppV2_DoesNotConsumeSdk.apk";
private static final String SDK_NAME = "com.android.tests.codeprovider.storagetest";
- private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
- private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
+ private static final String SHARED_DIR = "shared";
+ private static final String SANDBOX_DIR = "sandbox";
+
// Needs to be at least 20s since that's how long we delay reconcile on SdkSandboxManagerService
private static final long WAIT_FOR_RECONCILE_MS = 30000;
+ private final SecondaryUserUtils mUserUtils = new SecondaryUserUtils(this);
private final AdoptableStorageUtils mAdoptableUtils = new AdoptableStorageUtils(this);
private final DeviceLockUtils mDeviceLockUtils = new DeviceLockUtils(this);
@@ -86,12 +88,11 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
mWasRoot = getDevice().isAdbRoot();
getDevice().enableAdbRoot();
uninstallPackage(TEST_APP_STORAGE_PACKAGE);
- mOriginalUserId = getDevice().getCurrentUser();
}
@After
public void tearDown() throws Exception {
- removeSecondaryUserIfNecessary();
+ mUserUtils.removeSecondaryUserIfNecessary();
uninstallPackage(TEST_APP_STORAGE_PACKAGE);
if (!mWasRoot) {
getDevice().disableAdbRoot();
@@ -112,9 +113,11 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
"sdk_sandbox_system_data_file");
// Check label of /data/misc_{ce,de}/0/sdksandbox/<app-name>/shared
assertSelinuxLabel(
- getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true), "sdk_sandbox_data_file");
+ getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true),
+ "sdk_sandbox_data_file");
assertSelinuxLabel(
- getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, false), "sdk_sandbox_data_file");
+ getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, false),
+ "sdk_sandbox_data_file");
// Check label of /data/misc_{ce,de}/0/sdksandbox/<app-name>/<sdk-package>
assertSelinuxLabel(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true),
"sdk_sandbox_data_file");
@@ -138,9 +141,9 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
{
// Verify root directory is created for new user
- mSecondaryUserId = createAndStartSecondaryUser();
- final String cePath = getSdkDataRootPath(mSecondaryUserId, true);
- final String dePath = getSdkDataRootPath(mSecondaryUserId, false);
+ int secondaryUserId = mUserUtils.createAndStartSecondaryUser();
+ final String cePath = getSdkDataRootPath(secondaryUserId, true);
+ final String dePath = getSdkDataRootPath(secondaryUserId, false);
assertThat(getDevice().isDirectory(dePath)).isTrue();
assertThat(getDevice().isDirectory(cePath)).isTrue();
}
@@ -148,12 +151,9 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
@Test
public void testSdkDataRootDirectory_IsDestroyedOnUserDeletion() throws Exception {
- // Create new user
- mSecondaryUserId = createAndStartSecondaryUser();
-
// delete the new user
- final int newUser = mSecondaryUserId;
- removeSecondaryUserIfNecessary();
+ final int newUser = mUserUtils.createAndStartSecondaryUser();
+ mUserUtils.removeSecondaryUserIfNecessary(/*waitForUserDataDeletion=*/ true);
// Sdk Sandbox root directories should not exist as the user was removed
final String ceSdkSandboxDataRootPath = getSdkDataRootPath(newUser, true);
@@ -406,7 +406,7 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
final String cePackageDir = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true);
// Delete the shared directory
- final String sharedDir = cePackageDir + "/shared";
+ final String sharedDir = cePackageDir + "/" + SHARED_DIR;
getDevice().deleteFile(sharedDir);
assertDirectoryDoesNotExist(sharedDir);
@@ -496,8 +496,10 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
Arrays.asList(
getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE app data
getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE app data
- getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE sdk data
- getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE sdk data
+ getSdkDataInternalPath(
+ 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true), // CE sdk data
+ getSdkDataInternalPath(
+ 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, false), // DE sdk data
getSdkDataPerSdkPath(
0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true), // CE per-sdk
getSdkDataPerSdkPath(
@@ -532,8 +534,10 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
Arrays.asList(
getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE app data
getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE app data
- getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE sdk data
- getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE sdk data
+ getSdkDataInternalPath(
+ 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true), // CE sdk data
+ getSdkDataInternalPath(
+ 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, false), // DE sdk data
getSdkDataPerSdkPath(
0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true), // CE per-sdk
getSdkDataPerSdkPath(
@@ -568,8 +572,10 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
Arrays.asList(
getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE app data
getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE app data
- getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE sdk data
- getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE sdk data
+ getSdkDataInternalPath(
+ 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true), // CE sdk data
+ getSdkDataInternalPath(
+ 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, false), // DE sdk data
getSdkDataPerSdkPath(
0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true), // CE per-sdk
getSdkDataPerSdkPath(
@@ -597,15 +603,15 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
// Install first before creating the user
installPackage(TEST_APP_STORAGE_APK, "--user all");
- mSecondaryUserId = createAndStartSecondaryUser();
+ int secondaryUserId = mUserUtils.createAndStartSecondaryUser();
// Data directories should not exist as the package is not installed on new user
- final String ceAppPath = getAppDataPath(mSecondaryUserId, TEST_APP_STORAGE_PACKAGE, true);
- final String deAppPath = getAppDataPath(mSecondaryUserId, TEST_APP_STORAGE_PACKAGE, false);
- final String cePath = getSdkDataPackagePath(mSecondaryUserId,
- TEST_APP_STORAGE_PACKAGE, true);
- final String dePath = getSdkDataPackagePath(mSecondaryUserId,
- TEST_APP_STORAGE_PACKAGE, false);
+ final String ceAppPath = getAppDataPath(secondaryUserId, TEST_APP_STORAGE_PACKAGE, true);
+ final String deAppPath = getAppDataPath(secondaryUserId, TEST_APP_STORAGE_PACKAGE, false);
+ final String cePath =
+ getSdkDataPackagePath(secondaryUserId, TEST_APP_STORAGE_PACKAGE, true);
+ final String dePath =
+ getSdkDataPackagePath(secondaryUserId, TEST_APP_STORAGE_PACKAGE, false);
assertThat(getDevice().isDirectory(ceAppPath)).isFalse();
assertThat(getDevice().isDirectory(deAppPath)).isFalse();
@@ -626,7 +632,8 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
installPackage(TEST_APP_STORAGE_APK);
// Verify that shared storage exist
- final String sharedCePath = getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true);
+ final String sharedCePath =
+ getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true);
assertThat(getDevice().isDirectory(sharedCePath)).isTrue();
// Write a file in the shared storage that code needs to read and write it back
@@ -670,8 +677,10 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
/*includeRandomSuffix=*/false);
final List<String> deSdkDirsAfterLoadingSdksList = getSubDirs(dePackagePath,
/*includeRandomSuffix=*/false);
- assertThat(ceSdkDirsAfterLoadingSdksList).containsExactly("shared", SDK_NAME);
- assertThat(deSdkDirsAfterLoadingSdksList).containsExactly("shared", SDK_NAME);
+ assertThat(ceSdkDirsAfterLoadingSdksList)
+ .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR);
+ assertThat(deSdkDirsAfterLoadingSdksList)
+ .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR);
}
@Test
@@ -691,8 +700,10 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
/*includeRandomSuffix=*/false);
final List<String> deSdkDirsAfterLoadingSdksList = getSubDirs(dePackagePath,
/*includeRandomSuffix=*/false);
- assertThat(ceSdkDirsAfterLoadingSdksList).containsExactly("shared", SDK_NAME);
- assertThat(deSdkDirsAfterLoadingSdksList).containsExactly("shared", SDK_NAME);
+ assertThat(ceSdkDirsAfterLoadingSdksList)
+ .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR);
+ assertThat(deSdkDirsAfterLoadingSdksList)
+ .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR);
}
@Test
@@ -716,8 +727,10 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
/*includeRandomSuffix=*/false);
final List<String> deSdkDirsAfterLoadingSdksList =
getSubDirs(dePackagePath, /*includeRandomSuffix=*/ false);
- assertThat(ceSdkDirsAfterLoadingSdksList).containsExactly("shared", SDK_NAME);
- assertThat(deSdkDirsAfterLoadingSdksList).containsExactly("shared", SDK_NAME);
+ assertThat(ceSdkDirsAfterLoadingSdksList)
+ .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR);
+ assertThat(deSdkDirsAfterLoadingSdksList)
+ .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR);
}
@Test
@@ -730,9 +743,11 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
getSubDirs(cePackagePath, /*includeRandomSuffix=*/ true);
final List<String> deSdkDirsBeforeLoadingSdksList =
getSubDirs(dePackagePath, /*includeRandomSuffix=*/ true);
+
// Delete the sdk sub directories
- getDevice().deleteFile(cePackagePath + "/" + ceSdkDirsBeforeLoadingSdksList.get(1));
- getDevice().deleteFile(dePackagePath + "/" + deSdkDirsBeforeLoadingSdksList.get(1));
+ getDevice().deleteFile(cePackagePath + "/" + SHARED_DIR);
+ getDevice().deleteFile(dePackagePath + "/" + SHARED_DIR);
+
runPhase("loadSdk");
final List<String> ceSdkDirsAfterLoadingSdksList =
@@ -763,6 +778,9 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
@Test
public void testSdkDataSubDirectory_IsCreatedOnInstall() throws Exception {
// Directory should not exist before install
+ assertThat(getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SANDBOX_DIR, true)).isNull();
+ assertThat(getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SANDBOX_DIR, false))
+ .isNull();
assertThat(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true)).isNull();
assertThat(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, false)).isNull();
@@ -770,6 +788,10 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
installPackage(TEST_APP_STORAGE_APK);
// Verify directory is created
+ assertThat(getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SANDBOX_DIR, true))
+ .isNotNull();
+ assertThat(getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SANDBOX_DIR, false))
+ .isNotNull();
assertThat(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true)).isNotNull();
assertThat(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, false)).isNotNull();
}
@@ -904,7 +926,7 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
boolean isCeData = (i == 0) ? true : false;
final String sdkDataRootPath = getSdkDataRootPath(newVolumeUuid, 0, isCeData);
final String sdkDataPackagePath = sdkDataRootPath + "/" + TEST_APP_STORAGE_PACKAGE;
- final String sdkDataSharedPath = sdkDataPackagePath + "/" + "shared";
+ final String sdkDataSharedPath = sdkDataPackagePath + "/" + SHARED_DIR;
assertThat(getDevice().isDirectory(sdkDataRootPath)).isTrue();
assertThat(getDevice().isDirectory(sdkDataPackagePath)).isTrue();
@@ -932,7 +954,8 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
"pm move-package " + TEST_APP_STORAGE_PACKAGE + " " + newVolumeUuid));
final String sharedCePath =
- getSdkDataSharedPath(newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, true);
+ getSdkDataInternalPath(
+ newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true);
assertThat(getDevice().isDirectory(sharedCePath)).isTrue();
String fileToRead = sharedCePath + "/readme.txt";
@@ -989,7 +1012,7 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
assertDirectoryDoesNotExist(OldPackagePath);
final List<String> SdkDirsInNewVolume =
getSubDirs(sdkDataPackagePath, /*includeRandomSuffix=*/ false);
- assertThat(SdkDirsInNewVolume).containsExactly("shared", SDK_NAME);
+ assertThat(SdkDirsInNewVolume).containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR);
}
} finally {
mAdoptableUtils.cleanUpVolume();
@@ -1003,21 +1026,16 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
}
@Test
- public void testSharedPreferences_IsSyncedFromAppToSandbox() throws Exception {
- installPackage(TEST_APP_STORAGE_APK);
- runPhase("testSharedPreferences_IsSyncedFromAppToSandbox");
- }
-
- @Test
- public void testSharedPreferences_SyncPropagatesUpdates() throws Exception {
+ public void testSdkData_IsAttributedToApp_DisableQuota() throws Exception {
installPackage(TEST_APP_STORAGE_APK);
- runPhase("testSharedPreferences_SyncPropagatesUpdates");
- }
-
- @Test
- public void testSharedPreferences_SyncStartedBeforeLoadingSdk() throws Exception {
- installPackage(TEST_APP_STORAGE_APK);
- runPhase("testSharedPreferences_SyncStartedBeforeLoadingSdk");
+ String initialValue = getDevice().getProperty("fw.disable_quota");
+ try {
+ assertThat(getDevice().setProperty("fw.disable_quota", "true")).isTrue();
+ runPhase("testSdkDataIsAttributedToApp");
+ } finally {
+ if (initialValue == null) initialValue = "false";
+ assertThat(getDevice().setProperty("fw.disable_quota", initialValue)).isTrue();
+ }
}
private String getAppDataPath(int userId, String packageName, boolean isCeData) {
@@ -1066,19 +1084,54 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
"%s/%s", getSdkDataRootPath(volumeUuid, userId, isCeData), packageName);
}
- private String getSdkDataSharedPath(int userId, String packageName, boolean isCeData) {
- return getSdkDataSharedPath(/*volumeUuid=*/ null, userId, packageName, isCeData);
+ private String getSdkDataPerSdkPath(
+ int userId, String packageName, String sdkName, boolean isCeData) throws Exception {
+ return getSdkDataPerSdkPath(/*volumeUuid=*/ null, userId, packageName, sdkName, isCeData);
}
- private String getSdkDataSharedPath(
- @Nullable String volumeUuid, int userId, String packageName, boolean isCeData) {
- return String.format(
- "%s/shared", getSdkDataPackagePath(volumeUuid, userId, packageName, isCeData));
+ @Nullable
+ private String getSdkDataInternalPath(
+ int userId, String packageName, String internalDirName, boolean isCeData)
+ throws Exception {
+ return getSdkDataInternalPath(
+ /*volumeUuid=*/ null, userId, packageName, internalDirName, isCeData);
}
- private String getSdkDataPerSdkPath(
- int userId, String packageName, String sdkName, boolean isCeData) throws Exception {
- return getSdkDataPerSdkPath(/*volumeUuid=*/ null, userId, packageName, sdkName, isCeData);
+ // Internal sub-directory can have random suffix. So we need to iterate over the app-level
+ // directory to find it.
+ @Nullable
+ private String getSdkDataInternalPath(
+ @Nullable String volumeUuid,
+ int userId,
+ String packageName,
+ String internalDirName,
+ boolean isCeData)
+ throws Exception {
+ final String appLevelPath =
+ getSdkDataPackagePath(volumeUuid, userId, packageName, isCeData);
+ if (internalDirName.equals(SHARED_DIR)) {
+ return Paths.get(appLevelPath, SHARED_DIR).toString();
+ }
+
+ final String[] children = getDevice().getChildren(appLevelPath);
+ String result = null;
+ for (String child : children) {
+ if (!child.contains("#")) continue;
+ String[] tokens = child.split("#");
+ if (tokens.length != 2) {
+ continue;
+ }
+ String dirNameFound = tokens[0];
+ if (internalDirName.equals(dirNameFound)) {
+ if (result == null) {
+ result = Paths.get(appLevelPath, child).toString();
+ } else {
+ throw new IllegalStateException(
+ "Found two internal directory with same name: " + internalDirName);
+ }
+ }
+ }
+ return result;
}
// Per-Sdk directory has random suffix. So we need to iterate over the app-level directory
@@ -1096,6 +1149,7 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
final String[] children = getDevice().getChildren(appLevelPath);
String result = null;
for (String child : children) {
+ if (!child.contains("@")) continue;
String[] tokens = child.split("@");
if (tokens.length != 2) {
continue;
@@ -1123,7 +1177,12 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
}
final List<String> result = new ArrayList();
for (int i = 0; i < children.length; i++) {
- final String[] tokens = children[i].split("@");
+ String[] tokens;
+ if (children[i].contains("@")) {
+ tokens = children[i].split("@");
+ } else {
+ tokens = children[i].split("#");
+ }
result.add(tokens[0]);
}
return result;
@@ -1135,52 +1194,6 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
assertThat(output).contains("u:object_r:" + label);
}
- private int createAndStartSecondaryUser() throws Exception {
- assertWithMessage("Secondary user already created: " + mSecondaryUserId)
- .that(mSecondaryUserId).isEqualTo(-1);
- String name = "SdkSandboxStorageHostTest_User" + System.currentTimeMillis();
- int newId = getDevice().createUser(name);
- getDevice().startUser(newId);
- // Note we can't install apps on a locked user
- awaitUserUnlocked(newId);
- return newId;
- }
-
- private void awaitUserUnlocked(int userId) throws Exception {
- for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
- String userState = getDevice().executeShellCommand("am get-started-user-state "
- + userId);
- if (userState.contains("RUNNING_UNLOCKED")) {
- return;
- }
- Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
- }
- fail("Timed out in unlocking user: " + userId);
- }
-
- private void removeSecondaryUserIfNecessary() throws Exception {
- if (mSecondaryUserId != -1) {
- // Can't remove the 2nd user without switching out of it
- assertThat(getDevice().switchUser(mOriginalUserId)).isTrue();
- getDevice().executeShellCommand("pm remove-user -w " + mSecondaryUserId);
- waitForUserDataDeletion(mSecondaryUserId);
- mSecondaryUserId = -1;
- }
- }
-
- private void waitForUserDataDeletion(int userId) throws Exception {
- int timeElapsed = 0;
- final String deSdkSandboxDataRootPath = getSdkDataRootPath(userId, false);
- while (timeElapsed <= 30000) {
- if (!getDevice().isDirectory(deSdkSandboxDataRootPath)) {
- return;
- }
- Thread.sleep(1000);
- timeElapsed += 1000;
- }
- throw new AssertionError("User data was not deleted for UserId " + userId);
- }
-
private static void assertSuccess(String str) {
if (str == null || !str.startsWith("Success")) {
throw new AssertionError("Expected success string but found " + str);
@@ -1199,122 +1212,6 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
.isFalse();
}
- private static class AdoptableStorageUtils {
-
- private final BaseHostJUnit4Test mTest;
-
- private String mDiskId;
-
- AdoptableStorageUtils(BaseHostJUnit4Test test) {
- mTest = test;
- }
-
- public boolean isAdoptableStorageSupported() throws Exception {
- boolean hasFeature = mTest.getDevice().hasFeature(
- "feature:android.software.adoptable_storage");
- boolean hasFstab =
- Boolean.parseBoolean(
- mTest.getDevice().executeShellCommand("sm has-adoptable").trim());
- return hasFeature && hasFstab;
- }
-
- // Creates a new volume in adoptable storage and returns its uuid
- public String createNewVolume() throws Exception {
- mDiskId = getAdoptionDisk();
- assertEmpty(
- mTest.getDevice().executeShellCommand("sm partition " + mDiskId + " private"));
- final LocalVolumeInfo vol = getAdoptionVolume();
- return vol.uuid;
- }
-
- // Destroy the volume created before
- public void cleanUpVolume() throws Exception {
- mTest.getDevice().executeShellCommand("sm partition " + mDiskId + " public");
- mTest.getDevice().executeShellCommand("sm forget all");
- }
-
- private String getAdoptionDisk() throws Exception {
- // In the case where we run multiple test we cleanup the state of the device. This
- // results in the execution of sm forget all which causes the MountService to "reset"
- // all its knowledge about available drives. This can cause the adoptable drive to
- // become temporarily unavailable.
- int attempt = 0;
- String disks = mTest.getDevice().executeShellCommand("sm list-disks adoptable");
- while ((disks == null || disks.isEmpty()) && attempt++ < 15) {
- Thread.sleep(1000);
- disks = mTest.getDevice().executeShellCommand("sm list-disks adoptable");
- }
-
- if (disks == null || disks.isEmpty()) {
- throw new AssertionError(
- "Devices that claim to support adoptable storage must have "
- + "adoptable media inserted during CTS to verify correct behavior");
- }
- return disks.split("\n")[0].trim();
- }
-
- private static void assertEmpty(String str) {
- if (str != null && str.trim().length() > 0) {
- throw new AssertionError("Expected empty string but found " + str);
- }
- }
-
- private static class LocalVolumeInfo {
- public String volId;
- public String state;
- public String uuid;
-
- LocalVolumeInfo(String line) {
- final String[] split = line.split(" ");
- volId = split[0];
- state = split[1];
- uuid = split[2];
- }
- }
-
- private LocalVolumeInfo getAdoptionVolume() throws Exception {
- String[] lines = null;
- int attempt = 0;
- int mounted_count = 0;
- while (attempt++ < 15) {
- lines = mTest.getDevice().executeShellCommand(
- "sm list-volumes private").split("\n");
- CLog.w("getAdoptionVolume(): " + Arrays.toString(lines));
- for (String line : lines) {
- final LocalVolumeInfo info = new LocalVolumeInfo(line.trim());
- if (!"private".equals(info.volId)) {
- if ("mounted".equals(info.state)) {
- // make sure the storage is mounted and stable for a while
- mounted_count++;
- attempt--;
- if (mounted_count >= 3) {
- return waitForVolumeReady(info);
- }
- } else {
- mounted_count = 0;
- }
- }
- }
- Thread.sleep(1000);
- }
- throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
- }
-
- private LocalVolumeInfo waitForVolumeReady(LocalVolumeInfo vol)
- throws Exception {
- int attempt = 0;
- while (attempt++ < 15) {
- if (mTest.getDevice()
- .executeShellCommand("dumpsys package volumes")
- .contains(vol.volId)) {
- return vol;
- }
- Thread.sleep(1000);
- }
- throw new AssertionError("Volume not ready " + vol.volId);
- }
- }
-
private static class DeviceLockUtils {
private static final String FBE_MODE_EMULATED = "emulated";
@@ -1322,8 +1219,6 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
private final BaseHostJUnit4Test mTest;
- private boolean mIsDeviceLocked = false;
-
DeviceLockUtils(BaseHostJUnit4Test test) {
mTest = test;
}
@@ -1358,8 +1253,6 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
mTest.getDevice().rebootUntilOnline();
}
waitForBootCompleted(mTest.getDevice());
-
- mIsDeviceLocked = true;
}
public void clearScreenLock() throws Exception {
@@ -1384,14 +1277,13 @@ public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
}
public void unlockDevice() throws Exception {
- if (!mIsDeviceLocked) return;
- assertThat(
- mTest.runDeviceTests(
- "com.android.cts.appdataisolation.appa",
- "com.android.cts.appdataisolation.appa.AppATests",
- "testUnlockDevice"))
- .isTrue();
- mIsDeviceLocked = false;
+ try {
+ mTest.runDeviceTests(
+ "com.android.cts.appdataisolation.appa",
+ "com.android.cts.appdataisolation.appa.AppATests",
+ "testUnlockDevice");
+ } catch (Exception ignore) {
+ }
}
private boolean isFbeModeEmulated() throws Exception {
diff --git a/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java b/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java
index c0587488a..9b212959c 100644
--- a/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java
+++ b/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java
@@ -73,12 +73,12 @@ public final class SdkSandboxLifecycleHostTest extends BaseHostJUnit4Test {
mWasRoot = getDevice().isAdbRoot();
getDevice().enableAdbRoot();
-
- cleanUpAppAndSandboxProcesses();
}
@After
public void tearDown() throws Exception {
+ cleanUpAppAndSandboxProcesses();
+
if (!mWasRoot) {
getDevice().disableAdbRoot();
}
@@ -190,15 +190,15 @@ public final class SdkSandboxLifecycleHostTest extends BaseHostJUnit4Test {
@Test
public void testSandboxIsKilledWhenKillswitchEnabled() throws Exception {
try {
- getDevice().executeShellCommand(
- "device_config put sdk_sandbox disable_sdk_sandbox false");
+ getDevice()
+ .executeShellCommand("device_config put adservices disable_sdk_sandbox false");
startActivity(APP_PACKAGE, APP_ACTIVITY);
String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
assertThat(processDump).contains(APP_PACKAGE + '\n');
assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
- getDevice().executeShellCommand(
- "device_config put sdk_sandbox disable_sdk_sandbox true");
+ getDevice()
+ .executeShellCommand("device_config put adservices disable_sdk_sandbox true");
waitForProcessDeath(SANDBOX_1_PROCESS_NAME);
processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/Android.bp b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/Android.bp
index 16d575201..53b32eb1e 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/Android.bp
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/Android.bp
@@ -16,53 +16,14 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_app {
+android_test_helper_app {
name: "SdkSandboxClient",
defaults: ["platform_app_defaults"],
platform_apis: true,
- srcs: [
- "src/**/sdksandboxclient/*.java",
- ],
- static_libs: [
- "androidx.annotation_annotation",
- "androidx.core_core",
- ],
- resource_dirs: [
- "res",
- ],
- manifest: "AndroidManifest_Client1.xml",
-}
-
-android_app {
- name: "SdkSandboxClientTwo",
- defaults: ["platform_app_defaults"],
- platform_apis: true,
- srcs: [
- "src/**/sdksandboxclienttwo/*.java",
- ],
- static_libs: [
- "androidx.annotation_annotation",
- "androidx.core_core",
- ],
- resource_dirs: [
- "res",
- ],
- manifest: "AndroidManifest_Client2.xml",
-}
-
-android_app {
- name: "SdkSandboxClientWebView",
- defaults: ["platform_app_defaults"],
- platform_apis: true,
- srcs: [
- "src/**/sdksandboxclient/*.java",
- ],
- static_libs: [
- "androidx.annotation_annotation",
- "androidx.core_core",
- ],
- resource_dirs: [
- "res",
- ],
- manifest: "AndroidManifest_WebViewClient.xml",
+ srcs: ["src/**/sdksandboxclient/*.java"],
+ static_libs: ["SdkInterfaces"],
+ resource_dirs: ["res"],
+ manifest: "AndroidManifest_Client.xml",
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client1.xml b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client.xml
index 9d2362483..348aa3f0a 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client1.xml
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client.xml
@@ -15,10 +15,12 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:sharedUserId="com.android.sdksandboxclient.shared_uid"
package="com.android.sdksandboxclient" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
+ android:process="com.android.sdksandboxclient.processname"
android:label="@string/title_activity_main">
<activity
@@ -32,5 +34,11 @@
<uses-sdk-library android:name="com.android.sdksandboxcode"
android:versionMajor="1"
android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
+ <uses-sdk-library android:name="com.android.sdksandboxcode_webview"
+ android:versionMajor="1"
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
+ <uses-sdk-library android:name="com.android.sdksandboxcode_mediatee"
+ android:versionMajor="1"
+ android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
</application>
</manifest>
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/layout/activity_main.xml b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/layout/activity_main.xml
index f2502f71e..193eff6d0 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/layout/activity_main.xml
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/layout/activity_main.xml
@@ -22,7 +22,7 @@
<LinearLayout
style="?android:attr/buttonBarButtonStyle"
- android:orientation="horizontal"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -39,6 +39,27 @@
android:layout_width="wrap_content"
style="?android:attr/buttonBarButtonStyle"
android:text="@string/load_surface_package" />
+
+ <Button
+ android:id="@+id/play_video_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/play_video" />
+
+ <Button
+ android:id="@+id/create_file_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/create_file" />
+
+ <Button
+ android:id="@+id/sync_keys_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/sync_keys" />
</LinearLayout>
<SurfaceView
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/values/strings.xml b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/values/strings.xml
index 3b41dd9aa..5c889544f 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/values/strings.xml
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/res/values/strings.xml
@@ -17,6 +17,9 @@
<resources>
<string name="title_activity_main" description="Launcher title">Sdk Sandbox Test</string>
- <string name="load_code_provider">Load Code Provider</string>
+ <string name="load_code_provider">Load Code Providers</string>
<string name="load_surface_package">Load Surface Package</string>
+ <string name="create_file">Create File</string>
+ <string name="play_video">Play Video</string>
+ <string name="sync_keys">Sync Keys</string>
</resources> \ No newline at end of file
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclient/MainActivity.java b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclient/MainActivity.java
index 2a8fe5ea3..7c8749f52 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclient/MainActivity.java
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclient/MainActivity.java
@@ -22,41 +22,74 @@ import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN;
import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SURFACE_PACKAGE;
import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS;
+import android.annotation.NonNull;
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.sdksandbox.LoadSdkException;
import android.app.sdksandbox.RequestSurfacePackageException;
import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SdkSandboxManager;
+import android.app.sdksandbox.interfaces.ISdkApi;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.text.InputType;
import android.util.Log;
import android.view.SurfaceControlViewHost.SurfacePackage;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
import android.widget.Toast;
-import androidx.annotation.NonNull;
+import java.util.Set;
public class MainActivity extends Activity {
- private static final String SDK_NAME = "com.android.sdksandboxcode";
+ // TODO(b/253202014): Add toggle button
+ private static final Boolean IS_WEBVIEW_TESTING_ENABLED = false;
+ private static final String SDK_NAME =
+ IS_WEBVIEW_TESTING_ENABLED
+ ? "com.android.sdksandboxcode_webview"
+ : "com.android.sdksandboxcode";
+ private static final String MEDIATEE_SDK_NAME = "com.android.sdksandboxcode_mediatee";
private static final String TAG = "SdkSandboxClientMainActivity";
- private boolean mSdkLoaded = false;
+ private static final String VIEW_TYPE_KEY = "view-type";
+ private static final String VIDEO_VIEW_VALUE = "video-view";
+ private static final String VIDEO_URL_KEY = "video-url";
+
+ private static final Handler sHandler = new Handler(Looper.getMainLooper());
+
+ private static String sVideoUrl;
+
+ private boolean mSdksLoaded = false;
private SdkSandboxManager mSdkSandboxManager;
private Button mLoadButton;
private Button mRenderButton;
+ private Button mCreateFileButton;
+ private Button mPlayVideoButton;
+ private Button mSyncKeysButton;
+
private SurfaceView mRenderedView;
+ private SandboxedSdk mSandboxedSdk;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- mSdkSandboxManager = getApplicationContext().getSystemService(
- SdkSandboxManager.class);
+ mSdkSandboxManager = getApplicationContext().getSystemService(SdkSandboxManager.class);
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ sVideoUrl = extras.getString(VIDEO_URL_KEY);
+ }
mRenderedView = findViewById(R.id.rendered_view);
mRenderedView.setZOrderOnTop(true);
@@ -64,89 +97,246 @@ public class MainActivity extends Activity {
mLoadButton = findViewById(R.id.load_code_button);
mRenderButton = findViewById(R.id.request_surface_button);
+ mCreateFileButton = findViewById(R.id.create_file_button);
+ mPlayVideoButton = findViewById(R.id.play_video_button);
+ mSyncKeysButton = findViewById(R.id.sync_keys_button);
+
registerLoadSdkProviderButton();
registerLoadSurfacePackageButton();
+ registerCreateFileButton();
+ registerPlayVideoButton();
+ registerSyncKeysButton();
}
private void registerLoadSdkProviderButton() {
mLoadButton.setOnClickListener(
v -> {
- if (!mSdkLoaded) {
- Bundle params = new Bundle();
- OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver =
- new OutcomeReceiver<SandboxedSdk, LoadSdkException>() {
- @Override
- public void onResult(SandboxedSdk sandboxedSdk) {
- mSdkLoaded = true;
- makeToast("Loaded successfully!");
- mLoadButton.setText("Unload SDK");
- }
-
- @Override
- public void onError(LoadSdkException error) {
- makeToast("Failed: " + error);
- }
- };
- mSdkSandboxManager.loadSdk(SDK_NAME, params, Runnable::run, receiver);
- } else {
- mSdkSandboxManager.unloadSdk(SDK_NAME);
- mLoadButton.setText("Load SDK");
- mSdkLoaded = false;
+ if (mSdksLoaded) {
+ resetStateForLoadSdkButton();
+ return;
}
+ // Register for sandbox death event.
+ mSdkSandboxManager.addSdkSandboxProcessDeathCallback(
+ Runnable::run, () -> makeToast("Sdk Sandbox process died"));
+
+ Bundle params = new Bundle();
+ OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver =
+ new OutcomeReceiver<SandboxedSdk, LoadSdkException>() {
+ @Override
+ public void onResult(SandboxedSdk sandboxedSdk) {
+ mSdksLoaded = true;
+ mSandboxedSdk = sandboxedSdk;
+ makeToast("First SDK Loaded successfully!");
+ }
+
+ @Override
+ public void onError(LoadSdkException error) {
+ makeToast("Failed: " + error);
+ }
+ };
+ OutcomeReceiver<SandboxedSdk, LoadSdkException> mediateeReceiver =
+ new OutcomeReceiver<SandboxedSdk, LoadSdkException>() {
+ @Override
+ public void onResult(SandboxedSdk sandboxedSdk) {
+ makeToast("All SDKs Loaded successfully!");
+ // TODO(b/253449573): Add constant string for unload Sdk.
+ mLoadButton.setText("Unload SDK");
+ }
+
+ @Override
+ public void onError(LoadSdkException error) {
+ makeToast("Failed: " + error);
+ resetStateForLoadSdkButton();
+ }
+ };
+ mSdkSandboxManager.loadSdk(SDK_NAME, params, Runnable::run, receiver);
+ mSdkSandboxManager.loadSdk(
+ MEDIATEE_SDK_NAME, params, Runnable::run, mediateeReceiver);
});
}
+ private void resetStateForLoadSdkButton() {
+ mSdkSandboxManager.unloadSdk(SDK_NAME);
+ mSdkSandboxManager.unloadSdk(MEDIATEE_SDK_NAME);
+ mLoadButton.setText("Load SDKs");
+ mSdksLoaded = false;
+ }
+
private void registerLoadSurfacePackageButton() {
OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver =
- new OutcomeReceiver<Bundle, RequestSurfacePackageException>() {
- @Override
- public void onResult(@NonNull Bundle result) {
- new Handler(Looper.getMainLooper())
- .post(
- () -> {
- SurfacePackage surfacePackage =
- result.getParcelable(
- EXTRA_SURFACE_PACKAGE,
- SurfacePackage.class);
- mRenderedView.setChildSurfacePackage(surfacePackage);
- mRenderedView.setVisibility(View.VISIBLE);
- });
- makeToast("Rendered surface view");
+ new RequestSurfacePackageReceiver();
+ mRenderButton.setOnClickListener(
+ v -> {
+ if (mSdksLoaded) {
+ sHandler.post(
+ () -> {
+ mSdkSandboxManager.requestSurfacePackage(
+ SDK_NAME,
+ getRequestSurfacePackageParams(),
+ Runnable::run,
+ receiver);
+ });
+ } else {
+ makeToast("Sdk is not loaded");
}
+ });
+ }
- @Override
- public void onError(@NonNull RequestSurfacePackageException error) {
- makeToast("Failed: " + error);
- Log.e(TAG, error.getMessage(), error);
+ private void registerCreateFileButton() {
+ mCreateFileButton.setOnClickListener(
+ v -> {
+ if (!mSdksLoaded) {
+ makeToast("Sdk is not loaded");
+ return;
}
- };
- mRenderButton.setOnClickListener(
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Set size in MB");
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_NUMBER);
+ builder.setView(input);
+ builder.setPositiveButton(
+ "Create",
+ (dialog, which) -> {
+ int sizeInMb = -1;
+ try {
+ sizeInMb = Integer.parseInt(input.getText().toString());
+ } catch (Exception ignore) {
+ }
+ if (sizeInMb <= 0) {
+ makeToast("Please provide positive integer value");
+ return;
+ }
+ IBinder binder = mSandboxedSdk.getInterface();
+ ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
+ try {
+ String response = sdkApi.createFile(sizeInMb);
+ makeToast(response);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
+ builder.show();
+ });
+ }
+
+ private void registerPlayVideoButton() {
+ if (sVideoUrl == null) {
+ mPlayVideoButton.setVisibility(View.GONE);
+ return;
+ }
+
+ OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver =
+ new RequestSurfacePackageReceiver();
+ mPlayVideoButton.setOnClickListener(
v -> {
- if (mSdkLoaded) {
- new Handler(Looper.getMainLooper())
- .post(
- () -> {
- Bundle params = new Bundle();
- params.putInt(
- EXTRA_WIDTH_IN_PIXELS,
- mRenderedView.getWidth());
- params.putInt(
- EXTRA_HEIGHT_IN_PIXELS,
- mRenderedView.getHeight());
- params.putInt(
- EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
- params.putBinder(
- EXTRA_HOST_TOKEN, mRenderedView.getHostToken());
- mSdkSandboxManager.requestSurfacePackage(
- SDK_NAME, params, Runnable::run, receiver);
- });
+ if (mSdksLoaded) {
+ sHandler.post(
+ () -> {
+ Bundle params = getRequestSurfacePackageParams();
+ params.putString(VIEW_TYPE_KEY, VIDEO_VIEW_VALUE);
+ params.putString(VIDEO_URL_KEY, sVideoUrl);
+ mSdkSandboxManager.requestSurfacePackage(
+ SDK_NAME, params, Runnable::run, receiver);
+ });
} else {
makeToast("Sdk is not loaded");
}
});
}
+ private void registerSyncKeysButton() {
+ mSyncKeysButton.setOnClickListener(
+ v -> {
+ if (!mSdksLoaded) {
+ makeToast("Sdk is not loaded");
+ return;
+ }
+
+ final AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setTitle("Set the key and value to sync");
+ LinearLayout linearLayout = new LinearLayout(this);
+ linearLayout.setOrientation(1); // 1 is for vertical orientation
+ final EditText inputKey = new EditText(this);
+ final EditText inputValue = new EditText(this);
+ linearLayout.addView(inputKey);
+ linearLayout.addView(inputValue);
+ alert.setView(linearLayout);
+
+ alert.setPositiveButton(
+ "Sync",
+ (dialog, which) -> {
+ sHandler.post(
+ () -> {
+ final SharedPreferences pref =
+ PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext());
+ String keyToSync = inputKey.getText().toString();
+ String valueToSync = inputValue.getText().toString();
+ pref.edit().putString(keyToSync, valueToSync).commit();
+ mSdkSandboxManager.addSyncedSharedPreferencesKeys(
+ Set.of(keyToSync));
+ IBinder binder = mSandboxedSdk.getInterface();
+ ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
+ try {
+ // Allow some time for data to sync
+ Thread.sleep(1000);
+ String syncedKeysValue =
+ sdkApi.getSyncedSharedPreferencesString(
+ keyToSync);
+ if (syncedKeysValue.equals(valueToSync)) {
+ makeToast(
+ "Key was synced successfully\n"
+ + "Key is : "
+ + keyToSync
+ + " Value is : "
+ + syncedKeysValue);
+ } else {
+ makeToast("Key was not synced");
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ });
+ alert.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
+ alert.show();
+ });
+ }
+
+ private Bundle getRequestSurfacePackageParams() {
+ Bundle params = new Bundle();
+ params.putInt(EXTRA_WIDTH_IN_PIXELS, mRenderedView.getWidth());
+ params.putInt(EXTRA_HEIGHT_IN_PIXELS, mRenderedView.getHeight());
+ params.putInt(EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
+ params.putBinder(EXTRA_HOST_TOKEN, mRenderedView.getHostToken());
+ return params;
+ }
+
private void makeToast(String message) {
runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show());
}
+
+ private class RequestSurfacePackageReceiver
+ implements OutcomeReceiver<Bundle, RequestSurfacePackageException> {
+
+ @Override
+ public void onResult(Bundle result) {
+ sHandler.post(
+ () -> {
+ SurfacePackage surfacePackage =
+ result.getParcelable(EXTRA_SURFACE_PACKAGE, SurfacePackage.class);
+ mRenderedView.setChildSurfacePackage(surfacePackage);
+ mRenderedView.setVisibility(View.VISIBLE);
+ });
+ makeToast("Rendered surface view");
+ }
+
+ @Override
+ public void onError(@NonNull RequestSurfacePackageException error) {
+ makeToast("Failed: " + error.getMessage());
+ Log.e(TAG, error.getMessage(), error);
+ }
+ }
}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclienttwo/MainActivity.java b/sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclienttwo/MainActivity.java
deleted file mode 100644
index 02eb010de..000000000
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/src/com/android/sdksandboxclienttwo/MainActivity.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.sdksandboxclienttwo;
-
-import static android.app.sdksandbox.SdkSandboxManager.EXTRA_DISPLAY_ID;
-import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS;
-import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN;
-import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SURFACE_PACKAGE;
-import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS;
-
-import android.app.Activity;
-import android.app.sdksandbox.LoadSdkException;
-import android.app.sdksandbox.RequestSurfacePackageException;
-import android.app.sdksandbox.SandboxedSdk;
-import android.app.sdksandbox.SdkSandboxManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.OutcomeReceiver;
-import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.view.SurfaceView;
-import android.view.View;
-import android.widget.Button;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-
-public class MainActivity extends Activity {
- private static final String SDK_NAME = "com.android.sdksandboxcode";
-
- private boolean mSdkLoaded = false;
- private SdkSandboxManager mSdkSandboxManager;
-
- private Button mLoadButton;
- private Button mRenderButton;
- private SurfaceView mRenderedView;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mSdkSandboxManager = getApplicationContext().getSystemService(
- SdkSandboxManager.class);
-
- mRenderedView = findViewById(R.id.rendered_view);
- mRenderedView.setZOrderOnTop(true);
- mRenderedView.setVisibility(View.INVISIBLE);
-
- mLoadButton = findViewById(R.id.load_code_button);
- mRenderButton = findViewById(R.id.request_surface_button);
- registerLoadSdkProviderButton();
- registerLoadSurfacePackageButton();
- }
-
- private void registerLoadSdkProviderButton() {
- mLoadButton.setOnClickListener(
- v -> {
- Bundle params = new Bundle();
- OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver =
- new OutcomeReceiver<SandboxedSdk, LoadSdkException>() {
- @Override
- public void onResult(SandboxedSdk sandboxedSdk) {
- mSdkLoaded = true;
- makeToast("Loaded successfully!");
- }
-
- @Override
- public void onError(LoadSdkException error) {
- makeToast("Failed: " + error);
- }
- };
- mSdkSandboxManager.loadSdk(SDK_NAME, params, Runnable::run, receiver);
- });
- }
-
- private void registerLoadSurfacePackageButton() {
- OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver =
- new OutcomeReceiver<Bundle, RequestSurfacePackageException>() {
- @Override
- public void onResult(@NonNull Bundle result) {
- new Handler(Looper.getMainLooper())
- .post(
- () -> {
- SurfacePackage surfacePackage =
- result.getParcelable(
- EXTRA_SURFACE_PACKAGE,
- SurfacePackage.class);
- mRenderedView.setChildSurfacePackage(surfacePackage);
-
- mRenderedView.setVisibility(View.VISIBLE);
- });
- makeToast("Rendered surface view");
- }
-
- @Override
- public void onError(@NonNull RequestSurfacePackageException error) {
- makeToast("Failed: " + error);
- }
- };
- mRenderButton.setOnClickListener(
- v -> {
- if (mSdkLoaded) {
- new Handler(Looper.getMainLooper())
- .post(
- () -> {
- Bundle params = new Bundle();
- params.putInt(
- EXTRA_WIDTH_IN_PIXELS,
- mRenderedView.getWidth());
- params.putInt(
- EXTRA_HEIGHT_IN_PIXELS,
- mRenderedView.getHeight());
- params.putInt(
- EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
- params.putBinder(
- EXTRA_HOST_TOKEN, mRenderedView.getHostToken());
- mSdkSandboxManager.requestSurfacePackage(
- SDK_NAME, params, Runnable::run, receiver);
- });
- } else {
- makeToast("Sdk is not loaded");
- }
- });
- }
-
- private void makeToast(String message) {
- runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show());
- }
-
-}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/Android.bp b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/Android.bp
new file mode 100644
index 000000000..abb1a0361
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "SdkSandboxClient_SharedUid",
+ defaults: ["platform_app_defaults"],
+ srcs: ["src/**/sdksandboxclient_shareduid/*.java"],
+ static_libs: ["SdkInterfaces"],
+ resource_dirs: ["res"],
+ manifest: "AndroidManifest_Client.xml",
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client2.xml b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/AndroidManifest_Client.xml
index 6aef7a0e8..ae5e27a66 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxClient/AndroidManifest_Client2.xml
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/AndroidManifest_Client.xml
@@ -15,14 +15,16 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.sdksandboxclienttwo" >
+ android:sharedUserId="com.android.sdksandboxclient.shared_uid"
+ package="com.android.sdksandboxclient_shareduid" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
+ android:process="com.android.sdksandboxclient.processname"
android:label="@string/title_activity_main">
<activity
- android:name="com.android.sdksandboxclienttwo.MainActivity"
+ android:name="com.android.sdksandboxclient_shareduid.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/res/layout/activity_main.xml b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/res/layout/activity_main.xml
new file mode 100644
index 000000000..67b520612
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/res/layout/activity_main.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ style="?android:attr/buttonBarButtonStyle"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:id="@+id/load_code_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/load_code_provider" />
+
+ <Button
+ android:id="@+id/request_surface_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/load_surface_package" />
+
+ <Button
+ android:id="@+id/play_video_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/play_video" />
+
+ <Button
+ android:id="@+id/create_file_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/create_file" />
+
+ <Button
+ android:id="@+id/sync_keys_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/sync_keys" />
+
+ <Button
+ android:id="@+id/get_keys_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/get_keys" />
+ </LinearLayout>
+
+ <SurfaceView
+ android:id="@+id/rendered_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/res/values/strings.xml b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/res/values/strings.xml
new file mode 100644
index 000000000..2a3caa1ef
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<resources>
+ <string name="title_activity_main" description="Launcher title">Sdk Sandbox Shared Uid</string>
+ <string name="load_code_provider">Load Code Provider</string>
+ <string name="load_surface_package">Load Surface Package</string>
+ <string name="create_file">Create File</string>
+ <string name="play_video">Play Video</string>
+ <string name="sync_keys">Sync Keys</string>
+ <string name="get_keys">Get Keys</string>
+</resources> \ No newline at end of file
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/src/com/android/sdksandboxclient_shareduid/MainActivity.java b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/src/com/android/sdksandboxclient_shareduid/MainActivity.java
new file mode 100644
index 000000000..a03a7cde9
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxClient_SharedUid/src/com/android/sdksandboxclient_shareduid/MainActivity.java
@@ -0,0 +1,342 @@
+/*
+ * 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.sdksandboxclient_shareduid;
+
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_DISPLAY_ID;
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS;
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN;
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SURFACE_PACKAGE;
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.sdksandbox.LoadSdkException;
+import android.app.sdksandbox.RequestSurfacePackageException;
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SdkSandboxManager;
+import android.app.sdksandbox.interfaces.ISdkApi;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.text.InputType;
+import android.util.Log;
+import android.view.SurfaceControlViewHost.SurfacePackage;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import java.util.Set;
+
+public class MainActivity extends Activity {
+ private static final String SDK_NAME = "com.android.sdksandboxcode";
+ private static final String TAG = "SdkSandboxClient_SharedUidMainActivity";
+
+ private static final String VIEW_TYPE_KEY = "view-type";
+ private static final String VIDEO_VIEW_VALUE = "video-view";
+ private static final String VIDEO_URL_KEY = "video-url";
+
+ private static final Handler sHandler = new Handler(Looper.getMainLooper());
+
+ private static String sVideoUrl;
+
+ private boolean mSdkLoaded = false;
+ private SdkSandboxManager mSdkSandboxManager;
+
+ private Button mLoadButton;
+ private Button mRenderButton;
+ private Button mCreateFileButton;
+ private Button mPlayVideoButton;
+ private Button mSyncKeysButton;
+ private Button mGetKeysButton;
+
+ private SurfaceView mRenderedView;
+
+ private SandboxedSdk mSandboxedSdk;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ mSdkSandboxManager = getApplicationContext().getSystemService(SdkSandboxManager.class);
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ sVideoUrl = extras.getString(VIDEO_URL_KEY);
+ }
+
+ mRenderedView = findViewById(R.id.rendered_view);
+ mRenderedView.setZOrderOnTop(true);
+ mRenderedView.setVisibility(View.INVISIBLE);
+
+ mLoadButton = findViewById(R.id.load_code_button);
+ mRenderButton = findViewById(R.id.request_surface_button);
+ mCreateFileButton = findViewById(R.id.create_file_button);
+ mPlayVideoButton = findViewById(R.id.play_video_button);
+ mSyncKeysButton = findViewById(R.id.sync_keys_button);
+ mGetKeysButton = findViewById(R.id.get_keys_button);
+
+ registerLoadSdkProviderButton();
+ registerLoadSurfacePackageButton();
+ registerCreateFileButton();
+ registerPlayVideoButton();
+ registerSyncKeysButton();
+ registerGetKeysButton();
+ }
+
+ private void registerLoadSdkProviderButton() {
+ mLoadButton.setOnClickListener(
+ v -> {
+ if (!mSdkLoaded) {
+ // Register for sandbox death event.
+ mSdkSandboxManager.addSdkSandboxProcessDeathCallback(
+ Runnable::run, () -> makeToast("Sdk Sandbox process died"));
+
+ Bundle params = new Bundle();
+ OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver =
+ new OutcomeReceiver<SandboxedSdk, LoadSdkException>() {
+ @Override
+ public void onResult(SandboxedSdk sandboxedSdk) {
+ mSdkLoaded = true;
+
+ mSandboxedSdk = sandboxedSdk;
+
+ makeToast("Loaded successfully!");
+ mLoadButton.setText("Unload SDK");
+ }
+
+ @Override
+ public void onError(LoadSdkException error) {
+ makeToast("Failed: " + error);
+ }
+ };
+ mSdkSandboxManager.loadSdk(SDK_NAME, params, Runnable::run, receiver);
+ } else {
+ mSdkSandboxManager.unloadSdk(SDK_NAME);
+ mLoadButton.setText("Load SDK");
+ mSdkLoaded = false;
+ }
+ });
+ }
+
+ private void registerLoadSurfacePackageButton() {
+ OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver =
+ new RequestSurfacePackageReceiver();
+ mRenderButton.setOnClickListener(
+ v -> {
+ if (mSdkLoaded) {
+ sHandler.post(
+ () -> {
+ mSdkSandboxManager.requestSurfacePackage(
+ SDK_NAME,
+ getRequestSurfacePackageParams(),
+ Runnable::run,
+ receiver);
+ });
+ } else {
+ makeToast("Sdk is not loaded");
+ }
+ });
+ }
+
+ private void registerCreateFileButton() {
+ mCreateFileButton.setOnClickListener(
+ v -> {
+ if (!mSdkLoaded) {
+ makeToast("Sdk is not loaded");
+ return;
+ }
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Set size in MB");
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_NUMBER);
+ builder.setView(input);
+ builder.setPositiveButton(
+ "Create",
+ (dialog, which) -> {
+ int sizeInMb = -1;
+ try {
+ sizeInMb = Integer.parseInt(input.getText().toString());
+ } catch (Exception ignore) {
+ }
+ if (sizeInMb <= 0) {
+ makeToast("Please provide positive integer value");
+ return;
+ }
+ IBinder binder = mSandboxedSdk.getInterface();
+ ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
+ try {
+ String response = sdkApi.createFile(sizeInMb);
+ makeToast(response);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
+ builder.show();
+ });
+ }
+
+ private void registerPlayVideoButton() {
+ if (sVideoUrl == null) {
+ mPlayVideoButton.setVisibility(View.GONE);
+ return;
+ }
+
+ OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver =
+ new RequestSurfacePackageReceiver();
+ mPlayVideoButton.setOnClickListener(
+ v -> {
+ if (mSdkLoaded) {
+ sHandler.post(
+ () -> {
+ Bundle params = getRequestSurfacePackageParams();
+ params.putString(VIEW_TYPE_KEY, VIDEO_VIEW_VALUE);
+ params.putString(VIDEO_URL_KEY, sVideoUrl);
+ mSdkSandboxManager.requestSurfacePackage(
+ SDK_NAME, params, Runnable::run, receiver);
+ });
+ } else {
+ makeToast("Sdk is not loaded");
+ }
+ });
+ }
+
+ private void registerSyncKeysButton() {
+ mSyncKeysButton.setOnClickListener(
+ v -> {
+ if (!mSdkLoaded) {
+ makeToast("Sdk is not loaded");
+ return;
+ }
+
+ final AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setTitle("Set the key and value to sync");
+ LinearLayout linearLayout = new LinearLayout(this);
+ linearLayout.setOrientation(1); // 1 is for vertical orientation
+ final EditText inputKey = new EditText(this);
+ final EditText inputValue = new EditText(this);
+ linearLayout.addView(inputKey);
+ linearLayout.addView(inputValue);
+ alert.setView(linearLayout);
+
+ alert.setPositiveButton(
+ "Sync",
+ (dialog, which) -> {
+ sHandler.post(
+ () -> {
+ final SharedPreferences pref =
+ PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext());
+ String keyToSync = inputKey.getText().toString();
+ String valueToSync = inputValue.getText().toString();
+ pref.edit().putString(keyToSync, valueToSync).commit();
+ mSdkSandboxManager.addSyncedSharedPreferencesKeys(
+ Set.of(keyToSync));
+ IBinder binder = mSandboxedSdk.getInterface();
+ ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
+ try {
+ // Allow some time for data to sync
+ Thread.sleep(1000);
+ String syncedKeysValue =
+ sdkApi.getSyncedSharedPreferencesString(
+ keyToSync);
+ if (syncedKeysValue.equals(valueToSync)) {
+ makeToast(
+ "Key was synced successfully\n"
+ + "Key is : "
+ + keyToSync
+ + " Value is : "
+ + syncedKeysValue);
+ } else {
+ makeToast("Key was not synced");
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ });
+ alert.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
+ alert.show();
+ });
+ }
+
+ private void registerGetKeysButton() {
+ mGetKeysButton.setOnClickListener(
+ v -> {
+ sHandler.post(
+ () -> {
+ try {
+ // Allow some time for data to sync
+ Thread.sleep(1000);
+ Set<String> syncedKeys =
+ mSdkSandboxManager.getSyncedSharedPreferencesKeys();
+ makeToast(
+ "The number of synced keys is : "
+ + String.valueOf(syncedKeys.size())
+ + " and the keys are : "
+ + String.join(", ", syncedKeys));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ });
+ }
+
+ private Bundle getRequestSurfacePackageParams() {
+ Bundle params = new Bundle();
+ params.putInt(EXTRA_WIDTH_IN_PIXELS, mRenderedView.getWidth());
+ params.putInt(EXTRA_HEIGHT_IN_PIXELS, mRenderedView.getHeight());
+ params.putInt(EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
+ params.putBinder(EXTRA_HOST_TOKEN, mRenderedView.getHostToken());
+ return params;
+ }
+
+ private void makeToast(String message) {
+ runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show());
+ }
+
+ private class RequestSurfacePackageReceiver
+ implements OutcomeReceiver<Bundle, RequestSurfacePackageException> {
+
+ @Override
+ public void onResult(Bundle result) {
+ sHandler.post(
+ () -> {
+ SurfacePackage surfacePackage =
+ result.getParcelable(EXTRA_SURFACE_PACKAGE, SurfacePackage.class);
+ mRenderedView.setChildSurfacePackage(surfacePackage);
+ mRenderedView.setVisibility(View.VISIBLE);
+ });
+ makeToast("Rendered surface view");
+ }
+
+ @Override
+ public void onError(@NonNull RequestSurfacePackageException error) {
+ makeToast("Failed: " + error.getMessage());
+ Log.e(TAG, error.getMessage(), error);
+ }
+ }
+}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/Android.bp b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/Android.bp
index a1b6d6470..dfa63cc70 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/Android.bp
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/Android.bp
@@ -16,26 +16,50 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_defaults {
+ name: "SdkSandboxCodeDefaults",
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+ static_libs: ["SdkInterfaces"],
+ resource_dirs: ["res"],
+ certificate: ":sdksandbox-test",
+}
+
android_test_helper_app {
name: "SdkSandboxCodeProvider",
- defaults: ["platform_app_defaults"],
- certificate: ":sdksandbox-test",
+ defaults: [
+ "platform_app_defaults",
+ "SdkSandboxCodeDefaults"
+ ],
srcs: [
"src/**/sdksandboxcode_1/*.java",
+ "src/**/apiimplementation/*.java",
],
- resource_dirs: ["res"],
- platform_apis: true,
manifest: "AndroidManifest.xml",
}
android_test_helper_app {
name: "SdkSandboxWebViewProvider",
- defaults: ["platform_app_defaults"],
- certificate: ":sdksandbox-test",
+ defaults: [
+ "platform_app_defaults",
+ "SdkSandboxCodeDefaults"
+ ],
srcs: [
"src/**/sdksandboxcode_webview/*.java",
+ "src/**/apiimplementation/*.java"
],
- resource_dirs: ["res"],
- platform_apis: true,
manifest: "AndroidManifest_WebViewProvider.xml",
}
+
+android_test_helper_app {
+ name: "SdkSandboxMediateeProvider",
+ defaults: [
+ "platform_app_defaults",
+ "SdkSandboxCodeDefaults"
+ ],
+ srcs: [
+ "src/**/sdksandboxcode_mediatee/*.java",
+ "src/**/apiimplementation/*.java"
+ ],
+ manifest: "AndroidManifest_MediateeProvider.xml",
+}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_MediateeProvider.xml b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_MediateeProvider.xml
new file mode 100644
index 000000000..5dd393459
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_MediateeProvider.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.sdksandboxcode_mediatee">
+
+ <application>
+ <sdk-library android:name="com.android.sdksandboxcode_mediatee"
+ android:versionMajor="1" />
+ <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
+ android:value="com.android.sdksandboxcode_mediatee.SandboxedSdkMediateeProvider" />
+ </application>
+
+</manifest>
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_WebViewProvider.xml b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_WebViewProvider.xml
index 07731fbfb..474a1c53a 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_WebViewProvider.xml
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/AndroidManifest_WebViewProvider.xml
@@ -18,7 +18,7 @@
package="com.android.sdksandboxcode_webview">
<application>
- <sdk-library android:name="com.android.sdksandboxcode"
+ <sdk-library android:name="com.android.sdksandboxcode_webview"
android:versionMajor="1" />
<property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
android:value="com.android.sdksandboxcode_webview.SandboxedSdkWebViewProvider" />
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/apiimplementation/SdkApi.java b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/apiimplementation/SdkApi.java
new file mode 100644
index 000000000..65fa819cc
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/apiimplementation/SdkApi.java
@@ -0,0 +1,68 @@
+/*
+ * 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.apiimplementation;
+
+import android.app.sdksandbox.interfaces.ISdkApi;
+import android.app.sdksandbox.sdkprovider.SdkSandboxController;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.RemoteException;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class SdkApi extends ISdkApi.Stub {
+ private final Context mContext;
+
+ public SdkApi(Context sdkContext) {
+ mContext = sdkContext;
+ }
+
+ @Override
+ public String createFile(int sizeInMb) throws RemoteException {
+ try {
+ final Path path = Paths.get(mContext.getDataDir().getPath(), "file.txt");
+ Files.deleteIfExists(path);
+ Files.createFile(path);
+ final byte[] buffer = new byte[sizeInMb * 1024 * 1024];
+ Files.write(path, buffer);
+
+ final File file = new File(path.toString());
+ final long actualFilzeSize = file.length() / (1024 * 1024);
+ return "Created " + actualFilzeSize + " MB file successfully";
+ } catch (IOException e) {
+ throw new RemoteException(e);
+ }
+ }
+
+ @Override
+ public String getMessage() {
+ return "Message Received from a sandboxedSDK";
+ }
+
+ @Override
+ public String getSyncedSharedPreferencesString(String key) {
+ return getClientSharedPreferences().getString(key, "");
+ }
+
+ private SharedPreferences getClientSharedPreferences() {
+ return mContext.getSystemService(SdkSandboxController.class).getClientSharedPreferences();
+ }
+}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_1/SampleSandboxedSdkProvider.java b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_1/SampleSandboxedSdkProvider.java
index 0fdc41fef..ae2dc4d35 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_1/SampleSandboxedSdkProvider.java
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_1/SampleSandboxedSdkProvider.java
@@ -18,27 +18,42 @@ package com.android.sdksandboxcode_1;
import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SandboxedSdkProvider;
+import android.app.sdksandbox.interfaces.ISdkApi;
+import android.app.sdksandbox.sdkprovider.SdkSandboxController;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
-import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
+import android.widget.VideoView;
+import com.android.apiimplementation.SdkApi;
+
+import java.util.List;
import java.util.Random;
public class SampleSandboxedSdkProvider extends SandboxedSdkProvider {
private static final String TAG = "SampleSandboxedSdkProvider";
+ private static final String VIEW_TYPE_KEY = "view-type";
+ private static final String VIDEO_VIEW_VALUE = "video-view";
+ private static final String VIDEO_URL_KEY = "video-url";
+ // TODO(b/253202014): Add toggle button
+ private static final boolean SDK_SDK_COMM_ENABLED = true;
+
@Override
public SandboxedSdk onLoadSdk(Bundle params) {
- return new SandboxedSdk(new Binder());
+ return new SandboxedSdk(new SdkApi(getContext()));
}
@Override
@@ -48,14 +63,20 @@ public class SampleSandboxedSdkProvider extends SandboxedSdkProvider {
@Override
public View getView(Context windowContext, Bundle params, int width, int height) {
- return new TestView(windowContext, getContext(), width, height);
+ String type = params.getString(VIEW_TYPE_KEY, "");
+ if (VIDEO_VIEW_VALUE.equals(type)) {
+ String videoUrl = params.getString(VIDEO_URL_KEY, "");
+ return new TestVideoView(windowContext, videoUrl);
+ }
+ return new TestView(windowContext, getContext());
}
private static class TestView extends View {
+ private static final CharSequence MEDIATEE_SDK = "com.android.sdksandboxcode_mediatee";
private Context mSdkContext;
- TestView(Context windowContext, Context sdkContext, int width, int height) {
+ TestView(Context windowContext, Context sdkContext) {
super(windowContext);
mSdkContext = sdkContext;
}
@@ -69,7 +90,37 @@ public class SampleSandboxedSdkProvider extends SandboxedSdkProvider {
paint.setColor(Color.WHITE);
paint.setTextSize(50);
Random random = new Random();
- String message = mSdkContext.getResources().getString(R.string.view_message);
+ String message;
+ if (SDK_SDK_COMM_ENABLED) {
+ SandboxedSdk mediateeSdk;
+ try {
+ // get message from another sandboxed SDK
+ List<SandboxedSdk> sandboxedSdks =
+ mSdkContext
+ .getSystemService(SdkSandboxController.class)
+ .getSandboxedSdks();
+ mediateeSdk =
+ sandboxedSdks.stream()
+ .filter(
+ s ->
+ s.getSharedLibraryInfo()
+ .getName()
+ .contains(MEDIATEE_SDK))
+ .findAny()
+ .get();
+ } catch (Exception e) {
+ throw new RuntimeException("Error in sdk-sdk communication ", e);
+ }
+ try {
+ IBinder binder = mediateeSdk.getInterface();
+ ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
+ message = sdkApi.getMessage();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ message = mSdkContext.getResources().getString(R.string.view_message);
+ }
int c = Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256));
canvas.drawColor(c);
canvas.drawText(message, 75, 75, paint);
@@ -89,4 +140,18 @@ public class SampleSandboxedSdkProvider extends SandboxedSdkProvider {
}
}
+
+ private static class TestVideoView extends VideoView {
+
+ TestVideoView(Context windowContext, String url) {
+ super(windowContext);
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ setVideoURI(Uri.parse(url));
+ requestFocus();
+ start();
+ });
+ }
+ }
}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_mediatee/SandboxedSdkMediateeProvider.java b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_mediatee/SandboxedSdkMediateeProvider.java
new file mode 100644
index 000000000..35ecf5071
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_mediatee/SandboxedSdkMediateeProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sdksandboxcode_mediatee;
+
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.apiimplementation.SdkApi;
+
+public class SandboxedSdkMediateeProvider extends SandboxedSdkProvider {
+ @Override
+ public SandboxedSdk onLoadSdk(Bundle params) {
+ return new SandboxedSdk(new SdkApi(getContext()));
+ }
+
+ @Override
+ public View getView(Context windowContext, Bundle params, int width, int height) {
+ return null;
+ }
+}
diff --git a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_webview/SandboxedSdkWebViewProvider.java b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_webview/SandboxedSdkWebViewProvider.java
index 8ee569f13..419e00533 100644
--- a/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_webview/SandboxedSdkWebViewProvider.java
+++ b/sdksandbox/tests/manual-test-apps/SdkSandboxCodeProvider/src/com/android/sdksandboxcode_webview/SandboxedSdkWebViewProvider.java
@@ -19,19 +19,20 @@ package com.android.sdksandboxcode_webview;
import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SandboxedSdkProvider;
import android.content.Context;
-import android.os.Binder;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
+import com.android.apiimplementation.SdkApi;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class SandboxedSdkWebViewProvider extends SandboxedSdkProvider {
@Override
public SandboxedSdk onLoadSdk(Bundle params) {
- return new SandboxedSdk(new Binder());
+ return new SandboxedSdk(new SdkApi(getContext()));
}
@Override
@@ -77,7 +78,7 @@ public class SandboxedSdkWebViewProvider extends SandboxedSdkProvider {
WebView wv = new WebView(context);
WebSettings settings = wv.getSettings();
initializeSettings(settings);
- wv.loadUrl("https://www.google.com");
+ wv.loadUrl("localhost:5037");
mWebView = wv;
latch.countDown();
}
diff --git a/sdksandbox/tests/manual-test-apps/sdkinterfaces/Android.bp b/sdksandbox/tests/manual-test-apps/sdkinterfaces/Android.bp
new file mode 100644
index 000000000..7bbce2cc8
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/sdkinterfaces/Android.bp
@@ -0,0 +1,23 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+java_library {
+ name: "SdkInterfaces",
+ srcs: [
+ "src/**/*.aidl",
+ ],
+}
diff --git a/sdksandbox/tests/manual-test-apps/sdkinterfaces/src/android/app/sdksandbox/interfaces/ISdkApi.aidl b/sdksandbox/tests/manual-test-apps/sdkinterfaces/src/android/app/sdksandbox/interfaces/ISdkApi.aidl
new file mode 100644
index 000000000..c2a923705
--- /dev/null
+++ b/sdksandbox/tests/manual-test-apps/sdkinterfaces/src/android/app/sdksandbox/interfaces/ISdkApi.aidl
@@ -0,0 +1,23 @@
+// 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 android.app.sdksandbox.interfaces;
+
+interface ISdkApi {
+ String createFile(int sizeInMb);
+ String getSyncedSharedPreferencesString(String key);
+ // Representative method for SDK-SDK communication. This can be any method called by other
+ // sdks for example, to loadAd for a given adDetail
+ String getMessage();
+}
diff --git a/sdksandbox/tests/perf/scenarios/Android.bp b/sdksandbox/tests/perf/scenarios/Android.bp
new file mode 100644
index 000000000..de3db83b6
--- /dev/null
+++ b/sdksandbox/tests/perf/scenarios/Android.bp
@@ -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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "Android-Apache-2.0",
+ ],
+}
+
+java_library {
+ name: "sdksandbox-perf-test-scenarios",
+ static_libs: [
+ "platform-test-annotations",
+ ],
+
+ libs: [
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "platform-test-rules",
+ "ub-uiautomator",
+ "common-platform-scenarios",
+ "platform-test-options",
+ "androidx.media_media",
+ ],
+
+ srcs: ["src/**/*.java"],
+
+} \ No newline at end of file
diff --git a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxControllerUnitTest.java b/sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/LoadSdk.java
index 7f2f47076..cacb907e0 100644
--- a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxControllerUnitTest.java
+++ b/sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/LoadSdk.java
@@ -13,37 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.sdksandbox.test.scenario;
-package android.app.sdksandbox;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-
-import androidx.test.platform.app.InstrumentationRegistry;
+import android.os.SystemClock;
+import android.platform.test.scenario.annotation.Scenario;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+@Scenario
@RunWith(JUnit4.class)
-public class SdkSandboxControllerUnitTest {
- private Context mContext;
+public class LoadSdk {
- @Before
- public void setup() {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private SdkSandboxTestHelper mSdkSandboxTestHelper = new SdkSandboxTestHelper();
+
+ @AfterClass
+ public static void tearDown() throws IOException {
+ SdkSandboxTestHelper.closeClientApp();
}
- @Test
- public void testCreateInstance() throws Exception {
- SdkSandboxController controller = new SdkSandboxController(mContext);
- assertThat(controller).isNotNull();
+ @Before
+ public void setup() throws Exception {
+ mSdkSandboxTestHelper.openClientApp();
}
@Test
- public void testGetInstance() throws Exception {
- assertThat(mContext.getSystemService(SdkSandboxController.class)).isNotNull();
+ public void testLoadSdk() {
+ mSdkSandboxTestHelper.loadSandboxSdk();
+
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(2));
}
}
diff --git a/sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/RemoteRenderAd.java b/sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/RemoteRenderAd.java
new file mode 100644
index 000000000..fe0c6e364
--- /dev/null
+++ b/sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/RemoteRenderAd.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.sdksandbox.test.scenario;
+
+import android.os.SystemClock;
+import android.platform.test.scenario.annotation.Scenario;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+@Scenario
+@RunWith(JUnit4.class)
+public class RemoteRenderAd {
+
+ private static final int NUMBER_OF_ADS = 3;
+ private static final int TIME_BETWEEN_RENDERS_S = 2;
+ private SdkSandboxTestHelper mSdkSandboxTestHelper = new SdkSandboxTestHelper();
+
+ @AfterClass
+ public static void tearDown() throws IOException {
+ SdkSandboxTestHelper.closeClientApp();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ mSdkSandboxTestHelper.openClientApp();
+ mSdkSandboxTestHelper.loadSandboxSdk();
+ }
+
+ @Test
+ public void testRemoteRenderAd() {
+
+ // Loop to remote render ad multiple times sequentially
+ for (int i = 0; i < NUMBER_OF_ADS; i++) {
+ mSdkSandboxTestHelper.remoteRenderAd();
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(TIME_BETWEEN_RENDERS_S));
+ }
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(2));
+ }
+}
diff --git a/sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/SdkSandboxTestHelper.java b/sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/SdkSandboxTestHelper.java
new file mode 100644
index 000000000..6ce3a4d42
--- /dev/null
+++ b/sdksandbox/tests/perf/scenarios/src/android/sdksandbox/test/scenario/SdkSandboxTestHelper.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 android.sdksandbox.test.scenario;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/** Helper class for Sdk Sandbox e2e perf tests. */
+public class SdkSandboxTestHelper {
+ private static final UiDevice sUiDevice = UiDevice.getInstance(getInstrumentation());
+
+ private static final long UI_NAVIGATION_WAIT_MS = 1000;
+ private static final String SANDBOX_TEST_CLIENT_APP = "com.android.sdksandboxclient";
+ private static final String LOAD_BUTTON = "load_code_button";
+ private static final String RENDER_BUTTON = "request_surface_button";
+
+ /** Open sandbox client test app using shell command line. */
+ public void openClientApp() throws Exception {
+ sUiDevice.executeShellCommand(
+ "am start " + SANDBOX_TEST_CLIENT_APP + "/" + ".MainActivity");
+ }
+
+ /** Load sdk on sandbox client test app by clicking the loadSdk button. */
+ public void loadSandboxSdk() {
+ if (getLoadSdkButton() != null) {
+ getLoadSdkButton().click();
+ } else {
+ throw new RuntimeException("Did not find 'Load SDK' button.");
+ }
+
+ // wait until loadSdk
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
+
+ assertThat(getLoadSdkButton().getText()).isEqualTo("Unload SDK");
+ }
+
+ /** Remote render ad on sandbox client test app by clicking request surface button. */
+ public void remoteRenderAd() {
+ if (getRequestSurfaceButton() != null) {
+ getRequestSurfaceButton().click();
+ } else {
+ throw new RuntimeException("Did not find 'Load Surface Package' button.");
+ }
+ }
+
+ public static void closeClientApp() throws IOException {
+ sUiDevice.executeShellCommand("am force-stop " + SANDBOX_TEST_CLIENT_APP);
+ }
+
+ private UiObject2 getLoadSdkButton() {
+ return sUiDevice.wait(
+ Until.findObject(By.res(SANDBOX_TEST_CLIENT_APP, LOAD_BUTTON)),
+ UI_NAVIGATION_WAIT_MS);
+ }
+
+ private UiObject2 getRequestSurfaceButton() {
+ return sUiDevice.wait(
+ Until.findObject(By.res(SANDBOX_TEST_CLIENT_APP, RENDER_BUTTON)),
+ UI_NAVIGATION_WAIT_MS);
+ }
+}
diff --git a/sdksandbox/tests/perf/scenarios/tests/Android.bp b/sdksandbox/tests/perf/scenarios/tests/Android.bp
new file mode 100644
index 000000000..c929b7423
--- /dev/null
+++ b/sdksandbox/tests/perf/scenarios/tests/Android.bp
@@ -0,0 +1,41 @@
+// 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "Android-Apache-2.0",
+ ],
+}
+
+android_test {
+ name: "SdkSandboxPerfScenarioTests",
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "collector-device-lib-platform",
+ "microbenchmark-device-lib",
+ "platform-test-options",
+ "platform-test-rules",
+ "sdksandbox-perf-test-scenarios",
+ "ub-uiautomator",
+ ],
+ // Certificate and platform api is needed for collector-device-lib-platform.
+ certificate: "platform",
+ platform_apis: true,
+ srcs: ["src//**/*.java"],
+ test_suites: ["device-tests"],
+ data: [":perfetto_artifacts"],
+ min_sdk_version: "33",
+}
diff --git a/sdksandbox/tests/perf/scenarios/tests/AndroidManifest.xml b/sdksandbox/tests/perf/scenarios/tests/AndroidManifest.xml
new file mode 100644
index 000000000..4a250bb27
--- /dev/null
+++ b/sdksandbox/tests/perf/scenarios/tests/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 Google Inc. All Rights Reserved. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.sdksandbox.test.scenario" >
+ <uses-sdk android:minSdkVersion="33"/>
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.sdksandbox.test.scenario"
+ android:label="Sdk Sandbox Scenario-Based Tests" />
+</manifest>
diff --git a/sdksandbox/tests/perf/scenarios/tests/AndroidTest.xml b/sdksandbox/tests/perf/scenarios/tests/AndroidTest.xml
new file mode 100644
index 000000000..88fdb3b7a
--- /dev/null
+++ b/sdksandbox/tests/perf/scenarios/tests/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Config for integration test scenarios">
+ <option name="test-tag" value="SdkSandboxPerfScenarioTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- DeviceSetup will root the device. -->
+ <option name="set-test-harness" value="true" />
+ </target_preparer>
+
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="SdkSandboxPerfScenarioTests.apk"/>
+ <option name="test-file-name" value="SdkSandboxCodeProvider.apk"/>
+ <option name="test-file-name" value="SdkSandboxMediateeProvider.apk"/>
+ <option name="test-file-name" value="SdkSandboxWebViewProvider.apk"/>
+ <option name="test-file-name" value="SdkSandboxClient.apk"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="cmd sdk_sandbox set-state --enabled" />
+ <option name="teardown-command" value="cmd sdk_sandbox set-state --reset" />
+ </target_preparer>
+
+
+ <!-- Needed for pulling the collected trace config on to the host. -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector>
+
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results. -->
+ <option name="isolated-storage" value="false" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.sdksandbox.test.scenario"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+
+ <!-- Listener for collecting the traces and waiting for the device to stabilize. -->
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener" />
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ <!-- PerfettoListener related arguments. -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg"
+ key="perfetto_config_file" value="trace_config.textproto" />
+ </test>
+</configuration> \ No newline at end of file
diff --git a/sdksandbox/tests/perf/scenarios/tests/src/android/sdksandbox/test/scenario/LoadSdkMicrobenchmark.java b/sdksandbox/tests/perf/scenarios/tests/src/android/sdksandbox/test/scenario/LoadSdkMicrobenchmark.java
new file mode 100644
index 000000000..4f399684f
--- /dev/null
+++ b/sdksandbox/tests/perf/scenarios/tests/src/android/sdksandbox/test/scenario/LoadSdkMicrobenchmark.java
@@ -0,0 +1,32 @@
+/*
+ * 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 android.sdksandbox.test.scenario;
+
+import android.platform.test.microbenchmark.Microbenchmark;
+import android.platform.test.rule.DropCachesRule;
+import android.platform.test.rule.KillAppsRule;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+@RunWith(Microbenchmark.class)
+public class LoadSdkMicrobenchmark extends LoadSdk {
+ @Rule
+ public RuleChain rules =
+ RuleChain.outerRule(new KillAppsRule("com.android.sdksandboxclient"))
+ .around(new DropCachesRule());
+}
diff --git a/sdksandbox/tests/perf/scenarios/tests/src/android/sdksandbox/test/scenario/RemoteRenderAdMicrobenchmark.java b/sdksandbox/tests/perf/scenarios/tests/src/android/sdksandbox/test/scenario/RemoteRenderAdMicrobenchmark.java
new file mode 100644
index 000000000..f107c1e5e
--- /dev/null
+++ b/sdksandbox/tests/perf/scenarios/tests/src/android/sdksandbox/test/scenario/RemoteRenderAdMicrobenchmark.java
@@ -0,0 +1,32 @@
+/*
+ * 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 android.sdksandbox.test.scenario;
+
+import android.platform.test.microbenchmark.Microbenchmark;
+import android.platform.test.rule.DropCachesRule;
+import android.platform.test.rule.KillAppsRule;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+@RunWith(Microbenchmark.class)
+public class RemoteRenderAdMicrobenchmark extends RemoteRenderAd {
+ @Rule
+ public RuleChain rules =
+ RuleChain.outerRule(new KillAppsRule("com.android.sdksandboxclient"))
+ .around(new DropCachesRule());
+}
diff --git a/sdksandbox/tests/test-apps/CodeProviderWithResources/Android.bp b/sdksandbox/tests/test-apps/CodeProviderWithResources/Android.bp
index d6309c8c4..6e5edcae3 100644
--- a/sdksandbox/tests/test-apps/CodeProviderWithResources/Android.bp
+++ b/sdksandbox/tests/test-apps/CodeProviderWithResources/Android.bp
@@ -30,5 +30,7 @@ android_test_helper_app {
enabled: true,
},
test_suites: ["general-tests"],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/test-apps/EmptyProvider/Android.bp b/sdksandbox/tests/test-apps/EmptyProvider/Android.bp
new file mode 100644
index 000000000..ae17031ea
--- /dev/null
+++ b/sdksandbox/tests/test-apps/EmptyProvider/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "EmptySdkProviderApp",
+ manifest: "AndroidManifest.xml",
+ certificate: ":sdksandbox-test",
+ srcs: [
+ "src/**/*.java",
+ ],
+ visibility: [
+ "//cts/tests/tests:__subpackages__",
+ ],
+} \ No newline at end of file
diff --git a/sdksandbox/tests/test-apps/EmptyProvider/AndroidManifest.xml b/sdksandbox/tests/test-apps/EmptyProvider/AndroidManifest.xml
new file mode 100644
index 000000000..e899a8dc6
--- /dev/null
+++ b/sdksandbox/tests/test-apps/EmptyProvider/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.emptysdkprovider">
+
+ <application>
+ <sdk-library android:name="com.android.emptysdkprovider"
+ android:versionMajor="1"/>
+ <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
+ android:value="com.android.emptysdkprovider.EmptySdkProvider"/>
+ </application>
+</manifest>
diff --git a/sdksandbox/tests/test-apps/EmptyProvider/src/com/android/emptysdkprovider/EmptySdkProvider.java b/sdksandbox/tests/test-apps/EmptyProvider/src/com/android/emptysdkprovider/EmptySdkProvider.java
new file mode 100644
index 000000000..dcbfdde7f
--- /dev/null
+++ b/sdksandbox/tests/test-apps/EmptyProvider/src/com/android/emptysdkprovider/EmptySdkProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.emptysdkprovider;
+
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.view.View;
+
+/*
+This Empty Provider is loaded into the sandbox in testing scenarios where we wish to keep the
+sandbox process alive even when all other test sdk providers are unloaded. The Empty Provider hence
+is not required to attach to any views.
+ */
+public class EmptySdkProvider extends SandboxedSdkProvider {
+
+ @Override
+ public final SandboxedSdk onLoadSdk(Bundle params) {
+ return new SandboxedSdk(new Binder());
+ }
+
+ @Override
+ public final View getView(Context windowContext, Bundle params, int width, int height) {
+ return null;
+ }
+}
diff --git a/sdksandbox/tests/test-apps/FailingSdkProvider/Android.bp b/sdksandbox/tests/test-apps/FailingSdkProvider/Android.bp
index 279ff255b..e0f344010 100644
--- a/sdksandbox/tests/test-apps/FailingSdkProvider/Android.bp
+++ b/sdksandbox/tests/test-apps/FailingSdkProvider/Android.bp
@@ -24,4 +24,6 @@ android_test_helper_app {
"src/**/*.java",
],
platform_apis: true,
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/test-apps/TestProvider/Android.bp b/sdksandbox/tests/test-apps/TestProvider/Android.bp
index d191b0c8c..447966d28 100644
--- a/sdksandbox/tests/test-apps/TestProvider/Android.bp
+++ b/sdksandbox/tests/test-apps/TestProvider/Android.bp
@@ -29,5 +29,7 @@ android_test_helper_app {
enabled: true,
},
test_suites: ["general-tests"],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/test-apps/code-provider-app/Android.bp b/sdksandbox/tests/test-apps/code-provider-app/Android.bp
index 79d10c1c9..a2d5b9468 100644
--- a/sdksandbox/tests/test-apps/code-provider-app/Android.bp
+++ b/sdksandbox/tests/test-apps/code-provider-app/Android.bp
@@ -27,5 +27,7 @@ android_test_helper_app {
enabled: true,
},
test_suites: ["general-tests"],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/test-apps/service-app/Android.bp b/sdksandbox/tests/test-apps/service-app/Android.bp
index 7de92620f..bee0a1161 100644
--- a/sdksandbox/tests/test-apps/service-app/Android.bp
+++ b/sdksandbox/tests/test-apps/service-app/Android.bp
@@ -27,4 +27,6 @@ android_test_helper_app {
enabled: true,
},
test_suites: ["general-tests"],
+ min_sdk_version: "33",
+ target_sdk_version: "33",
}
diff --git a/sdksandbox/tests/testutils/Android.bp b/sdksandbox/tests/testutils/Android.bp
index 531cd2d65..1edf9c9e1 100644
--- a/sdksandbox/tests/testutils/Android.bp
+++ b/sdksandbox/tests/testutils/Android.bp
@@ -19,7 +19,7 @@ package {
java_library {
name: "SdkSandboxTestUtils",
srcs: [
- "src/**/*.java",
+ "src/**/testutils/*.java",
],
libs: [
"framework-sdksandbox.impl",
@@ -30,7 +30,13 @@ java_library {
visibility: [
// TODO(b/222118402): adjust once code is moved
"//packages/modules/AdServices:__subpackages__",
- // TODO(b/222118402): remove once code is moved
- "//packages/modules/SupplementalProcess/tests:__subpackages__",
],
}
+
+java_library_host {
+ name: "SdkSandboxHostTestUtils",
+ srcs: [
+ "src/**/hosttestutils/*.java",
+ ],
+ libs: ["tradefed"],
+} \ No newline at end of file
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/hosttestutils/AdoptableStorageUtils.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/hosttestutils/AdoptableStorageUtils.java
new file mode 100644
index 000000000..7e62875f0
--- /dev/null
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/hosttestutils/AdoptableStorageUtils.java
@@ -0,0 +1,135 @@
+/*
+ * 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 android.app.sdksandbox.hosttestutils;
+
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import java.util.Arrays;
+
+public class AdoptableStorageUtils {
+
+ private final BaseHostJUnit4Test mTest;
+
+ private String mDiskId;
+
+ public AdoptableStorageUtils(BaseHostJUnit4Test test) {
+ mTest = test;
+ }
+
+ public boolean isAdoptableStorageSupported() throws Exception {
+ boolean hasFeature =
+ mTest.getDevice().hasFeature("feature:android.software.adoptable_storage");
+ boolean hasFstab =
+ Boolean.parseBoolean(
+ mTest.getDevice().executeShellCommand("sm has-adoptable").trim());
+ return hasFeature && hasFstab;
+ }
+
+ // Creates a new volume in adoptable storage and returns its uuid
+ public String createNewVolume() throws Exception {
+ mDiskId = getAdoptionDisk();
+ assertEmpty(mTest.getDevice().executeShellCommand("sm partition " + mDiskId + " private"));
+ final LocalVolumeInfo vol = getAdoptionVolume();
+ return vol.uuid;
+ }
+
+ // Destroy the volume created before
+ public void cleanUpVolume() throws Exception {
+ mTest.getDevice().executeShellCommand("sm partition " + mDiskId + " public");
+ mTest.getDevice().executeShellCommand("sm forget all");
+ }
+
+ private String getAdoptionDisk() throws Exception {
+ // In the case where we run multiple test we cleanup the state of the device. This
+ // results in the execution of sm forget all which causes the MountService to "reset"
+ // all its knowledge about available drives. This can cause the adoptable drive to
+ // become temporarily unavailable.
+ int attempt = 0;
+ String disks = mTest.getDevice().executeShellCommand("sm list-disks adoptable");
+ while ((disks == null || disks.isEmpty()) && attempt++ < 15) {
+ Thread.sleep(1000);
+ disks = mTest.getDevice().executeShellCommand("sm list-disks adoptable");
+ }
+
+ if (disks == null || disks.isEmpty()) {
+ throw new AssertionError(
+ "Devices that claim to support adoptable storage must have "
+ + "adoptable media inserted during CTS to verify correct behavior");
+ }
+ return disks.split("\n")[0].trim();
+ }
+
+ private static void assertEmpty(String str) {
+ if (str != null && str.trim().length() > 0) {
+ throw new AssertionError("Expected empty string but found " + str);
+ }
+ }
+
+ private static class LocalVolumeInfo {
+ public String volId;
+ public String state;
+ public String uuid;
+
+ LocalVolumeInfo(String line) {
+ final String[] split = line.split(" ");
+ volId = split[0];
+ state = split[1];
+ uuid = split[2];
+ }
+ }
+
+ private LocalVolumeInfo getAdoptionVolume() throws Exception {
+ String[] lines = null;
+ int attempt = 0;
+ int mounted_count = 0;
+ while (attempt++ < 15) {
+ lines = mTest.getDevice().executeShellCommand("sm list-volumes private").split("\n");
+ LogUtil.CLog.w("getAdoptionVolume(): " + Arrays.toString(lines));
+ for (String line : lines) {
+ final LocalVolumeInfo info = new LocalVolumeInfo(line.trim());
+ if (!"private".equals(info.volId)) {
+ if ("mounted".equals(info.state)) {
+ // make sure the storage is mounted and stable for a while
+ mounted_count++;
+ attempt--;
+ if (mounted_count >= 3) {
+ return waitForVolumeReady(info);
+ }
+ } else {
+ mounted_count = 0;
+ }
+ }
+ }
+ Thread.sleep(1000);
+ }
+ throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
+ }
+
+ private LocalVolumeInfo waitForVolumeReady(LocalVolumeInfo vol) throws Exception {
+ int attempt = 0;
+ while (attempt++ < 15) {
+ if (mTest.getDevice()
+ .executeShellCommand("dumpsys package volumes")
+ .contains(vol.volId)) {
+ return vol;
+ }
+ Thread.sleep(1000);
+ }
+ throw new AssertionError("Volume not ready " + vol.volId);
+ }
+}
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/hosttestutils/SecondaryUserUtils.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/hosttestutils/SecondaryUserUtils.java
new file mode 100644
index 000000000..d063b89d8
--- /dev/null
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/hosttestutils/SecondaryUserUtils.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 android.app.sdksandbox.hosttestutils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+public class SecondaryUserUtils {
+
+ private static final long NUMBER_OF_POLLS = 2 * 60;
+ private static final long POLL_INTERVAL_IN_MILLIS = 1000;
+
+ private final BaseHostJUnit4Test mTest;
+
+ private int mOriginalUserId = -1;
+ private int mSecondaryUserId = -1;
+
+ public SecondaryUserUtils(BaseHostJUnit4Test test) {
+ mTest = test;
+ }
+
+ public int createAndStartSecondaryUser() throws Exception {
+ if (mSecondaryUserId != -1) {
+ throw new IllegalStateException("Cannot create secondary user, it already exists");
+ }
+ mOriginalUserId = mTest.getDevice().getCurrentUser();
+ String name = "SdkSandboxStorageHost_User" + System.currentTimeMillis();
+ mSecondaryUserId = mTest.getDevice().createUser(name);
+ // Note we can't install apps on a locked user, so we wait
+ mTest.getDevice().startUser(mSecondaryUserId, /*waitFlag=*/ true);
+ return mSecondaryUserId;
+ }
+
+ public void removeSecondaryUserIfNecessary() throws Exception {
+ removeSecondaryUserIfNecessary(/*waitForUserDataDeletion=*/ false);
+ }
+
+ public void removeSecondaryUserIfNecessary(boolean waitForUserDataDeletion) throws Exception {
+ if (mSecondaryUserId == -1) {
+ return;
+ }
+
+ final int userBeingRemoved = mSecondaryUserId;
+ // Set to -1 so that we can create new users later, even when removal goes wrong
+ mSecondaryUserId = -1;
+
+ if (mOriginalUserId != -1 && userBeingRemoved != -1) {
+ // Can't remove the 2nd user without switching out of it
+ assertThat(mTest.getDevice().switchUser(mOriginalUserId)).isTrue();
+ mTest.getDevice().removeUser(userBeingRemoved);
+ if (waitForUserDataDeletion) {
+ waitForUserDataDeletion(userBeingRemoved);
+ }
+ }
+ }
+
+ private void waitForUserDataDeletion(int userId) throws Exception {
+ final String deSdkSandboxDataRootPath = "/data/misc_de/" + userId + "/sdksandbox";
+ for (int i = 0; i < NUMBER_OF_POLLS; ++i) {
+ if (!mTest.getDevice().isDirectory(deSdkSandboxDataRootPath)) {
+ return;
+ }
+ Thread.sleep(POLL_INTERVAL_IN_MILLIS);
+ }
+ fail("User data was not deleted for UserId " + userId);
+ }
+}
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeLoadSdkCallback.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeLoadSdkCallback.java
index 6cad76c39..a5ed18e89 100644
--- a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeLoadSdkCallback.java
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeLoadSdkCallback.java
@@ -25,25 +25,21 @@ import android.os.OutcomeReceiver;
import com.google.common.base.Preconditions;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
public class FakeLoadSdkCallback implements OutcomeReceiver<SandboxedSdk, LoadSdkException> {
- private final CountDownLatch mLoadSdkLatch = new CountDownLatch(1);
+ private final WaitableCountDownLatch mLoadSdkLatch;
private boolean mLoadSdkSuccess;
private SandboxedSdk mSandboxedSdk;
private LoadSdkException mLoadSdkException = null;
- private final int mWaitTimeSec;
public FakeLoadSdkCallback() {
- mWaitTimeSec = 5;
+ mLoadSdkLatch = new WaitableCountDownLatch(5);
}
public FakeLoadSdkCallback(int waitTimeSec) {
Preconditions.checkArgument(waitTimeSec > 0, "Callback should use a positive wait time");
- mWaitTimeSec = waitTimeSec;
+ mLoadSdkLatch = new WaitableCountDownLatch(waitTimeSec);
}
@Override
@@ -65,7 +61,7 @@ public class FakeLoadSdkCallback implements OutcomeReceiver<SandboxedSdk, LoadSd
}
public boolean isLoadSdkSuccessful(boolean ignoreSdkAlreadyLoadedError) {
- waitForLatch(mLoadSdkLatch);
+ mLoadSdkLatch.waitForLatch();
if (ignoreSdkAlreadyLoadedError
&& ((mLoadSdkException == null)
|| (mLoadSdkException.getLoadSdkErrorCode()
@@ -76,37 +72,24 @@ public class FakeLoadSdkCallback implements OutcomeReceiver<SandboxedSdk, LoadSd
}
public int getLoadSdkErrorCode() {
- waitForLatch(mLoadSdkLatch);
+ mLoadSdkLatch.waitForLatch();
assertThat(mLoadSdkSuccess).isFalse();
return mLoadSdkException.getLoadSdkErrorCode();
}
public String getLoadSdkErrorMsg() {
- waitForLatch(mLoadSdkLatch);
+ mLoadSdkLatch.waitForLatch();
assertThat(mLoadSdkSuccess).isFalse();
return mLoadSdkException.getMessage();
}
public SandboxedSdk getSandboxedSdk() {
- waitForLatch(mLoadSdkLatch);
+ mLoadSdkLatch.waitForLatch();
return mSandboxedSdk;
}
public LoadSdkException getLoadSdkException() {
- waitForLatch(mLoadSdkLatch);
+ mLoadSdkLatch.waitForLatch();
return mLoadSdkException;
}
-
- private void waitForLatch(CountDownLatch latch) {
- try {
- // Wait for callback to be called
- if (!latch.await(mWaitTimeSec, TimeUnit.SECONDS)) {
- throw new IllegalStateException(
- "Callback not called within " + mWaitTimeSec + " seconds");
- }
- } catch (InterruptedException e) {
- throw new IllegalStateException(
- "Interrupted while waiting on callback: " + e.getMessage());
- }
- }
}
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeRequestSurfacePackageCallback.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeRequestSurfacePackageCallback.java
index 25673e3a2..389460795 100644
--- a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeRequestSurfacePackageCallback.java
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeRequestSurfacePackageCallback.java
@@ -16,74 +16,72 @@
package android.app.sdksandbox.testutils;
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SURFACE_PACKAGE;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.sdksandbox.RequestSurfacePackageException;
import android.os.Bundle;
import android.os.OutcomeReceiver;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import android.view.SurfaceControlViewHost;
public class FakeRequestSurfacePackageCallback
implements OutcomeReceiver<Bundle, RequestSurfacePackageException> {
- private CountDownLatch mSurfacePackageLatch = new CountDownLatch(1);
+ private final WaitableCountDownLatch mSurfacePackageLatch = new WaitableCountDownLatch(5);
private boolean mSurfacePackageSuccess;
- private int mErrorCode;
- private String mErrorMsg;
- private Bundle mExtraErrorInformation;
+ private RequestSurfacePackageException mSurfacePackageException;
+ private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
@Override
public void onError(RequestSurfacePackageException exception) {
mSurfacePackageSuccess = false;
- mErrorCode = exception.getRequestSurfacePackageErrorCode();
- mErrorMsg = exception.getMessage();
- mExtraErrorInformation = exception.getExtraErrorInformation();
+ mSurfacePackageException = exception;
mSurfacePackageLatch.countDown();
}
@Override
public void onResult(Bundle response) {
mSurfacePackageSuccess = true;
+ mSurfacePackage =
+ response.getParcelable(
+ EXTRA_SURFACE_PACKAGE, SurfaceControlViewHost.SurfacePackage.class);
mSurfacePackageLatch.countDown();
}
public boolean isRequestSurfacePackageSuccessful() {
- waitForLatch(mSurfacePackageLatch);
+ mSurfacePackageLatch.waitForLatch();
return mSurfacePackageSuccess;
}
public Bundle getExtraErrorInformation() {
- waitForLatch(mSurfacePackageLatch);
+ mSurfacePackageLatch.waitForLatch();
assertThat(mSurfacePackageSuccess).isFalse();
- return mExtraErrorInformation;
+ return mSurfacePackageException.getExtraErrorInformation();
+ }
+
+ public SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
+ mSurfacePackageLatch.waitForLatch();
+ assertThat(mSurfacePackageSuccess).isTrue();
+ return mSurfacePackage;
}
public int getSurfacePackageErrorCode() {
- waitForLatch(mSurfacePackageLatch);
+ mSurfacePackageLatch.waitForLatch();
assertThat(mSurfacePackageSuccess).isFalse();
- return mErrorCode;
+ return mSurfacePackageException.getRequestSurfacePackageErrorCode();
}
public String getSurfacePackageErrorMsg() {
- waitForLatch(mSurfacePackageLatch);
+ mSurfacePackageLatch.waitForLatch();
assertThat(mSurfacePackageSuccess).isFalse();
- return mErrorMsg;
+ return mSurfacePackageException.getMessage();
}
- private void waitForLatch(CountDownLatch latch) {
- try {
- // Wait for callback to be called
- final int waitTime = 5;
- if (!latch.await(waitTime, TimeUnit.SECONDS)) {
- throw new IllegalStateException(
- "Callback not called within " + waitTime + " seconds");
- }
- } catch (InterruptedException e) {
- throw new IllegalStateException(
- "Interrupted while waiting on callback: " + e.getMessage());
- }
+ public RequestSurfacePackageException getSurfacePackageException() {
+ mSurfacePackageLatch.waitForLatch();
+ assertThat(mSurfacePackageSuccess).isFalse();
+ return mSurfacePackageException;
}
}
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeSharedPreferencesSyncCallback.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeSharedPreferencesSyncCallback.java
index 6ef20711f..0be002208 100644
--- a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeSharedPreferencesSyncCallback.java
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/FakeSharedPreferencesSyncCallback.java
@@ -18,15 +18,10 @@ package android.app.sdksandbox.testutils;
import android.app.sdksandbox.ISharedPreferencesSyncCallback;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
public class FakeSharedPreferencesSyncCallback extends ISharedPreferencesSyncCallback.Stub {
- private CountDownLatch mSyncDataLatch = new CountDownLatch(1);
-
boolean mOnSandboxStartCalled = false;
-
boolean mOnErrorCalled = false;
+ private WaitableCountDownLatch mSyncDataLatch = new WaitableCountDownLatch(5);
private int mErrorCode;
private String mErrorMsg;
@@ -45,40 +40,27 @@ public class FakeSharedPreferencesSyncCallback extends ISharedPreferencesSyncCal
}
public boolean hasSandboxStarted() {
- waitForLatch(mSyncDataLatch);
+ mSyncDataLatch.waitForLatch();
return mOnSandboxStartCalled;
}
public boolean hasError() {
- waitForLatch(mSyncDataLatch);
+ mSyncDataLatch.waitForLatch();
return mOnErrorCalled;
}
public int getErrorCode() {
- waitForLatch(mSyncDataLatch);
+ mSyncDataLatch.waitForLatch();
return mErrorCode;
}
public String getErrorMsg() {
- waitForLatch(mSyncDataLatch);
+ mSyncDataLatch.waitForLatch();
return mErrorMsg;
}
public void resetLatch() {
- mSyncDataLatch = new CountDownLatch(1);
+ mSyncDataLatch = new WaitableCountDownLatch(5);
}
- private void waitForLatch(CountDownLatch latch) {
- try {
- // Wait for callback to be called
- final int waitTime = 5;
- if (!latch.await(waitTime, TimeUnit.SECONDS)) {
- throw new IllegalStateException(
- "Callback not called within " + waitTime + " seconds");
- }
- } catch (InterruptedException e) {
- throw new IllegalStateException(
- "Interrupted while waiting on callback: " + e.getMessage());
- }
- }
}
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java
index 567bbf80d..d313d3d49 100644
--- a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java
@@ -21,8 +21,8 @@ import android.app.sdksandbox.IRequestSurfacePackageCallback;
import android.app.sdksandbox.ISdkSandboxManager;
import android.app.sdksandbox.ISdkSandboxProcessDeathCallback;
import android.app.sdksandbox.ISharedPreferencesSyncCallback;
+import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SharedPreferencesUpdate;
-import android.content.pm.SharedLibraryInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -61,9 +61,8 @@ public class StubSdkSandboxManagerService extends ISdkSandboxManager.Stub {
Bundle params,
IRequestSurfacePackageCallback callback) {}
-
@Override
- public List<SharedLibraryInfo> getLoadedSdkLibrariesInfo(
+ public List<SandboxedSdk> getSandboxedSdks(
String callingPackageName, long timeAppCalledSystemServer) {
return Collections.emptyList();
}
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkToServiceLink.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkToServiceLink.java
index 2d2ce27cc..d3bde8b15 100644
--- a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkToServiceLink.java
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkToServiceLink.java
@@ -18,34 +18,20 @@ package android.app.sdksandbox.testutils;
import android.annotation.NonNull;
import android.app.sdksandbox.ISdkToServiceCallback;
-import android.content.pm.SharedLibraryInfo;
-import android.content.pm.VersionedPackage;
-import android.os.RemoteException;
+import android.app.sdksandbox.SandboxedSdk;
+import android.os.Binder;
import java.util.ArrayList;
import java.util.List;
public class StubSdkToServiceLink extends ISdkToServiceCallback.Stub {
- public static final SharedLibraryInfo SHARED_LIBRARY_INFO =
- new SharedLibraryInfo(
- "testpath",
- "test",
- new ArrayList<>(),
- "test",
- 0L,
- SharedLibraryInfo.TYPE_STATIC,
- new VersionedPackage("test", 0L),
- null,
- null,
- false /* isNative */);
-
@Override
@NonNull
- public List<SharedLibraryInfo> getLoadedSdkLibrariesInfo(String clientName)
- throws RemoteException {
- ArrayList<SharedLibraryInfo> list = new ArrayList<>();
- list.add(SHARED_LIBRARY_INFO);
+ public List<SandboxedSdk> getSandboxedSdks(String clientName) {
+ ArrayList<SandboxedSdk> list = new ArrayList<>();
+ SandboxedSdk sandboxedSdk = new SandboxedSdk(new Binder());
+ list.add(sandboxedSdk);
return list;
}
}
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/WaitableCountDownLatch.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/WaitableCountDownLatch.java
new file mode 100644
index 000000000..78b560b7f
--- /dev/null
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/WaitableCountDownLatch.java
@@ -0,0 +1,53 @@
+/*
+ * 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 android.app.sdksandbox.testutils;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class WaitableCountDownLatch {
+
+ private static final String EMPTY_STRING = "";
+ private final CountDownLatch mLatch;
+ private final int mWaitTimeSec;
+
+ public WaitableCountDownLatch(int waitTimeSec) {
+ mLatch = new CountDownLatch(1);
+ mWaitTimeSec = waitTimeSec;
+ }
+
+ public void countDown() {
+ mLatch.countDown();
+ }
+
+ public void waitForLatch() {
+ waitForLatch(EMPTY_STRING);
+ }
+
+ public void waitForLatch(String message) {
+ try {
+ // Wait for callback to be called
+ if (!mLatch.await(mWaitTimeSec, TimeUnit.SECONDS)) {
+ throw new IllegalStateException(
+ message + " Latch timed out after " + mWaitTimeSec + " seconds");
+ }
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(
+ "Interrupted while waiting for latch " + e.getMessage());
+ }
+ }
+}
diff --git a/sdksandbox/tests/testutils/testscenario/testrule/Android.bp b/sdksandbox/tests/testutils/testscenario/testrule/Android.bp
new file mode 100644
index 000000000..ed05facc4
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/testrule/Android.bp
@@ -0,0 +1,37 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "CtsSdkSandboxTestScenario",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "truth-prebuilt",
+ "CtsSdkSandboxTestExecutor",
+ "SdkSandboxTestUtils",
+ "compatibility-device-util-axt",
+ "androidx.test.core",
+ ],
+ visibility: [
+ "//cts/tests/tests:__subpackages__",
+ ],
+ manifest: "AndroidManifest.xml",
+ resource_dirs: ["res"],
+}
+
diff --git a/sdksandbox/tests/testutils/testscenario/testrule/AndroidManifest.xml b/sdksandbox/tests/testutils/testscenario/testrule/AndroidManifest.xml
new file mode 100644
index 000000000..b46086757
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/testrule/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.sdksandbox.testutils.testscenario">
+ <application android:maxRecents="1">
+ <activity android:name="android.app.sdksandbox.testutils.testscenario.SdkSandboxCtsActivity"
+ android:exported="true"
+ android:label="SdkSandboxCtsActivity"
+ android:screenOrientation="nosensor">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/sdksandbox/tests/testutils/testscenario/testrule/res/layout/surfaceview_layout.xml b/sdksandbox/tests/testutils/testscenario/testrule/res/layout/surfaceview_layout.xml
new file mode 100644
index 000000000..9db38a595
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/testrule/res/layout/surfaceview_layout.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:keepScreenOn="true"
+ android:orientation="vertical">
+
+ <SurfaceView
+ android:id="@+id/rendered_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</LinearLayout>
diff --git a/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/KeepSdkSandboxAliveRule.java b/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/KeepSdkSandboxAliveRule.java
new file mode 100644
index 000000000..9e94e35fd
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/KeepSdkSandboxAliveRule.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.app.sdksandbox.testutils.testscenario;
+
+import android.app.sdksandbox.SdkSandboxManager;
+import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * This rule is meant to be used as a {@link org.junit.ClassRule} for test suites where we intend to
+ * keep the sandbox process alive even when all other test sdks are unloaded. This rule loads a
+ * given empty sdk into a sandbox and unloads it after all tests have finished executing.
+ */
+public class KeepSdkSandboxAliveRule implements TestRule {
+
+ private static final String TAG = KeepSdkSandboxAliveRule.class.getName();
+ private final String mSdkName;
+ private SdkSandboxManager mSdkSandboxManager;
+
+ public KeepSdkSandboxAliveRule(String sdkName) {
+ mSdkName = sdkName;
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ // This statement would wrap around a test suite, similar to @BeforeClass and @AfterClass
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try (ActivityScenario scenario =
+ ActivityScenario.launch(SdkSandboxCtsActivity.class)) {
+ final Context context =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ mSdkSandboxManager = context.getSystemService(SdkSandboxManager.class);
+ final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+ mSdkSandboxManager.loadSdk(mSdkName, new Bundle(), Runnable::run, callback);
+ if (!callback.isLoadSdkSuccessful()) {
+ if (callback.getLoadSdkErrorCode()
+ == SdkSandboxManager.LOAD_SDK_SDK_SANDBOX_DISABLED) {
+ // TODO: Change this Log to an ExecutionCondition in Junit5 so that test
+ // suite is skipped if condition is not met
+ Log.w(TAG, "Sdk Sandbox is disabled");
+ } else {
+ throw callback.getLoadSdkException();
+ }
+ }
+ }
+ try {
+ base.evaluate();
+ } finally {
+ try (ActivityScenario scenario =
+ ActivityScenario.launch(SdkSandboxCtsActivity.class)) {
+ mSdkSandboxManager.unloadSdk(mSdkName);
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxCtsActivity.java b/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxCtsActivity.java
new file mode 100644
index 000000000..ab1183579
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxCtsActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.app.sdksandbox.testutils.testscenario;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SdkSandboxCtsActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.surfaceview_layout);
+ }
+}
diff --git a/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxScenarioRule.java b/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxScenarioRule.java
new file mode 100644
index 000000000..914e752d4
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/testrule/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxScenarioRule.java
@@ -0,0 +1,173 @@
+/*
+ * 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 android.app.sdksandbox.testutils.testscenario;
+
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_DISPLAY_ID;
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS;
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN;
+import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.sdksandbox.RequestSurfacePackageException;
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SdkSandboxManager;
+import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
+import android.app.sdksandbox.testutils.FakeRequestSurfacePackageCallback;
+import android.app.sdksandbox.testutils.WaitableCountDownLatch;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.SurfaceView;
+import android.view.View;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This rule is used to invoke tests inside SDKs. It loads a given Sdk, calls for a test to be
+ * executed inside given Sdk and unloads the Sdk once the execution is finished.
+ * assertSdkTestRunPasses() contains the logic to trigger an in-SDK test and retrieve its results,
+ * while {@link SdkSandboxTestScenarioRunner} handles the Sdk-side logic for test execution.
+ */
+public final class SdkSandboxScenarioRule implements TestRule {
+ // We need to allow a fair amount of time to time out since we might
+ // want to execute fairly large tests.
+ private static final int TEST_TIMEOUT_S = 60;
+ private final String mSdkName;
+ private ISdkSandboxTestExecutor mTestExecutor;
+
+ public SdkSandboxScenarioRule(String sdkName) {
+ mSdkName = sdkName;
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ // This statement would wrap around every test, similar to @Before and @After
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try (ActivityScenario scenario =
+ ActivityScenario.launch(SdkSandboxCtsActivity.class)) {
+ final Context context =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ SdkSandboxManager sdkSandboxManager =
+ context.getSystemService(SdkSandboxManager.class);
+ final SandboxedSdk sdk = getLoadedSdk(sdkSandboxManager, mSdkName);
+ assertThat(scenario.getState()).isEqualTo(Lifecycle.State.RESUMED);
+ setView(scenario, sdkSandboxManager);
+ mTestExecutor = ISdkSandboxTestExecutor.Stub.asInterface(sdk.getInterface());
+ try {
+ base.evaluate();
+ } finally {
+ sdkSandboxManager.unloadSdk(mSdkName);
+ }
+ }
+ }
+ };
+ }
+
+ public void assertSdkTestRunPasses(String testMethodName) throws Exception {
+ assertSdkTestRunPasses(testMethodName, new Bundle());
+ }
+
+ public void assertSdkTestRunPasses(String testMethodName, Bundle params) throws Exception {
+ WaitableCountDownLatch testDoneLatch = new WaitableCountDownLatch(TEST_TIMEOUT_S);
+ AtomicReference<String> errorRef = new AtomicReference<>(null);
+
+ ISdkSandboxResultCallback.Stub callback =
+ new ISdkSandboxResultCallback.Stub() {
+ public void onResult() {
+ testDoneLatch.countDown();
+ }
+
+ public void onError(String errorMessage) {
+ if (TextUtils.isEmpty(errorMessage)) {
+ errorRef.set("Error executing test: Sdk returned no stacktrace");
+ } else {
+ errorRef.set(errorMessage);
+ }
+ testDoneLatch.countDown();
+ }
+ };
+
+ assertThat(mTestExecutor).isNotNull();
+ mTestExecutor.executeTest(testMethodName, params, callback);
+
+ testDoneLatch.waitForLatch("Sdk did not return any response");
+
+ if (errorRef.get() != null) assertWithMessage(errorRef.get()).fail();
+ assertThat(true).isTrue();
+ }
+
+ private static SandboxedSdk getLoadedSdk(SdkSandboxManager mSdkSandboxManager, String sdkName)
+ throws Exception {
+ final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+ mSdkSandboxManager.loadSdk(sdkName, new Bundle(), Runnable::run, callback);
+ if (!callback.isLoadSdkSuccessful()) {
+ Assume.assumeTrue(
+ "Skipping test because Sdk Sandbox is disabled",
+ callback.getLoadSdkErrorCode()
+ != SdkSandboxManager.LOAD_SDK_SDK_SANDBOX_DISABLED);
+ throw callback.getLoadSdkException();
+ }
+ return callback.getSandboxedSdk();
+ }
+
+ private void setView(ActivityScenario scenario, SdkSandboxManager mSdkSandboxManager)
+ throws Exception {
+ AtomicReference<RequestSurfacePackageException> surfacePackageException =
+ new AtomicReference<>(null);
+ scenario.onActivity(
+ activity -> {
+ final SurfaceView renderedView = activity.findViewById(R.id.rendered_view);
+ final FakeRequestSurfacePackageCallback surfacePackageCallback =
+ new FakeRequestSurfacePackageCallback();
+
+ Bundle params = new Bundle();
+ params.putInt(EXTRA_WIDTH_IN_PIXELS, renderedView.getWidth());
+ params.putInt(EXTRA_HEIGHT_IN_PIXELS, renderedView.getHeight());
+ params.putInt(EXTRA_DISPLAY_ID, activity.getDisplay().getDisplayId());
+ params.putBinder(EXTRA_HOST_TOKEN, renderedView.getHostToken());
+
+ mSdkSandboxManager.requestSurfacePackage(
+ mSdkName, params, Runnable::run, surfacePackageCallback);
+
+ if (!surfacePackageCallback.isRequestSurfacePackageSuccessful()) {
+ surfacePackageException.set(
+ surfacePackageCallback.getSurfacePackageException());
+ } else {
+ renderedView.setChildSurfacePackage(
+ surfacePackageCallback.getSurfacePackage());
+ renderedView.setVisibility(View.VISIBLE);
+ renderedView.setZOrderOnTop(true);
+ }
+ });
+ if (surfacePackageException.get() != null) {
+ throw surfacePackageException.get();
+ }
+ }
+}
diff --git a/sdksandbox/tests/testutils/testscenario/testrunner/Android.bp b/sdksandbox/tests/testutils/testscenario/testrunner/Android.bp
new file mode 100644
index 000000000..110008217
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/testrunner/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "CtsSdkSandboxTestRunner",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "truth-prebuilt",
+ "CtsSdkSandboxTestExecutor",
+ "compatibility-device-util-axt",
+ ],
+ visibility: [
+ "//cts/tests/tests:__subpackages__",
+ ],
+}
+
diff --git a/sdksandbox/tests/testutils/testscenario/testrunner/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxTestScenarioRunner.java b/sdksandbox/tests/testutils/testscenario/testrunner/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxTestScenarioRunner.java
new file mode 100644
index 000000000..762ffe06c
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/testrunner/src/android/app/sdksandbox/testutils/testscenario/SdkSandboxTestScenarioRunner.java
@@ -0,0 +1,166 @@
+/*
+ * 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 android.app.sdksandbox.testutils.testscenario;
+
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkProvider;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+import androidx.test.annotation.UiThreadTest;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * This class contains all the binding logic needed for a test suite within an SDK. To set up SDK
+ * tests, extend this class. To attach a custom view to your tests, override beforeEachTest() to
+ * return your view (for example a WebView).
+ */
+public abstract class SdkSandboxTestScenarioRunner extends SandboxedSdkProvider {
+ private static final String TAG = SdkSandboxTestScenarioRunner.class.getName();
+
+ private Object mTestInstance;
+
+ /**
+ * This API allows you to provide a separate test class to execute tests against. It will
+ * default to the class instance it is already attached to.
+ */
+ public Object getTestInstance() {
+ return this;
+ }
+
+ public View beforeEachTest(Context windowContext, Bundle params, int width, int height) {
+ return new View(windowContext);
+ }
+
+ @Override
+ public final View getView(Context windowContext, Bundle params, int width, int height) {
+ return beforeEachTest(windowContext, params, width, height);
+ }
+
+ @Override
+ public final SandboxedSdk onLoadSdk(Bundle params) {
+ mTestInstance = getTestInstance();
+
+ ISdkSandboxTestExecutor.Stub testExecutor =
+ new ISdkSandboxTestExecutor.Stub() {
+ public void executeTest(
+ String testName,
+ Bundle testParams,
+ ISdkSandboxResultCallback resultCallback) {
+ try {
+ // We allow test authors to write a test without the bundle parameters
+ // for convenience.
+ // We will first look for the test name with a bundle parameter
+ // if we don't find that, we will load the test without a parameter.
+ boolean hasParams = true;
+ Method testMethod =
+ findTest(testName, /*throwException*/ false, Bundle.class);
+ if (testMethod == null) {
+ hasParams = false;
+ testMethod = findTest(testName, /*throwException*/ true);
+ }
+
+ if (testMethod.isAnnotationPresent(UiThreadTest.class)) {
+ // The method reference has to be final before being
+ // used inside a lambda to ensure the variant stays the
+ // same
+ final Method method = testMethod;
+ final boolean params = hasParams;
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ invokeTestMethod(
+ method,
+ params,
+ testParams,
+ resultCallback);
+ });
+ } else {
+ invokeTestMethod(testMethod, hasParams, testParams, resultCallback);
+ }
+ } catch (NoSuchMethodException error) {
+ try {
+ resultCallback.onError(getStackTrace(error));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to find method and report back");
+ }
+ }
+ }
+ };
+
+ return new SandboxedSdk(testExecutor);
+ }
+
+ private Method findTest(String testName, boolean throwException, Class<?>... parameterTypes)
+ throws NoSuchMethodException {
+ try {
+ return mTestInstance.getClass().getMethod(testName, parameterTypes);
+ } catch (NoSuchMethodException error) {
+ if (throwException) {
+ throw error;
+ }
+ return null;
+ }
+ }
+
+ private void invokeTestMethod(
+ final Method method,
+ final boolean hasParams,
+ final Bundle params,
+ final ISdkSandboxResultCallback resultCallback) {
+ try {
+ if (hasParams) {
+ method.invoke(mTestInstance, params);
+ } else {
+ method.invoke(mTestInstance);
+ }
+ resultCallback.onResult();
+ } catch (Exception error) {
+ String errorStackTrace = getStackTrace(error);
+
+ try {
+ resultCallback.onError(errorStackTrace);
+ } catch (Exception ex) {
+ if (error.getCause() instanceof AssertionError) {
+ Log.e(TAG, "Assertion failed on invoked method " + errorStackTrace);
+ } else if (error.getCause() instanceof InvocationTargetException) {
+ Log.e(TAG, "Invocation target failed " + errorStackTrace);
+ } else if (error.getCause() instanceof NoSuchMethodException) {
+ Log.e(TAG, "Test method not found " + errorStackTrace);
+ } else {
+ Log.e(TAG, "Test execution failed " + errorStackTrace);
+ }
+ }
+ }
+ }
+
+ private String getStackTrace(Exception error) {
+ StringWriter errorStackTrace = new StringWriter();
+ PrintWriter errorWriter = new PrintWriter(errorStackTrace);
+ error.getCause().printStackTrace(errorWriter);
+ return errorStackTrace.toString();
+ }
+}
diff --git a/sdksandbox/tests/testutils/testscenario/textexecutor/Android.bp b/sdksandbox/tests/testutils/testscenario/textexecutor/Android.bp
new file mode 100644
index 000000000..cc690844e
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/textexecutor/Android.bp
@@ -0,0 +1,28 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "CtsSdkSandboxTestExecutor",
+ srcs: [
+ "src/**/*.aidl",
+ ],
+ visibility: [
+ "//packages/modules/AdServices/sdksandbox/tests/testutils:__subpackages__",
+ ],
+}
+
diff --git a/sdksandbox/tests/testutils/testscenario/textexecutor/src/android/app/sdksandbox/testutils/testscenario/ISdkSandboxResultCallback.aidl b/sdksandbox/tests/testutils/testscenario/textexecutor/src/android/app/sdksandbox/testutils/testscenario/ISdkSandboxResultCallback.aidl
new file mode 100644
index 000000000..adcccfd31
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/textexecutor/src/android/app/sdksandbox/testutils/testscenario/ISdkSandboxResultCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 android.app.sdksandbox.testutils.testscenario;
+
+interface ISdkSandboxResultCallback {
+ oneway void onResult();
+
+ oneway void onError(in String errorMessage);
+}
diff --git a/sdksandbox/tests/testutils/testscenario/textexecutor/src/android/app/sdksandbox/testutils/testscenario/ISdkSandboxTestExecutor.aidl b/sdksandbox/tests/testutils/testscenario/textexecutor/src/android/app/sdksandbox/testutils/testscenario/ISdkSandboxTestExecutor.aidl
new file mode 100644
index 000000000..47f2eeca0
--- /dev/null
+++ b/sdksandbox/tests/testutils/testscenario/textexecutor/src/android/app/sdksandbox/testutils/testscenario/ISdkSandboxTestExecutor.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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 android.app.sdksandbox.testutils.testscenario;
+
+import java.util.List;
+import android.os.Bundle;
+
+import android.app.sdksandbox.testutils.testscenario.ISdkSandboxResultCallback;
+
+interface ISdkSandboxTestExecutor {
+ oneway void executeTest(String testName, in Bundle params, in ISdkSandboxResultCallback callback);
+}
diff --git a/sdksandbox/tests/unittest/Android.bp b/sdksandbox/tests/unittest/Android.bp
index a86f99581..cb4a10715 100644
--- a/sdksandbox/tests/unittest/Android.bp
+++ b/sdksandbox/tests/unittest/Android.bp
@@ -19,7 +19,7 @@ package {
java_defaults {
name: "SdkSandboxUnitTestsDefault",
min_sdk_version: "Tiramisu",
- target_sdk_version: "current",
+ target_sdk_version: "Tiramisu",
test_suites: [
"mts-adservices",
"general-tests"
@@ -91,7 +91,7 @@ android_test {
android_test {
name: "SdkSandboxFrameworkUnitTests",
srcs: [
- "src/android/app/sdksandbox/*.java",
+ "src/android/app/sdksandbox/**/*.java",
":framework-sdksandbox-sources",
],
defaults: ["SdkSandboxUnitTestsDefault"],
diff --git a/sdksandbox/tests/unittest/SdkSandboxFrameworkTest.xml b/sdksandbox/tests/unittest/SdkSandboxFrameworkTest.xml
index 2858635ef..56be0f7e2 100644
--- a/sdksandbox/tests/unittest/SdkSandboxFrameworkTest.xml
+++ b/sdksandbox/tests/unittest/SdkSandboxFrameworkTest.xml
@@ -17,6 +17,11 @@
<configuration description="Config for SdkSandboxFramework unit test cases">
<option name="test-tag" value="SdkSandboxFrameworkUnitTest" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="CodeProviderWithResources.apk"/>
diff --git a/sdksandbox/tests/unittest/SdkSandboxManagerServiceTest.xml b/sdksandbox/tests/unittest/SdkSandboxManagerServiceTest.xml
index 46f16a8e3..90957d030 100644
--- a/sdksandbox/tests/unittest/SdkSandboxManagerServiceTest.xml
+++ b/sdksandbox/tests/unittest/SdkSandboxManagerServiceTest.xml
@@ -17,6 +17,11 @@
<configuration description="Config for SdkSandboxManagerService unit test cases">
<option name="test-tag" value="SdkSandboxManagerServiceUnitTest" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="SampleCodeProviderApp.apk"/>
diff --git a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkContextUnitTest.java b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkContextUnitTest.java
index e08c504a4..fc835727e 100644
--- a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkContextUnitTest.java
+++ b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkContextUnitTest.java
@@ -67,6 +67,7 @@ public class SandboxedSdkContextUnitTest {
mSandboxedSdkContext =
new SandboxedSdkContext(
InstrumentationRegistry.getContext(),
+ getClass().getClassLoader(),
CLIENT_PACKAGE_NAME,
info,
SDK_NAME,
@@ -117,6 +118,11 @@ public class SandboxedSdkContextUnitTest {
}
@Test
+ public void testClassLoader() {
+ assertThat(mSandboxedSdkContext.getClassLoader()).isEqualTo(getClass().getClassLoader());
+ }
+
+ @Test
public void testGetDataDir_CredentialEncrypted() throws Exception {
assertThat(mSandboxedSdkContext.getDataDir().toString()).isEqualTo(SDK_CE_DATA_DIR);
@@ -153,6 +159,7 @@ public class SandboxedSdkContextUnitTest {
SandboxedSdkContext sandboxedSdkContext =
new SandboxedSdkContext(
testContext,
+ getClass().getClassLoader(),
CLIENT_PACKAGE_NAME,
info,
SDK_NAME,
@@ -179,6 +186,7 @@ public class SandboxedSdkContextUnitTest {
SandboxedSdkContext sandboxedSdkContext =
new SandboxedSdkContext(
testContext,
+ getClass().getClassLoader(),
CLIENT_PACKAGE_NAME,
info,
SDK_NAME,
@@ -204,6 +212,7 @@ public class SandboxedSdkContextUnitTest {
new SandboxedSdkContext(
InstrumentationRegistry.getContext()
.createCredentialProtectedStorageContext(),
+ getClass().getClassLoader(),
CLIENT_PACKAGE_NAME,
info,
SDK_NAME,
@@ -213,6 +222,7 @@ public class SandboxedSdkContextUnitTest {
new SandboxedSdkContext(
InstrumentationRegistry.getContext()
.createCredentialProtectedStorageContext(),
+ getClass().getClassLoader(),
CLIENT_PACKAGE_NAME,
info,
SDK_NAME,
diff --git a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkUnitTest.java b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkUnitTest.java
new file mode 100644
index 000000000..d6e1f719a
--- /dev/null
+++ b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SandboxedSdkUnitTest.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 android.app.sdksandbox;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VersionedPackage;
+import android.os.Binder;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+
+@RunWith(JUnit4.class)
+public class SandboxedSdkUnitTest {
+
+ public static final SharedLibraryInfo SHARED_LIBRARY_INFO =
+ new SharedLibraryInfo(
+ "testpath",
+ "test",
+ new ArrayList<>(),
+ "test",
+ 0L,
+ SharedLibraryInfo.TYPE_STATIC,
+ new VersionedPackage("test", 0L),
+ null,
+ null,
+ false /* isNative */);
+
+ @Test
+ public void testAttachSharedLibraryInfoFailsWhenCalledAgain() {
+ SandboxedSdk sandboxedSdk = new SandboxedSdk(new Binder());
+ sandboxedSdk.attachSharedLibraryInfo(SHARED_LIBRARY_INFO);
+ // Only one update should be received
+ assertThrows(
+ "SharedLibraryInfo already set",
+ IllegalStateException.class,
+ () -> sandboxedSdk.attachSharedLibraryInfo(SHARED_LIBRARY_INFO));
+ }
+
+ @Test
+ public void testGetInterface() {
+ Binder binder = new Binder();
+ SandboxedSdk sandboxedSdk = new SandboxedSdk(binder);
+ assertThat(sandboxedSdk.getInterface()).isSameInstanceAs(binder);
+ }
+
+ @Test
+ public void testGetSharedLibraryInfo() {
+ SandboxedSdk sandboxedSdk = new SandboxedSdk(new Binder());
+ sandboxedSdk.attachSharedLibraryInfo(SHARED_LIBRARY_INFO);
+ assertThat(sandboxedSdk.getSharedLibraryInfo()).isSameInstanceAs(SHARED_LIBRARY_INFO);
+ }
+
+ @Test
+ public void testGetSharedLibraryInfoNull() {
+ SandboxedSdk sandboxedSdk = new SandboxedSdk(new Binder());
+ assertThrows(IllegalStateException.class, () -> sandboxedSdk.getSharedLibraryInfo());
+ }
+
+ @Test
+ public void testDescribeContents() {
+ SandboxedSdk sandboxedSdk = new SandboxedSdk(new Binder());
+ int descriptor = sandboxedSdk.describeContents();
+ assertThat(descriptor).isEqualTo(0);
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ SandboxedSdk sandboxedSdk = new SandboxedSdk(new Binder());
+
+ Parcel parcel = Parcel.obtain();
+ sandboxedSdk.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+ SandboxedSdk fromParcel = SandboxedSdk.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(sandboxedSdk.getInterface()).isEqualTo(fromParcel.getInterface());
+ }
+
+ @Test
+ public void testWriteToParcelWithSharedLibraryInfo() {
+ SandboxedSdk sandboxedSdk = new SandboxedSdk(new Binder());
+ sandboxedSdk.attachSharedLibraryInfo(SHARED_LIBRARY_INFO);
+
+ Parcel parcel = Parcel.obtain();
+ sandboxedSdk.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+ SandboxedSdk fromParcel = SandboxedSdk.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(sandboxedSdk.getInterface()).isEqualTo(fromParcel.getInterface());
+ assertThat(sandboxedSdk.getSharedLibraryInfo().getName())
+ .isEqualTo(fromParcel.getSharedLibraryInfo().getName());
+ assertThat(sandboxedSdk.getSharedLibraryInfo().getLongVersion())
+ .isEqualTo(fromParcel.getSharedLibraryInfo().getLongVersion());
+ }
+}
diff --git a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxLocalSingletonUnitTest.java b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxLocalSingletonUnitTest.java
new file mode 100644
index 000000000..32ba47245
--- /dev/null
+++ b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxLocalSingletonUnitTest.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 android.app.sdksandbox;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+@RunWith(JUnit4.class)
+public class SdkSandboxLocalSingletonUnitTest {
+ private IBinder mBinder;
+
+ @Before
+ public void setup() throws RemoteException {
+ mBinder = Mockito.mock(IBinder.class);
+ Mockito.when(mBinder.getInterfaceDescriptor()).thenReturn(ISdkToServiceCallback.DESCRIPTOR);
+ }
+
+ @After
+ public void tearDown() {
+ SdkSandboxLocalSingleton.destroySingleton();
+ }
+
+ @Test
+ public void testInitInstanceWithIncorrectType() throws RemoteException {
+ IBinder binder2 = Mockito.mock(IBinder.class);
+ Mockito.when(binder2.getInterfaceDescriptor()).thenReturn("xyz");
+ assertThrows(
+ "IInterface not supported",
+ UnsupportedOperationException.class,
+ () -> SdkSandboxLocalSingleton.initInstance(binder2));
+ }
+
+ @Test
+ public void testInitInstance() {
+ SdkSandboxLocalSingleton.initInstance(mBinder);
+ SdkSandboxLocalSingleton singletonInstance = SdkSandboxLocalSingleton.getExistingInstance();
+ assertThat(singletonInstance).isNotNull();
+ assertThat(singletonInstance.getSdkToServiceCallback().asBinder()).isEqualTo(mBinder);
+ }
+
+ @Test
+ public void testInitInstanceWhenAlreadyExists() {
+ SdkSandboxLocalSingleton.initInstance(mBinder);
+
+ IBinder binder2 = Mockito.mock(IBinder.class);
+ SdkSandboxLocalSingleton.initInstance(binder2);
+
+ // Assert that the callback was not overridden and the new instance was not created.
+ SdkSandboxLocalSingleton singletonInstance = SdkSandboxLocalSingleton.getExistingInstance();
+ assertThat(singletonInstance).isNotNull();
+ assertThat(singletonInstance.getSdkToServiceCallback().asBinder()).isEqualTo(mBinder);
+ }
+
+ @Test
+ public void testGetInstanceReturnsSameObjectAlways() {
+ SdkSandboxLocalSingleton.initInstance(mBinder);
+
+ SdkSandboxLocalSingleton singletonInstance = SdkSandboxLocalSingleton.getExistingInstance();
+ assertThat(singletonInstance).isNotNull();
+ assertThat(SdkSandboxLocalSingleton.getExistingInstance())
+ .isSameInstanceAs(singletonInstance);
+ }
+
+ @Test
+ public void testGetInstanceWhenItDoesNotExist() {
+ assertThrows(
+ "SdkSandboxLocalSingleton not found",
+ IllegalStateException.class,
+ SdkSandboxLocalSingleton::getExistingInstance);
+ }
+}
diff --git a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxManagerUnitTest.java b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxManagerUnitTest.java
index d6256f5bf..4c7583150 100644
--- a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxManagerUnitTest.java
+++ b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SdkSandboxManagerUnitTest.java
@@ -32,7 +32,6 @@ import static org.junit.Assert.assertTrue;
import android.app.sdksandbox.testutils.FakeOutcomeReceiver;
import android.content.Context;
-import android.content.pm.SharedLibraryInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.OutcomeReceiver;
@@ -152,15 +151,14 @@ public class SdkSandboxManagerUnitTest {
}
@Test
- public void testGetLoadedSdkLibrariesInfo() throws Exception {
- List<SharedLibraryInfo> sharedLibraries = List.of();
- Mockito.when(mBinder.getLoadedSdkLibrariesInfo(Mockito.anyString(), Mockito.anyLong()))
- .thenReturn(sharedLibraries);
+ public void testGetSandboxedSdks() throws Exception {
+ List<SandboxedSdk> sandboxedSdks = List.of();
+ Mockito.when(mBinder.getSandboxedSdks(Mockito.anyString(), Mockito.anyLong()))
+ .thenReturn(sandboxedSdks);
- assertThat(mSdkSandboxManager.getLoadedSdkLibrariesInfo()).isEqualTo(sharedLibraries);
+ assertThat(mSdkSandboxManager.getSandboxedSdks()).isSameInstanceAs(sandboxedSdks);
Mockito.verify(mBinder)
- .getLoadedSdkLibrariesInfo(
- Mockito.eq(mContext.getPackageName()), Mockito.anyLong());
+ .getSandboxedSdks(Mockito.eq(mContext.getPackageName()), Mockito.anyLong());
}
@Test
diff --git a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java
index 65c034abb..9dbc849f6 100644
--- a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java
+++ b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java
@@ -37,6 +37,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collections;
@@ -89,6 +90,23 @@ public class SharedPreferencesSyncManagerUnitTest {
final SharedPreferencesSyncManager manager2 =
SharedPreferencesSyncManager.getInstance(mContext, mSdkSandboxManagerService);
assertThat(manager1).isSameInstanceAs(manager2);
+
+ Context mockContext = Mockito.mock(Context.class);
+ Mockito.when(mockContext.getPackageName()).thenReturn(mContext.getPackageName());
+ final SharedPreferencesSyncManager manager3 =
+ SharedPreferencesSyncManager.getInstance(mockContext, mSdkSandboxManagerService);
+ assertThat(manager1).isSameInstanceAs(manager3);
+ }
+
+ @Test
+ public void test_sharedPreferencesSyncManager_isSingletonPerPackage() throws Exception {
+ final SharedPreferencesSyncManager manager1 =
+ SharedPreferencesSyncManager.getInstance(mContext, mSdkSandboxManagerService);
+
+ Context mockContext = Mockito.mock(Context.class);
+ final SharedPreferencesSyncManager manager2 =
+ SharedPreferencesSyncManager.getInstance(mockContext, mSdkSandboxManagerService);
+ assertThat(manager1).isNotSameInstanceAs(manager2);
}
@Test
@@ -122,6 +140,18 @@ public class SharedPreferencesSyncManagerUnitTest {
}
@Test
+ public void test_removeKeys_updateSentForRemoval() throws Exception {
+ mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
+
+ // Remove key
+ mSyncManager.removeSharedPreferencesSyncKeys(Set.of(KEY_TO_UPDATE));
+
+ final SharedPreferencesUpdate update = mSdkSandboxManagerService.getLastUpdate();
+ assertThat(update.getData().keySet()).doesNotContain(Set.of(KEY_TO_UPDATE));
+ assertThat(update.getKeysInUpdate()).containsExactly(KEY_WITH_TYPE_TO_UPDATE);
+ }
+
+ @Test
public void test_bulkSync_syncSpecifiedKeys() throws Exception {
// Populate default shared preference with test data
populateDefaultSharedPreference(TEST_DATA);
diff --git a/sdksandbox/tests/unittest/src/android/app/sdksandbox/sdkprovider/SdkSandboxControllerUnitTest.java b/sdksandbox/tests/unittest/src/android/app/sdksandbox/sdkprovider/SdkSandboxControllerUnitTest.java
new file mode 100644
index 000000000..d07b8d465
--- /dev/null
+++ b/sdksandbox/tests/unittest/src/android/app/sdksandbox/sdkprovider/SdkSandboxControllerUnitTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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 android.app.sdksandbox.sdkprovider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.sdksandbox.ISdkToServiceCallback;
+import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SandboxedSdkContext;
+import android.app.sdksandbox.SdkSandboxLocalSingleton;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.os.Binder;
+import android.os.RemoteException;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class SdkSandboxControllerUnitTest {
+ private static final String RESOURCES_PACKAGE = "com.android.codeproviderresources_1";
+
+ private Context mContext;
+ private SandboxedSdkContext mSandboxedSdkContext;
+ private SdkSandboxLocalSingleton mSdkSandboxLocalSingleton;
+ private StaticMockitoSession mStaticMockSession;
+
+ @Before
+ public void setup() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mSandboxedSdkContext =
+ new SandboxedSdkContext(
+ mContext,
+ getClass().getClassLoader(),
+ /*clientPackageName=*/ "",
+ new ApplicationInfo(),
+ /*sdkName=*/ "",
+ /*sdkCeDataDir=*/ null,
+ /*sdkDeDataDir=*/ null);
+
+ mStaticMockSession =
+ ExtendedMockito.mockitoSession()
+ .mockStatic(SdkSandboxLocalSingleton.class)
+ .startMocking();
+ mSdkSandboxLocalSingleton = Mockito.mock(SdkSandboxLocalSingleton.class);
+ // Populate mSdkSandboxLocalSingleton
+ ExtendedMockito.doReturn(mSdkSandboxLocalSingleton)
+ .when(() -> SdkSandboxLocalSingleton.getExistingInstance());
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testCreateInstance() throws Exception {
+ final SdkSandboxController controller = new SdkSandboxController(mContext);
+ assertThat(controller).isNotNull();
+ }
+
+ @Test
+ public void testInitWithAnyContext() throws Exception {
+ SdkSandboxController controller = mContext.getSystemService(SdkSandboxController.class);
+ assertThat(controller).isNotNull();
+ // Does not fail on initialising with same context
+ controller.initialize(mContext);
+ assertThat(controller).isNotNull();
+ }
+
+ @Test
+ public void testGetSandboxedSdks() throws RemoteException {
+ SdkSandboxController controller = mContext.getSystemService(SdkSandboxController.class);
+ controller.initialize(mSandboxedSdkContext);
+
+ // Mock singleton methods
+ ISdkToServiceCallback serviceCallback = Mockito.mock(ISdkToServiceCallback.class);
+ ArrayList<SandboxedSdk> sandboxedSdksMock = new ArrayList<>();
+ sandboxedSdksMock.add(new SandboxedSdk(new Binder()));
+ Mockito.when(serviceCallback.getSandboxedSdks(Mockito.anyString()))
+ .thenReturn(sandboxedSdksMock);
+ Mockito.when(mSdkSandboxLocalSingleton.getSdkToServiceCallback())
+ .thenReturn(serviceCallback);
+
+ List<SandboxedSdk> sandboxedSdks = controller.getSandboxedSdks();
+ assertThat(sandboxedSdks).isEqualTo(sandboxedSdksMock);
+ }
+
+ @Test
+ public void testGetSandboxedSdksFailsWithIncorrectContext() {
+ SdkSandboxController controller = mContext.getSystemService(SdkSandboxController.class);
+
+ assertThrows(
+ "Only available from the context obtained by calling android.app.sdksandbox"
+ + ".SandboxedSdkProvider#getContext()",
+ UnsupportedOperationException.class,
+ () -> controller.getSandboxedSdks());
+ }
+
+ @Test
+ public void testGetClientSharedPreferences_onlyFromSandboxedContext() throws Exception {
+ final SdkSandboxController controller = new SdkSandboxController(mContext);
+ assertThrows(
+ "Only available from SandboxedSdkContext",
+ UnsupportedOperationException.class,
+ () -> controller.getClientSharedPreferences());
+ }
+
+ @Test
+ public void testGetClientSharedPreferences() throws Exception {
+ final SdkSandboxController controller = new SdkSandboxController(mContext);
+ controller.initialize(mSandboxedSdkContext);
+
+ final SharedPreferences sp = controller.getClientSharedPreferences();
+ // Assert same instance as a name SharedPreference on sandboxed context
+ final SharedPreferences spFromSandboxedContext =
+ mSandboxedSdkContext.getSharedPreferences(
+ SdkSandboxController.CLIENT_SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ assertThat(sp).isSameInstanceAs(spFromSandboxedContext);
+ // Assert same instance as a name SharedPreference on original context
+ final SharedPreferences spFromOriginalContext =
+ mContext.getSharedPreferences(
+ SdkSandboxController.CLIENT_SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ assertThat(sp).isSameInstanceAs(spFromOriginalContext);
+ }
+}
diff --git a/sdksandbox/tests/unittest/src/com/android/sdksandbox/SdkSandboxTest.java b/sdksandbox/tests/unittest/src/com/android/sdksandbox/SdkSandboxTest.java
index 0e7701b5e..815596ec6 100644
--- a/sdksandbox/tests/unittest/src/com/android/sdksandbox/SdkSandboxTest.java
+++ b/sdksandbox/tests/unittest/src/com/android/sdksandbox/SdkSandboxTest.java
@@ -18,8 +18,11 @@ package com.android.sdksandbox;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
import android.app.sdksandbox.LoadSdkException;
import android.app.sdksandbox.SandboxedSdk;
+import android.app.sdksandbox.SdkSandboxLocalSingleton;
import android.app.sdksandbox.SharedPreferencesKey;
import android.app.sdksandbox.SharedPreferencesUpdate;
import android.app.sdksandbox.testutils.StubSdkToServiceLink;
@@ -30,10 +33,8 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
-import android.preference.PreferenceManager;
import android.view.SurfaceControlViewHost;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -131,7 +132,27 @@ public class SdkSandboxTest {
@After
public void teardown() throws Exception {
- getClientSharedPreference().edit().clear().commit();
+ mService.getClientSharedPreferences().edit().clear().commit();
+ }
+
+ @Test
+ public void testSandboxInitialization_initializesSdkSandboxLocalSingleTon() throws Exception {
+ assertThrows(
+ IllegalStateException.class, () -> SdkSandboxLocalSingleton.getExistingInstance());
+
+ mService.initialize(new StubSdkToServiceLink());
+
+ assertThat(SdkSandboxLocalSingleton.getExistingInstance()).isNotNull();
+ }
+
+ @Test
+ public void testSandboxInitialization_clearsSyncedData() throws Exception {
+ // First write some data
+ mService.syncDataFromClient(TEST_UPDATE);
+
+ mService.initialize(new StubSdkToServiceLink());
+
+ assertThat(mService.getClientSharedPreferences().getAll()).isEmpty();
}
@Test
@@ -140,7 +161,6 @@ public class SdkSandboxTest {
RemoteCode mRemoteCode = new RemoteCode(latch);
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- new Binder(),
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -148,8 +168,7 @@ public class SdkSandboxTest {
null,
new Bundle(),
mRemoteCode,
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
assertThat(mRemoteCode.mSuccessful).isTrue();
}
@@ -160,10 +179,8 @@ public class SdkSandboxTest {
RemoteCode mRemoteCode1 = new RemoteCode(latch1);
CountDownLatch latch2 = new CountDownLatch(1);
RemoteCode mRemoteCode2 = new RemoteCode(latch2);
- IBinder duplicateToken = new Binder();
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- duplicateToken,
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -171,13 +188,11 @@ public class SdkSandboxTest {
null,
new Bundle(),
mRemoteCode1,
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
assertThat(latch1.await(1, TimeUnit.MINUTES)).isTrue();
assertThat(mRemoteCode1.mSuccessful).isTrue();
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- duplicateToken,
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -185,8 +200,7 @@ public class SdkSandboxTest {
null,
new Bundle(),
mRemoteCode2,
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
assertThat(latch2.await(1, TimeUnit.MINUTES)).isTrue();
assertThat(mRemoteCode2.mSuccessful).isFalse();
assertThat(mRemoteCode2.mErrorCode)
@@ -194,49 +208,11 @@ public class SdkSandboxTest {
}
@Test
- public void testLoadingMultiple() throws Exception {
- CountDownLatch latch1 = new CountDownLatch(1);
- RemoteCode mRemoteCode1 = new RemoteCode(latch1);
- CountDownLatch latch2 = new CountDownLatch(1);
- RemoteCode mRemoteCode2 = new RemoteCode(latch2);
- StubSdkToServiceLink sdkToServiceLink = new StubSdkToServiceLink();
- mService.loadSdk(
- CLIENT_PACKAGE_NAME,
- new Binder(),
- mApplicationInfo,
- SDK_NAME,
- SDK_PROVIDER_CLASS,
- null,
- null,
- new Bundle(),
- mRemoteCode1,
- SANDBOX_LATENCY_INFO,
- sdkToServiceLink);
- mService.loadSdk(
- CLIENT_PACKAGE_NAME,
- new Binder(),
- mApplicationInfo,
- SDK_NAME,
- SDK_PROVIDER_CLASS,
- null,
- null,
- new Bundle(),
- mRemoteCode2,
- SANDBOX_LATENCY_INFO,
- sdkToServiceLink);
- assertThat(latch1.await(1, TimeUnit.MINUTES)).isTrue();
- assertThat(mRemoteCode1.mSuccessful).isTrue();
- assertThat(latch2.await(1, TimeUnit.MINUTES)).isTrue();
- assertThat(mRemoteCode2.mSuccessful).isTrue();
- }
-
- @Test
public void testRequestSurfacePackage() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
RemoteCode mRemoteCode = new RemoteCode(latch);
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- new Binder(),
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -244,8 +220,7 @@ public class SdkSandboxTest {
null,
new Bundle(),
mRemoteCode,
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
CountDownLatch surfaceLatch = new CountDownLatch(1);
@@ -271,7 +246,6 @@ public class SdkSandboxTest {
RemoteCode mRemoteCode = new RemoteCode(latch);
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- new Binder(),
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -279,8 +253,7 @@ public class SdkSandboxTest {
null,
new Bundle(),
mRemoteCode,
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
CountDownLatch surfaceLatch = new CountDownLatch(1);
@@ -305,10 +278,6 @@ public class SdkSandboxTest {
@Test
public void testDump_NoSdk() {
- Mockito.doNothing()
- .when(mContext)
- .enforceCallingPermission(
- Mockito.eq("android.permission.DUMP"), Mockito.anyString());
final StringWriter stringWriter = new StringWriter();
mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
assertThat(stringWriter.toString()).contains("mHeldSdk is empty");
@@ -316,14 +285,8 @@ public class SdkSandboxTest {
@Test
public void testDump_WithSdk() {
- Mockito.doNothing()
- .when(mContext)
- .enforceCallingPermission(
- Mockito.eq("android.permission.DUMP"), Mockito.anyString());
-
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- new Binder(),
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -331,8 +294,7 @@ public class SdkSandboxTest {
null,
new Bundle(),
new RemoteCode(new CountDownLatch(1)),
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
final StringWriter stringWriter = new StringWriter();
mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
@@ -363,17 +325,12 @@ public class SdkSandboxTest {
assertThat(callback.isDisabled()).isFalse();
}
- @Test(expected = SecurityException.class)
- public void testDump_WithoutPermission() {
- mService.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]);
- }
-
@Test
public void testSyncDataFromClient_StoresInClientSharedPreference() throws Exception {
mService.syncDataFromClient(TEST_UPDATE);
// Verify that ClientSharedPreference contains the synced data
- SharedPreferences pref = getClientSharedPreference();
+ SharedPreferences pref = mService.getClientSharedPreferences();
assertThat(pref.getAll().keySet()).containsExactlyElementsIn(TEST_DATA.keySet());
assertThat(pref.getAll().values()).containsExactlyElementsIn(TEST_DATA.values());
}
@@ -402,7 +359,7 @@ public class SdkSandboxTest {
mService.syncDataFromClient(update);
// Verify that ClientSharedPreference contains the synced data
- SharedPreferences pref = getClientSharedPreference();
+ SharedPreferences pref = mService.getClientSharedPreferences();
assertThat(pref.getAll().keySet()).containsExactlyElementsIn(bundle.keySet());
assertThat(pref.getString("string", "")).isEqualTo("value");
assertThat(pref.getBoolean("boolean", false)).isEqualTo(true);
@@ -425,7 +382,7 @@ public class SdkSandboxTest {
mService.syncDataFromClient(newUpdate);
// Verify that ClientSharedPreference contains the synced data
- SharedPreferences pref = getClientSharedPreference();
+ SharedPreferences pref = mService.getClientSharedPreferences();
assertThat(pref.getAll().keySet()).containsExactlyElementsIn(TEST_DATA.keySet());
assertThat(pref.getString(KEY_TO_UPDATE, "")).isEqualTo("update");
}
@@ -441,7 +398,7 @@ public class SdkSandboxTest {
mService.syncDataFromClient(newUpdate);
// Verify that ClientSharedPreference contains the synced data
- SharedPreferences pref = getClientSharedPreference();
+ SharedPreferences pref = mService.getClientSharedPreferences();
assertThat(pref.getAll().keySet()).doesNotContain(KEY_TO_UPDATE);
}
@@ -460,7 +417,6 @@ public class SdkSandboxTest {
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- new Binder(),
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -468,8 +424,7 @@ public class SdkSandboxTest {
null,
new Bundle(),
mRemoteCode,
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
assertThat(mRemoteCode.mSandboxLatencyInfo.getLatencySystemServerToSandbox())
.isEqualTo(
@@ -505,12 +460,9 @@ public class SdkSandboxTest {
TIME_SDK_CALL_COMPLETED,
TIME_SANDBOX_CALLED_SYSTEM_SERVER);
- final IBinder sdkToken = new Binder();
-
final CountDownLatch latch = new CountDownLatch(1);
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- sdkToken,
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -518,12 +470,11 @@ public class SdkSandboxTest {
null,
new Bundle(),
new RemoteCode(latch),
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
final UnloadSdkCallbackImpl unloadSdkCallback = new UnloadSdkCallbackImpl();
- mService.unloadSdk(sdkToken, unloadSdkCallback, SANDBOX_LATENCY_INFO);
+ mService.unloadSdk(SDK_NAME, unloadSdkCallback, SANDBOX_LATENCY_INFO);
final SandboxLatencyInfo sandboxLatencyInfo = unloadSdkCallback.getSandboxLatencyInfo();
@@ -561,7 +512,6 @@ public class SdkSandboxTest {
mService.loadSdk(
CLIENT_PACKAGE_NAME,
- new Binder(),
mApplicationInfo,
SDK_NAME,
SDK_PROVIDER_CLASS,
@@ -569,8 +519,7 @@ public class SdkSandboxTest {
null,
new Bundle(),
mRemoteCode,
- SANDBOX_LATENCY_INFO,
- new StubSdkToServiceLink());
+ SANDBOX_LATENCY_INFO);
assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
CountDownLatch surfaceLatch = new CountDownLatch(1);
@@ -663,10 +612,6 @@ public class SdkSandboxTest {
return bundle;
}
- private SharedPreferences getClientSharedPreference() {
- return PreferenceManager.getDefaultSharedPreferences(mContext);
- }
-
private static class RequestSurfacePackageCallbackImpl
extends IRequestSurfacePackageFromSdkCallback.Stub {
private CountDownLatch mLatch;
diff --git a/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java b/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
index 231656395..c8f5db3ea 100644
--- a/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
+++ b/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
@@ -20,6 +20,7 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREG
import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__ADD_SDK_SANDBOX_LIFECYCLE_CALLBACK;
import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__REMOVE_SDK_SANDBOX_LIFECYCLE_CALLBACK;
+import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE;
import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER;
import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP;
import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX;
@@ -108,6 +109,7 @@ public class SdkSandboxManagerServiceUnitTest {
private MockitoSession mStaticMockSession = null;
private Context mSpyContext;
private SdkSandboxManagerService.Injector mInjector;
+ private int mClientAppUid;
private static final String CLIENT_PACKAGE_NAME = "com.android.client";
private static final String SDK_NAME = "com.android.codeprovider";
@@ -173,6 +175,8 @@ public class SdkSandboxManagerServiceUnitTest {
mInjector = Mockito.spy(new InjectorForTest());
mService = new SdkSandboxManagerService(mSpyContext, mProvider, mInjector);
+
+ mClientAppUid = Process.myUid();
}
@After
@@ -336,7 +340,6 @@ public class SdkSandboxManagerServiceUnitTest {
android.Manifest.permission.ACCESS_NETWORK_STATE);
}
-
@Test
public void testLoadSdk_successOnFirstLoad_errorOnLoadAgain() throws Exception {
disableNetworkPermissionChecks();
@@ -366,33 +369,51 @@ public class SdkSandboxManagerServiceUnitTest {
}
@Test
- public void testLoadSdk_errorOnFirstLoad_canBeLoadedAgain() throws Exception {
+ public void testLoadSdk_firstLoadPending_errorOnLoadAgainRequest() throws Exception {
disableNetworkPermissionChecks();
disableForegroundCheck();
- // Load code, but make it fail
+ // Request to load the SDK, but do not complete loading it
{
FakeLoadSdkCallbackBinder callback = new FakeLoadSdkCallbackBinder();
mService.loadSdk(
TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER, new Bundle(), callback);
- // Assume SupplementalProcess load fails
- mSdkSandboxService.sendLoadCodeError();
+ }
+
+ // Requesting to load the SDK while the first load is still pending should throw an error
+ {
+ FakeLoadSdkCallbackBinder callback = new FakeLoadSdkCallbackBinder();
+ mService.loadSdk(
+ TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER, new Bundle(), callback);
+ // Verify loading failed
assertThat(callback.isLoadSdkSuccessful()).isFalse();
+ assertThat(callback.getLoadSdkErrorCode())
+ .isEqualTo(SdkSandboxManager.LOAD_SDK_ALREADY_LOADED);
+ assertThat(callback.getLoadSdkErrorMsg()).contains("is currently being loaded");
}
+ }
- // Caller should be able to retry loading the code
+ @Test
+ public void testLoadSdk_errorOnFirstLoad_canBeLoadedAgain() throws Exception {
+ disableNetworkPermissionChecks();
+ disableForegroundCheck();
+
+ // Load code, but make it fail
{
FakeLoadSdkCallbackBinder callback = new FakeLoadSdkCallbackBinder();
mService.loadSdk(
TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER, new Bundle(), callback);
- // Assume SupplementalProcess loads successfully
- mSdkSandboxService.sendLoadCodeSuccessful();
- assertThat(callback.isLoadSdkSuccessful()).isTrue();
+ // Assume sdk load fails
+ mSdkSandboxService.sendLoadCodeError();
+ assertThat(callback.isLoadSdkSuccessful()).isFalse();
}
+
+ // Caller should be able to retry loading the code
+ loadSdk(SDK_NAME);
}
@Test
- public void testLoadSdk_SandboxDiesInBetween() throws Exception {
+ public void testLoadSdk_sandboxDiesInBetween() throws Exception {
disableNetworkPermissionChecks();
disableForegroundCheck();
@@ -415,7 +436,24 @@ public class SdkSandboxManagerServiceUnitTest {
}
@Test
- public void testRequestSurfacePackageSdkNotLoaded() throws Exception {
+ public void testLoadSdk_sandboxIsInitialized() throws Exception {
+ loadSdk(SDK_NAME);
+
+ // Verify that sandbox was initialized
+ assertThat(mSdkSandboxService.getInitializationCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testLoadSdk_sandboxIsInitialized_onlyOnce() throws Exception {
+ loadSdk(SDK_NAME);
+ loadSdk(SDK_PROVIDER_RESOURCES_SDK_NAME);
+
+ // Verify that sandbox was initialized
+ assertThat(mSdkSandboxService.getInitializationCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testRequestSurfacePackageSdkNotLoaded_SandboxExists() throws Exception {
disableNetworkPermissionChecks();
disableForegroundCheck();
@@ -428,21 +466,21 @@ public class SdkSandboxManagerServiceUnitTest {
// Trying to request package with not exist SDK packageName
String sdkName = "invalid";
- IllegalArgumentException thrown =
- assertThrows(
- IllegalArgumentException.class,
- () ->
- mService.requestSurfacePackage(
- TEST_PACKAGE,
- sdkName,
- new Binder(),
- 0,
- 500,
- 500,
- TIME_APP_CALLED_SYSTEM_SERVER,
- new Bundle(),
- new FakeRequestSurfacePackageCallbackBinder()));
- assertThat(thrown).hasMessageThat().contains("Sdk " + sdkName + " is not loaded");
+ FakeRequestSurfacePackageCallbackBinder surfacePackageCallback =
+ new FakeRequestSurfacePackageCallbackBinder();
+ mService.requestSurfacePackage(
+ TEST_PACKAGE,
+ sdkName,
+ new Binder(),
+ 0,
+ 500,
+ 500,
+ TIME_APP_CALLED_SYSTEM_SERVER,
+ new Bundle(),
+ surfacePackageCallback);
+ assertThat(surfacePackageCallback.isRequestSurfacePackageSuccessful()).isFalse();
+ assertThat(surfacePackageCallback.getSurfacePackageErrorCode())
+ .isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED);
}
@Test
@@ -465,13 +503,13 @@ public class SdkSandboxManagerServiceUnitTest {
callback);
assertThat(callback.isRequestSurfacePackageSuccessful()).isFalse();
assertThat(callback.getSurfacePackageErrorCode())
- .isEqualTo(SdkSandboxManager.SDK_SANDBOX_PROCESS_NOT_AVAILABLE);
+ .isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED);
}
@Test
public void testRequestSurfacePackage() throws Exception {
// 1. We first need to collect a proper sdkToken by calling loadCode
- loadSdk();
+ loadSdk(SDK_NAME);
// 2. Call request package with the retrieved sdkToken
FakeRequestSurfacePackageCallbackBinder surfacePackageCallback =
@@ -528,12 +566,12 @@ public class SdkSandboxManagerServiceUnitTest {
requestSurfacePackageCallback);
assertThat(requestSurfacePackageCallback.isRequestSurfacePackageSuccessful()).isFalse();
assertThat(requestSurfacePackageCallback.getSurfacePackageErrorCode())
- .isEqualTo(SdkSandboxManager.SDK_SANDBOX_PROCESS_NOT_AVAILABLE);
+ .isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED);
}
@Test
public void testSurfacePackageError() throws Exception {
- loadSdk();
+ loadSdk(SDK_NAME);
// Assume SurfacePackage encounters an error.
FakeRequestSurfacePackageCallbackBinder surfacePackageCallback =
@@ -583,7 +621,7 @@ public class SdkSandboxManagerServiceUnitTest {
assertThat(surfacePackageCallback.isRequestSurfacePackageSuccessful()).isFalse();
assertThat(surfacePackageCallback.getSurfacePackageErrorCode())
- .isEqualTo(SdkSandboxManager.SDK_SANDBOX_PROCESS_NOT_AVAILABLE);
+ .isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED);
}
@Test
@@ -595,7 +633,7 @@ public class SdkSandboxManagerServiceUnitTest {
TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER, lifecycleCallback);
// Load SDK and start the sandbox
- loadSdk();
+ loadSdk(SDK_NAME);
// Kill the sandbox
ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
@@ -612,7 +650,7 @@ public class SdkSandboxManagerServiceUnitTest {
@Test
public void testAddSdkSandboxProcessDeathCallback_AfterStartingSandbox() throws Exception {
// Load SDK and start the sandbox
- loadSdk();
+ loadSdk(SDK_NAME);
// Register for sandbox death event
FakeSdkSandboxProcessDeathCallbackBinder lifecycleCallback =
@@ -635,7 +673,7 @@ public class SdkSandboxManagerServiceUnitTest {
@Test
public void testMultipleAddSdkSandboxProcessDeathCallbacks() throws Exception {
// Load SDK and start the sandbox
- loadSdk();
+ loadSdk(SDK_NAME);
// Register for sandbox death event
FakeSdkSandboxProcessDeathCallbackBinder lifecycleCallback1 =
@@ -665,7 +703,7 @@ public class SdkSandboxManagerServiceUnitTest {
@Test
public void testRemoveSdkSandboxProcessDeathCallback() throws Exception {
// Load SDK and start the sandbox
- loadSdk();
+ loadSdk(SDK_NAME);
// Register for sandbox death event
FakeSdkSandboxProcessDeathCallbackBinder lifecycleCallback1 =
@@ -734,7 +772,14 @@ public class SdkSandboxManagerServiceUnitTest {
PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES);
assertThat(info).isNotNull();
SandboxedSdkContext sandboxedSdkContext =
- new SandboxedSdkContext(context, CLIENT_PACKAGE_NAME, info, SDK_NAME, null, null);
+ new SandboxedSdkContext(
+ context,
+ getClass().getClassLoader(),
+ CLIENT_PACKAGE_NAME,
+ info,
+ SDK_NAME,
+ null,
+ null);
Resources resources = sandboxedSdkContext.getResources();
int integerId = resources.getIdentifier("test_integer", "integer",
@@ -790,7 +835,7 @@ public class SdkSandboxManagerServiceUnitTest {
disableKillUid();
// First load SDK.
- loadSdk();
+ loadSdk(SDK_NAME);
final CallingInfo callingInfo = new CallingInfo(Process.myUid(), TEST_PACKAGE);
@@ -811,7 +856,7 @@ public class SdkSandboxManagerServiceUnitTest {
disableKillUid();
// First load SDK.
- loadSdk();
+ loadSdk(SDK_NAME);
final CallingInfo callingInfo = new CallingInfo(Process.myUid(), TEST_PACKAGE);
@@ -879,20 +924,20 @@ public class SdkSandboxManagerServiceUnitTest {
}
@Test
- public void testGetLoadedSdkLibrariesInfo_afterLoadSdkSuccess() throws Exception {
- loadSdk();
- assertThat(mService.getLoadedSdkLibrariesInfo(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER))
+ public void testGetSandboxedSdks_afterLoadSdkSuccess() throws Exception {
+ loadSdk(SDK_NAME);
+ assertThat(mService.getSandboxedSdks(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER))
.hasSize(1);
assertThat(
- mService.getLoadedSdkLibrariesInfo(
- TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER)
+ mService.getSandboxedSdks(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER)
.get(0)
+ .getSharedLibraryInfo()
.getName())
.isEqualTo(SDK_NAME);
}
@Test
- public void testGetLoadedSdkLibrariesInfo_errorLoadingSdk() throws Exception {
+ public void testGetSandboxedSdks_errorLoadingSdk() throws Exception {
disableNetworkPermissionChecks();
disableForegroundCheck();
@@ -906,7 +951,7 @@ public class SdkSandboxManagerServiceUnitTest {
assertThat(callback.isLoadSdkSuccessful()).isFalse();
assertThat(callback.getLoadSdkErrorCode())
.isEqualTo(SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR);
- assertThat(mService.getLoadedSdkLibrariesInfo(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER))
+ assertThat(mService.getSandboxedSdks(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER))
.isEmpty();
}
@@ -928,28 +973,17 @@ public class SdkSandboxManagerServiceUnitTest {
@Test
public void testUnloadSdkThatIsNotLoaded() throws Exception {
// Load SDK to bring up a sandbox
- loadSdk();
- // Trying to load an SDK that is not loaded should fail.
- assertThrows(
- IllegalArgumentException.class,
- () -> mService.unloadSdk(
- TEST_PACKAGE, SDK_PROVIDER_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER));
+ loadSdk(SDK_NAME);
+ // Trying to unload an SDK that is not loaded should do nothing - it's a no-op.
+ mService.unloadSdk(TEST_PACKAGE, SDK_PROVIDER_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER);
}
@Test
public void testUnloadSdkThatIsLoaded() throws Exception {
disableKillUid();
- loadSdk();
+ loadSdk(SDK_NAME);
- FakeLoadSdkCallbackBinder callback2 = new FakeLoadSdkCallbackBinder();
- mService.loadSdk(
- TEST_PACKAGE,
- SDK_PROVIDER_RESOURCES_SDK_NAME,
- TIME_APP_CALLED_SYSTEM_SERVER,
- new Bundle(),
- callback2);
- mSdkSandboxService.sendLoadCodeSuccessful();
- assertThat(callback2.isLoadSdkSuccessful()).isTrue();
+ loadSdk(SDK_PROVIDER_RESOURCES_SDK_NAME);
final CallingInfo callingInfo = new CallingInfo(Process.myUid(), TEST_PACKAGE);
mService.unloadSdk(TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER);
@@ -967,10 +1001,32 @@ public class SdkSandboxManagerServiceUnitTest {
}
@Test
+ public void testUnloadSdkThatIsBeingLoaded() throws Exception {
+ // Ask to load SDK, but don't finish loading it
+ disableKillUid();
+ disableNetworkPermissionChecks();
+ disableForegroundCheck();
+
+ FakeLoadSdkCallbackBinder callback = new FakeLoadSdkCallbackBinder();
+ mService.loadSdk(
+ TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER, new Bundle(), callback);
+
+ // Trying to unload an SDK that is being loaded should fail
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mService.unloadSdk(TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER));
+
+ // After loading the SDK, unloading should not fail
+ mSdkSandboxService.sendLoadCodeSuccessful();
+ assertThat(callback.isLoadSdkSuccessful()).isTrue();
+ mService.unloadSdk(TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER);
+ }
+
+ @Test
public void testUnloadSdkAfterKillingSandboxDoesNotThrowException() throws Exception {
disableKillUid();
- loadSdk();
+ loadSdk(SDK_NAME);
// Kill the sandbox
ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
@@ -1030,7 +1086,7 @@ public class SdkSandboxManagerServiceUnitTest {
callback.resetLatch();
// Now loadSdk so that sandbox is created
- loadSdk();
+ loadSdk(SDK_NAME);
// Verify that onSandboxStart was called
assertThat(callback.hasSandboxStarted()).isTrue();
@@ -1059,7 +1115,7 @@ public class SdkSandboxManagerServiceUnitTest {
@Test
public void testStopSdkSandbox() throws Exception {
disableKillUid();
- loadSdk();
+ loadSdk(SDK_NAME);
Mockito.doNothing()
.when(mSpyContext)
@@ -1117,7 +1173,7 @@ public class SdkSandboxManagerServiceUnitTest {
@Test
public void testLatencyMetrics_IpcFromAppToSystemServer_LoadSdk() throws Exception {
- loadSdk();
+ loadSdk(SDK_NAME);
ExtendedMockito.verify(
() ->
@@ -1128,14 +1184,14 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP
- TIME_APP_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ mClientAppUid));
}
@Test
public void testLatencyMetrics_IpcFromAppToSystemServer_RequestSurfacePackage()
throws Exception {
- loadSdk();
+ loadSdk(SDK_NAME);
// 2. Call request package
FakeRequestSurfacePackageCallbackBinder surfacePackageCallback =
@@ -1154,37 +1210,36 @@ public class SdkSandboxManagerServiceUnitTest {
() ->
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
+ SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
(int)
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP
- TIME_APP_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ mClientAppUid));
}
@Test
- public void testLatencyMetrics_IpcFromAppToSystemServer_GetLoadedSdkLibrariesInfo() {
- mService.getLoadedSdkLibrariesInfo(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER);
+ public void testLatencyMetrics_IpcFromAppToSystemServer_GetSandboxedSdks() {
+ mService.getSandboxedSdks(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER);
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__GET_LOADED_SDK_LIBRARIES_INFO,
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__GET_SANDBOXED_SDKS,
(int)
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP
- TIME_APP_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ mClientAppUid));
}
@Test
public void testLatencyMetrics_IpcFromAppToSystemServer_UnloadSdk() throws Exception {
disableKillUid();
- loadSdk();
+
+ loadSdk(SDK_NAME);
mService.unloadSdk(TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER);
ExtendedMockito.verify(
() ->
@@ -1195,8 +1250,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP
- TIME_APP_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ mClientAppUid));
}
@Test
@@ -1217,8 +1272,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP
- TIME_APP_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ mClientAppUid));
}
// TODO(b/242149555): Update tests to use fake for getting time series.
@@ -1230,7 +1285,7 @@ public class SdkSandboxManagerServiceUnitTest {
START_TIME_TO_LOAD_SANDBOX,
END_TIME_TO_LOAD_SANDBOX,
TIME_SYSTEM_SERVER_CALLS_SANDBOX);
- loadSdk();
+ loadSdk(SDK_NAME);
final int timeToLoadSdk = (int) (END_TIME_TO_LOAD_SANDBOX - START_TIME_TO_LOAD_SANDBOX);
@@ -1241,7 +1296,8 @@ public class SdkSandboxManagerServiceUnitTest {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
timeToLoadSdk,
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX,
+ mClientAppUid));
int timeSystemServerAppToSandbox =
(int)
@@ -1257,12 +1313,13 @@ public class SdkSandboxManagerServiceUnitTest {
timeSystemServerAppToSandbox,
/*success=*/ true,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
}
@Test
public void testLatencyMetrics_SystemServerSandboxToApp_LoadSdk() throws Exception {
- loadSdk();
+ loadSdk(SDK_NAME);
ExtendedMockito.verify(
() ->
@@ -1271,7 +1328,8 @@ public class SdkSandboxManagerServiceUnitTest {
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX)));
+ Mockito.eq(SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX),
+ Mockito.eq(mClientAppUid)));
ExtendedMockito.verify(
() ->
@@ -1280,7 +1338,8 @@ public class SdkSandboxManagerServiceUnitTest {
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX)));
+ Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX),
+ Mockito.eq(mClientAppUid)));
ExtendedMockito.verify(
() ->
@@ -1289,7 +1348,8 @@ public class SdkSandboxManagerServiceUnitTest {
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER)));
+ Mockito.eq(SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER),
+ Mockito.eq(mClientAppUid)));
}
@Test
@@ -1320,7 +1380,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SANDBOX_RECEIVED_CALL_FROM_SYSTEM_SERVER
- TIME_SYSTEM_SERVER_CALLED_SANDBOX),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX));
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX,
+ mClientAppUid));
int sandboxLatency =
(int)
@@ -1335,7 +1396,8 @@ public class SdkSandboxManagerServiceUnitTest {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
sandboxLatency,
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1344,7 +1406,8 @@ public class SdkSandboxManagerServiceUnitTest {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
(int) (TIME_SDK_CALL_COMPLETED - TIME_SANDBOX_CALLED_SDK),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1355,7 +1418,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_SANDBOX
- TIME_SANDBOX_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER));
+ SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER,
+ mClientAppUid));
}
@Test
@@ -1363,6 +1427,7 @@ public class SdkSandboxManagerServiceUnitTest {
throws Exception {
disableNetworkPermissionChecks();
disableForegroundCheck();
+
Mockito.when(mInjector.getCurrentTime())
.thenReturn(
TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP,
@@ -1400,7 +1465,8 @@ public class SdkSandboxManagerServiceUnitTest {
timeSystemServerAppToSandbox,
/*success=*/ false,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
}
@Test
@@ -1408,6 +1474,7 @@ public class SdkSandboxManagerServiceUnitTest {
throws RemoteException {
disableNetworkPermissionChecks();
disableForegroundCheck();
+
Mockito.when(mInjector.getCurrentTime())
.thenReturn(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP, TIME_FAILURE_HANDLED);
@@ -1425,7 +1492,8 @@ public class SdkSandboxManagerServiceUnitTest {
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ false,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1434,6 +1502,7 @@ public class SdkSandboxManagerServiceUnitTest {
Mockito.anyInt(),
Mockito.anyInt(),
Mockito.anyBoolean(),
+ Mockito.anyInt(),
Mockito.anyInt()),
Mockito.times(2));
}
@@ -1473,7 +1542,8 @@ public class SdkSandboxManagerServiceUnitTest {
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ false,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1482,6 +1552,7 @@ public class SdkSandboxManagerServiceUnitTest {
Mockito.anyInt(),
Mockito.anyInt(),
Mockito.anyBoolean(),
+ Mockito.anyInt(),
Mockito.anyInt()),
Mockito.times(2));
}
@@ -1510,7 +1581,8 @@ public class SdkSandboxManagerServiceUnitTest {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK,
(int) (TIME_FAILURE_HANDLED - START_TIME_TO_LOAD_SANDBOX),
/*success=*/ false,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX,
+ mClientAppUid));
}
@Test
@@ -1526,7 +1598,7 @@ public class SdkSandboxManagerServiceUnitTest {
TIME_SYSTEM_SERVER_CALLED_APP,
TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP,
TIME_SYSTEM_SERVER_CALLED_SANDBOX);
- loadSdk();
+ loadSdk(SDK_NAME);
// 2. Call request package
FakeRequestSurfacePackageCallbackBinder surfacePackageCallback =
@@ -1548,47 +1620,44 @@ public class SdkSandboxManagerServiceUnitTest {
() ->
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
+ SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
(int)
(TIME_SYSTEM_SERVER_CALLED_SANDBOX
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ true,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED),
- Mockito.eq(
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
+ Mockito.eq(SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX)));
+ Mockito.eq(SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX),
+ Mockito.eq(mClientAppUid)));
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED),
- Mockito.eq(
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
+ Mockito.eq(SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX)));
+ Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX),
+ Mockito.eq(mClientAppUid)));
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED),
- Mockito.eq(
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
+ Mockito.eq(SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER)));
+ Mockito.eq(SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER),
+ Mockito.eq(mClientAppUid)));
}
@Test
@@ -1605,7 +1674,7 @@ public class SdkSandboxManagerServiceUnitTest {
TIME_SYSTEM_SERVER_CALLED_APP,
TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP,
TIME_SYSTEM_SERVER_CALLED_SANDBOX);
- loadSdk();
+ loadSdk(SDK_NAME);
// 2. Call request package
FakeRequestSurfacePackageCallbackBinder surfacePackageCallback =
@@ -1627,80 +1696,122 @@ public class SdkSandboxManagerServiceUnitTest {
() ->
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
+ SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
(int)
(TIME_SYSTEM_SERVER_CALLED_SANDBOX
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ true,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED),
- Mockito.eq(
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
+ Mockito.eq(SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX)));
+ Mockito.eq(SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX),
+ Mockito.eq(mClientAppUid)));
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED),
- Mockito.eq(
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
+ Mockito.eq(SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX)));
+ Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX),
+ Mockito.eq(mClientAppUid)));
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED),
- Mockito.eq(
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
+ Mockito.eq(SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK)));
+ Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK),
+ Mockito.eq(mClientAppUid)));
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
Mockito.eq(SdkSandboxStatsLog.SANDBOX_API_CALLED),
- Mockito.eq(
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
+ Mockito.eq(SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE),
Mockito.anyInt(),
Mockito.eq(/*success=*/ true),
- Mockito.eq(SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER)));
+ Mockito.eq(SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER),
+ Mockito.eq(mClientAppUid)));
+ }
+
+ @Test
+ public void testLatencyMetrics_SystemServerSandboxToApp_RequestSurfacePackage()
+ throws RemoteException {
+ Mockito.when(mInjector.getCurrentTime())
+ .thenReturn(
+ TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP,
+ START_TIME_TO_LOAD_SANDBOX,
+ END_TIME_TO_LOAD_SANDBOX,
+ TIME_SYSTEM_SERVER_CALLED_SANDBOX,
+ TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_SANDBOX,
+ TIME_SYSTEM_SERVER_CALLED_APP,
+ TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP,
+ TIME_SYSTEM_SERVER_CALLED_SANDBOX,
+ TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_SANDBOX,
+ TIME_SYSTEM_SERVER_CALLED_APP);
+ loadSdk(SDK_NAME);
+
+ // 2. Call request package
+ FakeRequestSurfacePackageCallbackBinder surfacePackageCallback =
+ new FakeRequestSurfacePackageCallbackBinder();
+ mService.requestSurfacePackage(
+ TEST_PACKAGE,
+ SDK_NAME,
+ new Binder(),
+ 0,
+ 500,
+ 500,
+ TIME_APP_CALLED_SYSTEM_SERVER,
+ new Bundle(),
+ surfacePackageCallback);
+ mSdkSandboxService.sendSurfacePackageReady(
+ new SandboxLatencyInfo(TIME_SYSTEM_SERVER_CALLED_SANDBOX));
+
+ ExtendedMockito.verify(
+ () ->
+ SdkSandboxStatsLog.write(
+ SdkSandboxStatsLog.SANDBOX_API_CALLED,
+ SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
+ (int)
+ (TIME_SYSTEM_SERVER_CALLED_APP
+ - TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_SANDBOX),
+ /*success=*/ true,
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP,
+ mClientAppUid));
}
@Test
- public void testLatencyMetrics_SystemServerAppToSandbox_GetLoadedSdkLibrariesInfo() {
+ public void testLatencyMetrics_SystemServerAppToSandbox_GetSandboxedSdks() {
// TODO(b/242149555): Update tests to use fake for getting time series.
Mockito.when(mInjector.getCurrentTime())
.thenReturn(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP, END_TIME_IN_SYSTEM_SERVER);
- mService.getLoadedSdkLibrariesInfo(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER);
+ mService.getSandboxedSdks(TEST_PACKAGE, TIME_APP_CALLED_SYSTEM_SERVER);
ExtendedMockito.verify(
() ->
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__GET_LOADED_SDK_LIBRARIES_INFO,
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__GET_SANDBOXED_SDKS,
(int)
(END_TIME_IN_SYSTEM_SERVER
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ true,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
}
@Test
@@ -1711,12 +1822,11 @@ public class SdkSandboxManagerServiceUnitTest {
() ->
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
+ SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
/*latency=*/ 1,
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_APP));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_APP,
+ mClientAppUid));
}
@Test
@@ -1753,14 +1863,14 @@ public class SdkSandboxManagerServiceUnitTest {
assertThat(listener.isKillSwitchEnabled()).isFalse();
listener.onPropertiesChanged(
new DeviceConfig.Properties(
- DeviceConfig.NAMESPACE_SDK_SANDBOX,
+ DeviceConfig.NAMESPACE_ADSERVICES,
Map.of(PROPERTY_DISABLE_SANDBOX, "true")));
assertThat(listener.isKillSwitchEnabled()).isTrue();
listener.onPropertiesChanged(
new DeviceConfig.Properties(
- DeviceConfig.NAMESPACE_SDK_SANDBOX,
+ DeviceConfig.NAMESPACE_ADSERVICES,
Map.of(PROPERTY_DISABLE_SANDBOX, "false")));
- assertThat(listener.isKillSwitchEnabled()).isTrue();
+ assertThat(listener.isKillSwitchEnabled()).isFalse();
}
@Test
@@ -1770,13 +1880,13 @@ public class SdkSandboxManagerServiceUnitTest {
mService.getSdkSandboxSettingsListener();
listener.onPropertiesChanged(
new DeviceConfig.Properties(
- DeviceConfig.NAMESPACE_SDK_SANDBOX,
+ DeviceConfig.NAMESPACE_ADSERVICES,
Map.of(PROPERTY_DISABLE_SANDBOX, "false")));
mService.getSdkSandboxSettingsListener().reset();
- loadSdk();
+ loadSdk(SDK_NAME);
listener.onPropertiesChanged(
new DeviceConfig.Properties(
- DeviceConfig.NAMESPACE_SDK_SANDBOX,
+ DeviceConfig.NAMESPACE_ADSERVICES,
Map.of(PROPERTY_DISABLE_SANDBOX, "true")));
int callingUid = Binder.getCallingUid();
final CallingInfo callingInfo = new CallingInfo(callingUid, TEST_PACKAGE);
@@ -1784,15 +1894,18 @@ public class SdkSandboxManagerServiceUnitTest {
}
@Test
- public void testLoadSdkFailsWhenSandboxDisabled() {
+ public void testLoadSdkFailsWhenSandboxDisabled() throws Exception {
disableNetworkPermissionChecks();
disableForegroundCheck();
SdkSandboxManagerService.SdkSandboxSettingsListener listener =
mService.getSdkSandboxSettingsListener();
listener.reset();
+ // Sleep needed to avoid deadlock.
+ // TODO(b/257255118): Remove this sleep.
+ Thread.sleep(500);
listener.onPropertiesChanged(
new DeviceConfig.Properties(
- DeviceConfig.NAMESPACE_SDK_SANDBOX,
+ DeviceConfig.NAMESPACE_ADSERVICES,
Map.of(PROPERTY_DISABLE_SANDBOX, "true")));
FakeLoadSdkCallbackBinder callback = new FakeLoadSdkCallbackBinder();
mService.loadSdk(
@@ -1828,14 +1941,14 @@ public class SdkSandboxManagerServiceUnitTest {
() ->
SdkSandboxStatsLog.write(
SdkSandboxStatsLog.SANDBOX_API_CALLED,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
+ SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE,
(int)
(TIME_FAILURE_HANDLED
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ false,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
}
@Test
@@ -1857,7 +1970,7 @@ public class SdkSandboxManagerServiceUnitTest {
TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_SANDBOX,
TIME_SYSTEM_SERVER_CALLED_APP);
- loadSdk();
+ loadSdk(SDK_NAME);
mService.unloadSdk(TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER);
ExtendedMockito.verify(
() ->
@@ -1868,8 +1981,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP
- TIME_APP_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1881,11 +1994,12 @@ public class SdkSandboxManagerServiceUnitTest {
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ true,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
}
@Test
- public void testLatencyMetrics_systemServer_unloadSdk_withSandboxLatencies() throws Exception {
+ public void testLatencyMetrics_SystemServer_UnloadSdk_WithSandboxLatencies() throws Exception {
disableKillUid();
Mockito.when(mInjector.getCurrentTime())
@@ -1904,7 +2018,7 @@ public class SdkSandboxManagerServiceUnitTest {
TIME_SYSTEM_SERVER_CALLED_APP,
TIME_SANDBOX_CALLED_SYSTEM_SERVER);
- loadSdk();
+ loadSdk(SDK_NAME);
mService.unloadSdk(TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER);
mSdkSandboxService.sendUnloadSdkSuccess();
@@ -1917,7 +2031,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SANDBOX_RECEIVED_CALL_FROM_SYSTEM_SERVER
- TIME_SYSTEM_SERVER_CALLED_SANDBOX),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX));
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1930,7 +2045,8 @@ public class SdkSandboxManagerServiceUnitTest {
- (TIME_SDK_CALL_COMPLETED
- TIME_SANDBOX_CALLED_SDK)),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1939,7 +2055,8 @@ public class SdkSandboxManagerServiceUnitTest {
SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__UNLOAD_SDK,
(int) (TIME_SDK_CALL_COMPLETED - TIME_SANDBOX_CALLED_SDK),
/*success=*/ true,
- SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1950,7 +2067,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SANDBOX_CALLED_SYSTEM_SERVER
- TIME_SANDBOX_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER));
+ SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER,
+ mClientAppUid));
ExtendedMockito.verify(
() ->
@@ -1961,7 +2079,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SYSTEM_SERVER_CALLED_APP
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_SANDBOX),
/*success=*/ true,
- SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP));
+ SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP,
+ mClientAppUid));
}
@Test
@@ -1985,8 +2104,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP
- TIME_APP_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ mClientAppUid));
}
@Test
@@ -2013,7 +2132,8 @@ public class SdkSandboxManagerServiceUnitTest {
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ true,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
}
@Test
@@ -2045,8 +2165,8 @@ public class SdkSandboxManagerServiceUnitTest {
(TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP
- TIME_APP_CALLED_SYSTEM_SERVER),
/*success=*/ true,
- SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER));
+ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER,
+ mClientAppUid));
}
@Test
@@ -2081,7 +2201,8 @@ public class SdkSandboxManagerServiceUnitTest {
- TIME_SYSTEM_SERVER_RECEIVED_CALL_FROM_APP),
/*success=*/ true,
SdkSandboxStatsLog
- .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX));
+ .SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX,
+ mClientAppUid));
}
private SandboxLatencyInfo getFakedSandboxLatencies() {
@@ -2096,12 +2217,12 @@ public class SdkSandboxManagerServiceUnitTest {
return sandboxLatencyInfo;
}
- private void loadSdk() throws RemoteException {
+ private void loadSdk(String sdkName) throws RemoteException {
disableNetworkPermissionChecks();
disableForegroundCheck();
FakeLoadSdkCallbackBinder callback = new FakeLoadSdkCallbackBinder();
mService.loadSdk(
- TEST_PACKAGE, SDK_NAME, TIME_APP_CALLED_SYSTEM_SERVER, new Bundle(), callback);
+ TEST_PACKAGE, sdkName, TIME_APP_CALLED_SYSTEM_SERVER, new Bundle(), callback);
mSdkSandboxService.sendLoadCodeSuccessful();
assertThat(callback.isLoadSdkSuccessful()).isTrue();
}
@@ -2171,10 +2292,11 @@ public class SdkSandboxManagerServiceUnitTest {
private ILoadSdkInSandboxCallback mLoadSdkInSandboxCallback;
private final ISdkSandboxManagerToSdkSandboxCallback mManagerToSdkCallback;
private IRequestSurfacePackageFromSdkCallback mRequestSurfacePackageFromSdkCallback = null;
- private ISdkToServiceCallback mSdkToServiceCallback;
private IUnloadSdkCallback mUnloadSdkCallback = null;
private boolean mSurfacePackageRequested = false;
+ private int mInitializationCount = 0;
+
boolean mIsDisabledResponse = false;
private SharedPreferencesUpdate mLastSyncUpdate = null;
@@ -2184,9 +2306,13 @@ public class SdkSandboxManagerServiceUnitTest {
}
@Override
+ public void initialize(ISdkToServiceCallback sdkToServiceCallback) {
+ mInitializationCount++;
+ }
+
+ @Override
public void loadSdk(
String callingPackageName,
- IBinder codeToken,
ApplicationInfo info,
String sdkName,
String sdkProviderClass,
@@ -2194,15 +2320,13 @@ public class SdkSandboxManagerServiceUnitTest {
String deDataDir,
Bundle params,
ILoadSdkInSandboxCallback callback,
- SandboxLatencyInfo sandboxLatencyInfo,
- ISdkToServiceCallback sdkToServiceCallback) {
+ SandboxLatencyInfo sandboxLatencyInfo) {
mLoadSdkInSandboxCallback = callback;
- mSdkToServiceCallback = sdkToServiceCallback;
}
@Override
public void unloadSdk(
- IBinder sdkToken,
+ String sdkName,
IUnloadSdkCallback callback,
SandboxLatencyInfo sandboxLatencyInfo) {
mUnloadSdkCallback = callback;
@@ -2232,12 +2356,11 @@ public class SdkSandboxManagerServiceUnitTest {
return mLastSyncUpdate;
}
+ int getInitializationCount() {
+ return mInitializationCount;
+ }
+
void sendLoadCodeSuccessful() throws RemoteException {
- // Whenever loadSdk has been called successfully, the callback should have been
- // instantiated.
- Objects.requireNonNull(
- mSdkToServiceCallback,
- "mSdkToServiceCallback should have been passed when loadSdk succeeded");
mLoadSdkInSandboxCallback.onLoadSdkSuccess(
new SandboxedSdk(new Binder()),
mManagerToSdkCallback,
diff --git a/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxStorageManagerUnitTest.java b/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxStorageManagerUnitTest.java
index 1e5934913..9b4252790 100644
--- a/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxStorageManagerUnitTest.java
+++ b/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxStorageManagerUnitTest.java
@@ -29,7 +29,7 @@ import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.pm.PackageManagerLocal;
-import com.android.server.sdksandbox.SdkSandboxStorageManager.SdkDataDirInfo;
+import com.android.server.sdksandbox.SdkSandboxStorageManager.StorageDirInfo;
import com.android.server.sdksandbox.SdkSandboxStorageManager.SubDirectories;
import org.junit.After;
@@ -41,6 +41,7 @@ import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -56,6 +57,8 @@ public class SdkSandboxStorageManagerUnitTest {
private static final String CLIENT_PKG_NAME = "client";
private static final String SDK_NAME = "sdk";
private static final String SDK2_NAME = "sdk2";
+ private static final String STORAGE_UUID = "41217664-9172-527a-b3d5-edabb50a7d69";
+ private static final int USER_ID = 0;
// Use the test app's private storage as mount point for sdk storage testing
private SdkSandboxStorageManager mSdkSandboxStorageManager;
@@ -105,59 +108,58 @@ public class SdkSandboxStorageManagerUnitTest {
@Test
public void test_GetSdkDataPackageDirectory() throws Exception {
- assertThat(mSdkSandboxStorageManager.getSdkDataPackageDirectory(null, 0, "foo", true))
+ assertThat(mSdkSandboxStorageManager.getSdkDataPackageDirectory(null, USER_ID, "foo", true))
.isEqualTo(mTestDir + "/data/misc_ce/0/sdksandbox/foo");
// Build DE path
- assertThat(mSdkSandboxStorageManager.getSdkDataPackageDirectory(null, 0, "foo", false))
+ assertThat(
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ null, USER_ID, "foo", false))
.isEqualTo(mTestDir + "/data/misc_de/0/sdksandbox/foo");
// Build with different package name
- assertThat(mSdkSandboxStorageManager.getSdkDataPackageDirectory(null, 0, "bar", true))
+ assertThat(mSdkSandboxStorageManager.getSdkDataPackageDirectory(null, USER_ID, "bar", true))
.isEqualTo(mTestDir + "/data/misc_ce/0/sdksandbox/bar");
// Build with different user
assertThat(mSdkSandboxStorageManager.getSdkDataPackageDirectory(null, 10, "foo", true))
.isEqualTo(mTestDir + "/data/misc_ce/10/sdksandbox/foo");
// Build with different volume
- assertThat(mSdkSandboxStorageManager.getSdkDataPackageDirectory("hello", 0, "foo", true))
+ assertThat(
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ "hello", USER_ID, "foo", true))
.isEqualTo(mTestDir + "/mnt/expand/hello/misc_ce/0/sdksandbox/foo");
}
@Test
- public void test_SdkDataDirInfo_GetterApis() throws Exception {
- final SdkDataDirInfo sdkInfo = new SdkDataDirInfo("foo", "bar");
+ public void test_StorageDirInfo_GetterApis() throws Exception {
+ final StorageDirInfo sdkInfo = new StorageDirInfo("foo", "bar");
assertThat(sdkInfo.getCeDataDir()).isEqualTo("foo");
assertThat(sdkInfo.getDeDataDir()).isEqualTo("bar");
}
@Test
- public void test_GetSdkDataDirInfo_NonExistingStorage() throws Exception {
- // Call getSdkDataDirInfo on SdkStorageManager
+ public void test_getSdkStorageDirInfo_nonExistingStorage() throws Exception {
+ // Call getSdkStorageDirInfo on SdkStorageManager
final CallingInfo callingInfo = new CallingInfo(CLIENT_UID, CLIENT_PKG_NAME);
- final SdkDataDirInfo sdkInfo =
- mSdkSandboxStorageManager.getSdkDataDirInfo(callingInfo, SDK_NAME);
+ final StorageDirInfo sdkInfo =
+ mSdkSandboxStorageManager.getSdkStorageDirInfo(callingInfo, SDK_NAME);
assertThat(sdkInfo.getCeDataDir()).isNull();
assertThat(sdkInfo.getDeDataDir()).isNull();
}
@Test
- public void test_GetSdkDataDirInfo_StorageExists() throws Exception {
- createSdkStorageForTest(
- /*volumeUuid=*/ null,
- /*userId=*/ 0,
- CLIENT_PKG_NAME,
- Arrays.asList(SDK_NAME),
- Collections.emptyList());
+ public void test_getSdkStorageDirInfo_storageExists() throws Exception {
+ createSdkStorageForTest(Arrays.asList(SDK_NAME), Collections.emptyList());
final ApplicationInfo info = new ApplicationInfo();
- info.storageUuid = UUID.fromString("41217664-9172-527a-b3d5-edabb50a7d69");
+ info.storageUuid = UUID.fromString(STORAGE_UUID);
Mockito.doReturn(info)
.when(mPmMock)
.getApplicationInfo(Mockito.any(String.class), Mockito.anyInt());
- // Call getSdkDataDirInfo on SdkStorageManager
+ // Call getSdkStorageDirInfo on SdkStorageManager
final CallingInfo callingInfo = new CallingInfo(CLIENT_UID, CLIENT_PKG_NAME);
- final SdkDataDirInfo sdkInfo =
- mSdkSandboxStorageManager.getSdkDataDirInfo(callingInfo, SDK_NAME);
+ final StorageDirInfo sdkInfo =
+ mSdkSandboxStorageManager.getSdkStorageDirInfo(callingInfo, SDK_NAME);
assertThat(sdkInfo.getCeDataDir())
.isEqualTo(mTestDir + "/data/misc_ce/0/sdksandbox/client/sdk@sdk");
@@ -166,10 +168,41 @@ public class SdkSandboxStorageManagerUnitTest {
}
@Test
- public void test_GetMountedVolumes_NewVolumeExists() throws Exception {
+ public void test_getInernalStorageDirInfo_nonExistingStorage() throws Exception {
+ final CallingInfo callingInfo = new CallingInfo(CLIENT_UID, CLIENT_PKG_NAME);
+ final StorageDirInfo dirInfo =
+ mSdkSandboxStorageManager.getInternalStorageDirInfo(callingInfo, SDK_NAME);
+
+ assertThat(dirInfo.getCeDataDir()).isNull();
+ assertThat(dirInfo.getDeDataDir()).isNull();
+ }
+
+ @Test
+ public void test_getInternalStorageDirInfo_storageExists() throws Exception {
+ createSdkStorageForTest(Collections.emptyList(), Arrays.asList(SubDirectories.SANDBOX_DIR));
+
+ final ApplicationInfo info = new ApplicationInfo();
+ info.storageUuid = UUID.fromString(STORAGE_UUID);
+ Mockito.doReturn(info)
+ .when(mPmMock)
+ .getApplicationInfo(Mockito.any(String.class), Mockito.anyInt());
+
+ final CallingInfo callingInfo = new CallingInfo(CLIENT_UID, CLIENT_PKG_NAME);
+ final StorageDirInfo internalSubDirInfo =
+ mSdkSandboxStorageManager.getInternalStorageDirInfo(
+ callingInfo, SubDirectories.SANDBOX_DIR);
+
+ assertThat(internalSubDirInfo.getCeDataDir())
+ .isEqualTo(mTestDir + "/data/misc_ce/0/sdksandbox/client/sandbox#sandbox");
+ assertThat(internalSubDirInfo.getDeDataDir())
+ .isEqualTo(mTestDir + "/data/misc_de/0/sdksandbox/client/sandbox#sandbox");
+ }
+
+ @Test
+ public void test_getMountedVolumes_newVolumeExists() throws Exception {
createSdkStorageForTest(
/*volumeUuid=*/ null,
- /*userId=*/ 0,
+ USER_ID,
CLIENT_PKG_NAME,
Arrays.asList(SDK_NAME),
Collections.emptyList());
@@ -189,7 +222,7 @@ public class SdkSandboxStorageManagerUnitTest {
public void test_GetMountedVolumes_NewVolumeDoesNotExist() throws Exception {
createSdkStorageForTest(
/*volumeUuid=*/ null,
- /*userId=*/ 0,
+ USER_ID,
CLIENT_PKG_NAME,
Arrays.asList(SDK_NAME),
Collections.emptyList());
@@ -203,7 +236,7 @@ public class SdkSandboxStorageManagerUnitTest {
public void test_onUserUnlocking_Instrumentation_NoSdk_PackageDirNotRemoved() throws Exception {
createSdkStorageForTest(
/*volumeUuid=*/ null,
- /*userId=*/ 0,
+ USER_ID,
CLIENT_PKG_NAME,
Collections.emptyList(),
Collections.emptyList());
@@ -216,12 +249,12 @@ public class SdkSandboxStorageManagerUnitTest {
final Path ceDataPackageDirectory =
Paths.get(
mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- null, 0, CLIENT_PKG_NAME, true));
+ null, USER_ID, CLIENT_PKG_NAME, true));
final Path deDataPackageDirectory =
Paths.get(
mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- null, 0, CLIENT_PKG_NAME, false));
+ null, USER_ID, CLIENT_PKG_NAME, false));
assertThat(Files.exists(ceDataPackageDirectory)).isTrue();
assertThat(Files.exists(deDataPackageDirectory)).isTrue();
@@ -231,22 +264,22 @@ public class SdkSandboxStorageManagerUnitTest {
public void test_onUserUnlocking_NoInstrumentation_NoSdk_PackageDirRemoved() throws Exception {
createSdkStorageForTest(
/*volumeUuid=*/ null,
- /*userId=*/ 0,
+ USER_ID,
CLIENT_PKG_NAME,
Collections.emptyList(),
Collections.emptyList());
- mSdkSandboxStorageManager.onUserUnlocking(0);
+ mSdkSandboxStorageManager.onUserUnlocking(USER_ID);
final Path ceDataPackageDirectory =
Paths.get(
mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- null, 0, CLIENT_PKG_NAME, true));
+ null, USER_ID, CLIENT_PKG_NAME, true));
final Path deDataPackageDirectory =
Paths.get(
mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- null, 0, CLIENT_PKG_NAME, false));
+ null, USER_ID, CLIENT_PKG_NAME, false));
assertThat(Files.exists(ceDataPackageDirectory)).isFalse();
assertThat(Files.exists(deDataPackageDirectory)).isFalse();
@@ -254,12 +287,7 @@ public class SdkSandboxStorageManagerUnitTest {
@Test
public void test_SdkSubDirectories_NonExistingParentPath() throws Exception {
- createSdkStorageForTest(
- /*volumeUuid=*/ null,
- /*userId=*/ 0,
- CLIENT_PKG_NAME,
- Arrays.asList(SDK_NAME),
- Collections.emptyList());
+ createSdkStorageForTest(Arrays.asList(SDK_NAME), Collections.emptyList());
final SubDirectories subDirs = new SubDirectories("/does/not/exist");
@@ -269,16 +297,11 @@ public class SdkSandboxStorageManagerUnitTest {
@Test
public void test_SdkSubDirectories_GetSdkSubDir() throws Exception {
- createSdkStorageForTest(
- /*volumeUuid=*/ null,
- /*userId=*/ 0,
- CLIENT_PKG_NAME,
- Arrays.asList(SDK_NAME),
- Collections.emptyList());
+ createSdkStorageForTest(Arrays.asList(SDK_NAME), Collections.emptyList());
final String packageDir =
mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- /*volumeUuid=*/ null, /*userId=*/ 0, CLIENT_PKG_NAME, /*isCeData=*/ true);
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, /*isCeData=*/ true);
final SubDirectories subDirs = new SubDirectories(packageDir);
@@ -294,15 +317,12 @@ public class SdkSandboxStorageManagerUnitTest {
@Test
public void test_SdkSubDirectories_IsValid() throws Exception {
createSdkStorageForTest(
- /*volumeUuid=*/ null,
- /*userId=*/ 0,
- CLIENT_PKG_NAME,
Arrays.asList(SDK_NAME, SDK2_NAME),
- Arrays.asList("shared"));
+ Arrays.asList(SubDirectories.SHARED_DIR, SubDirectories.SANDBOX_DIR));
final String packageDir =
mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- /*volumeUuid=*/ null, /*userId=*/ 0, CLIENT_PKG_NAME, /*isCeData=*/ true);
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, /*isCeData=*/ true);
final SubDirectories subDirs = new SubDirectories(packageDir);
@@ -314,16 +334,11 @@ public class SdkSandboxStorageManagerUnitTest {
@Test
public void test_SdkSubDirectories_IsValid_MissingNonSdkStorage() throws Exception {
// Avoid creating "shared" storage
- createSdkStorageForTest(
- /*volumeUuid=*/ null,
- /*userId=*/ 0,
- CLIENT_PKG_NAME,
- Arrays.asList(SDK_NAME),
- Collections.emptyList());
+ createSdkStorageForTest(Arrays.asList(SDK_NAME), Collections.emptyList());
final String packageDir =
mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- /*volumeUuid=*/ null, /*userId=*/ 0, CLIENT_PKG_NAME, /*isCeData=*/ true);
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, /*isCeData=*/ true);
final SubDirectories subDirs = new SubDirectories(packageDir);
@@ -332,17 +347,12 @@ public class SdkSandboxStorageManagerUnitTest {
@Test
public void test_SdkSubDirectories_IsValid_HasUnknownSubDir() throws Exception {
- createSdkStorageForTest(
- /*volumeUuid=*/ null,
- /*userId=*/ 0,
- CLIENT_PKG_NAME,
- Arrays.asList(SDK_NAME),
- Arrays.asList("shared"));
+ createSdkStorageForTest(Arrays.asList(SDK_NAME), Arrays.asList(SubDirectories.SHARED_DIR));
// Create a random subdir not following our format
final String packageDir =
mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- /*volumeUuid=*/ null, /*userId=*/ 0, CLIENT_PKG_NAME, /*isCeData=*/ true);
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, /*isCeData=*/ true);
Path invalidSubDir = Paths.get(packageDir, "invalid");
Files.createDirectories(invalidSubDir);
@@ -351,6 +361,114 @@ public class SdkSandboxStorageManagerUnitTest {
assertThat(subDirs.isValid(Set.of(SDK_NAME))).isFalse();
}
+ @Test
+ public void test_SdkSubDirectories_GenerateSubDirNames_InternalOnly_NonExisting()
+ throws Exception {
+ final SubDirectories subDirs = new SubDirectories("does.not.exist");
+
+ final List<String> internalSubDirs = subDirs.generateSubDirNames(Collections.emptyList());
+ assertThat(internalSubDirs).hasSize(SubDirectories.INTERNAL_SUBDIRS.size());
+ assertThat(internalSubDirs).contains(SubDirectories.SHARED_DIR);
+ for (String subDir : internalSubDirs) {
+ if (subDir.equals(SubDirectories.SHARED_DIR)) continue;
+ final String[] tokens = subDir.split("#");
+ assertThat(tokens).asList().hasSize(2);
+ assertThat(SubDirectories.INTERNAL_SUBDIRS).contains(tokens[0]);
+ }
+ }
+
+ @Test
+ public void test_SdkSubDirectories_GenerateSubDirNames_InternalOnly_Existing()
+ throws Exception {
+ createSdkStorageForTest(
+ Collections.emptyList(),
+ Arrays.asList(SubDirectories.SHARED_DIR, SubDirectories.SANDBOX_DIR));
+
+ final String packageDir =
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, /*isCeData=*/ true);
+ final SubDirectories subDirs = new SubDirectories(packageDir);
+
+ final List<String> internalSubDirs = subDirs.generateSubDirNames(Collections.emptyList());
+ final String expectedSandboxDirName =
+ SubDirectories.SANDBOX_DIR + "#" + SubDirectories.SANDBOX_DIR;
+ assertThat(internalSubDirs).containsExactly("shared", expectedSandboxDirName);
+ }
+
+ @Test
+ public void test_SdkSubDirectories_GenerateSubDirNames_WithSdkNames() throws Exception {
+ createSdkStorageForTest(
+ Arrays.asList(SDK_NAME),
+ Arrays.asList(SubDirectories.SHARED_DIR, SubDirectories.SANDBOX_DIR));
+
+ final String packageDir =
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, /*isCeData=*/ true);
+ final SubDirectories subDirs = new SubDirectories(packageDir);
+
+ final List<String> allSubDirNames =
+ subDirs.generateSubDirNames(Arrays.asList(SDK_NAME, "foo"));
+ assertThat(allSubDirNames).hasSize(2 + SubDirectories.INTERNAL_SUBDIRS.size());
+
+ // Assert internal directories
+ final String expectedSandboxDirName =
+ SubDirectories.SANDBOX_DIR + "#" + SubDirectories.SANDBOX_DIR;
+ assertThat(allSubDirNames).containsAtLeast("shared", expectedSandboxDirName);
+
+ // Assert per-sdk directories
+ assertThat(allSubDirNames).contains(SDK_NAME + "@" + SDK_NAME);
+ boolean foundFoo =
+ allSubDirNames.stream()
+ .anyMatch(s -> s.contains("@") && s.split("@")[0].equals("foo"));
+ assertThat(foundFoo).isTrue();
+ }
+
+ @Test
+ public void test_getSdkStorageDirInfo() throws Exception {
+ final List<String> sdkNames = Arrays.asList(SDK_NAME);
+ createSdkStorageForTest(sdkNames, new ArrayList<>());
+
+ final ApplicationInfo info = new ApplicationInfo();
+ info.storageUuid = UUID.fromString(STORAGE_UUID);
+ Mockito.doReturn(info)
+ .when(mPmMock)
+ .getApplicationInfo(Mockito.any(String.class), Mockito.anyInt());
+
+ final CallingInfo callingInfo = new CallingInfo(CLIENT_UID, CLIENT_PKG_NAME);
+
+ List<StorageDirInfo> sdkStorageDirInfo =
+ mSdkSandboxStorageManager.getSdkStorageDirInfo(callingInfo);
+ List<StorageDirInfo> expectedSdkStorageDirInfo =
+ getSdkStorageDirInfoForTest(
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, sdkNames);
+
+ assertThat(sdkStorageDirInfo).containsExactlyElementsIn(expectedSdkStorageDirInfo);
+ }
+
+ @Test
+ public void test_getInternalStorageDirInfo() throws Exception {
+ final List<String> nonSdkDirectories =
+ Arrays.asList(SubDirectories.SHARED_DIR, SubDirectories.SANDBOX_DIR);
+ createSdkStorageForTest(new ArrayList<>(), nonSdkDirectories);
+
+ final ApplicationInfo info = new ApplicationInfo();
+ info.storageUuid = UUID.fromString(STORAGE_UUID);
+ Mockito.doReturn(info)
+ .when(mPmMock)
+ .getApplicationInfo(Mockito.any(String.class), Mockito.anyInt());
+
+ final CallingInfo callingInfo = new CallingInfo(CLIENT_UID, CLIENT_PKG_NAME);
+
+ List<StorageDirInfo> internalStorageDirInfo =
+ mSdkSandboxStorageManager.getInternalStorageDirInfo(callingInfo);
+ List<StorageDirInfo> expectedInternalStorageDirInfo =
+ getInternalStorageDirInfoForTest(
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, nonSdkDirectories);
+
+ assertThat(internalStorageDirInfo)
+ .containsExactlyElementsIn(expectedInternalStorageDirInfo);
+ }
+
/**
* A helper method for create sdk storage for test purpose.
*
@@ -365,24 +483,83 @@ public class SdkSandboxStorageManagerUnitTest {
List<String> sdkNames,
List<String> nonSdkDirectories)
throws Exception {
- for (int i = 0; i < 2; i++) {
- final boolean isCeData = (i == 0);
- final String packageDir =
- mSdkSandboxStorageManager.getSdkDataPackageDirectory(
- volumeUuid, userId, packageName, isCeData);
- final Path packagePath = Paths.get(packageDir);
- Files.createDirectories(packagePath);
- for (String sdkName : sdkNames) {
- final Path perSdkPath = Paths.get(packageDir, sdkName + "@" + sdkName);
- Files.createDirectories(perSdkPath);
- }
-
- for (String dir : nonSdkDirectories) {
- final Path subDir =
- Paths.get(packageDir, dir.equals("shared") ? dir : dir + "#" + dir);
- Files.createDirectories(subDir);
- }
+ final List<StorageDirInfo> sdkStorageInfos =
+ getSdkStorageDirInfoForTest(volumeUuid, userId, packageName, sdkNames);
+ final List<StorageDirInfo> internalStorageDirInfos =
+ getInternalStorageDirInfoForTest(
+ volumeUuid, userId, packageName, nonSdkDirectories);
+
+ createPackagePath(volumeUuid, userId, packageName, /*isCeData=*/ true);
+ createPackagePath(volumeUuid, userId, packageName, /*isCeData=*/ false);
+
+ createFilesFromList(sdkStorageInfos);
+ createFilesFromList(internalStorageDirInfos);
+ }
+
+ private void createPackagePath(
+ String volumeUuid, int userId, String packageName, boolean isCeData) throws Exception {
+ final String packageDir =
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ volumeUuid, userId, packageName, isCeData);
+ Files.createDirectories(Paths.get(packageDir));
+ }
+
+ private void createFilesFromList(List<StorageDirInfo> storageDirInfos) throws Exception {
+ final int storageDirInfosSize = storageDirInfos.size();
+
+ for (int i = 0; i < storageDirInfosSize; i++) {
+ final Path ceSdkStoragePath = Paths.get(storageDirInfos.get(i).getCeDataDir());
+ Files.createDirectories(ceSdkStoragePath);
+
+ final Path deSdkStoragePath = Paths.get(storageDirInfos.get(i).getDeDataDir());
+ Files.createDirectories(deSdkStoragePath);
+ }
+ }
+
+ private void createSdkStorageForTest(List<String> sdkNames, List<String> nonSdkDirectories)
+ throws Exception {
+ createSdkStorageForTest(
+ /*volumeUuid=*/ null, USER_ID, CLIENT_PKG_NAME, sdkNames, nonSdkDirectories);
+ }
+
+ /** A helper method to get the storage paths of SDKs for test purpose */
+ private List<StorageDirInfo> getSdkStorageDirInfoForTest(
+ String volumeUuid, int userId, String packageName, List<String> sdkNames) {
+
+ final List<StorageDirInfo> sdkStorageDirInfo = new ArrayList<>();
+ final String cePackageDir =
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ volumeUuid, userId, packageName, /*isCeData=*/ true);
+ final String dePackageDir =
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ volumeUuid, userId, packageName, /*isCeData=*/ false);
+
+ for (String sdkName : sdkNames) {
+ String sdkCeSubDirPath = cePackageDir + "/" + sdkName + "@" + sdkName;
+ String sdkDeSubDirPath = dePackageDir + "/" + sdkName + "@" + sdkName;
+ sdkStorageDirInfo.add(new StorageDirInfo(sdkCeSubDirPath, sdkDeSubDirPath));
+ }
+ return sdkStorageDirInfo;
+ }
+
+ /** A helper method to get the internal storage paths for test purpose */
+ private List<StorageDirInfo> getInternalStorageDirInfoForTest(
+ String volumeUuid, int userId, String packageName, List<String> nonSdkDirectories) {
+ final List<StorageDirInfo> internalStorageDirInfo = new ArrayList<>();
+ final String cePackageDir =
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ volumeUuid, userId, packageName, /*isCeData=*/ true);
+ final String dePackageDir =
+ mSdkSandboxStorageManager.getSdkDataPackageDirectory(
+ volumeUuid, userId, packageName, /*isCeData=*/ false);
+
+ for (String dir : nonSdkDirectories) {
+ String path = dir.equals(SubDirectories.SHARED_DIR) ? dir : dir + '#' + dir;
+ String sdkCeSubDirPath = cePackageDir + "/" + path;
+ String sdkDeSubDirPath = dePackageDir + "/" + path;
+ internalStorageDirInfo.add(new StorageDirInfo(sdkCeSubDirPath, sdkDeSubDirPath));
}
+ return internalStorageDirInfo;
}
private static class FakeSdkSandboxManagerLocal implements SdkSandboxManagerLocal {