summaryrefslogtreecommitdiff
path: root/adservices/service-core/java/com/android/adservices/service
diff options
context:
space:
mode:
Diffstat (limited to 'adservices/service-core/java/com/android/adservices/service')
-rw-r--r--adservices/service-core/java/com/android/adservices/service/Flags.java154
-rw-r--r--adservices/service-core/java/com/android/adservices/service/FlagsConstants.java42
-rw-r--r--adservices/service-core/java/com/android/adservices/service/PhFlags.java206
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionRunner.java45
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionServiceImpl.java54
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/OnDeviceAdSelectionRunner.java15
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/TrustedServerAdSelectionRunner.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/signature/ProtectedAudienceSignatureManager.java95
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/signature/ThreadUnsafeByteArrayOutputStream.java4
-rw-r--r--adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentStorageManager.java6
-rw-r--r--adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java167
-rw-r--r--adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java48
-rw-r--r--adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java9
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/AdServicesCommonServiceImpl.java6
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/AppManifestConfig.java42
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigCall.java153
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigHelper.java61
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigMetricsLogger.java92
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/FledgeAuthorizationFilter.java39
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/PackageChangedReceiver.java4
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/PermissionHelper.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java2
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/AdServicesStorageManager.java16
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/AppConsentForRStorageManager.java266
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/AppConsentStorageManager.java119
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/ConsentCompositeStorage.java658
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java4
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java31
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/ConsentManagerV2.java1445
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/ConsentMigrationUtils.java193
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/IConsentStorage.java26
-rw-r--r--adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceServiceImpl.java14
-rw-r--r--adservices/service-core/java/com/android/adservices/service/devapi/DevContextFilter.java2
-rw-r--r--adservices/service-core/java/com/android/adservices/service/encryptionkey/EncryptionKey.java15
-rw-r--r--adservices/service-core/java/com/android/adservices/service/enrollment/EnrollmentData.java15
-rw-r--r--adservices/service-core/java/com/android/adservices/service/exception/ConsentStorageDeferException.java26
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/PrivacyParams.java5
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/Source.java83
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java40
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/noising/ImpressionNoiseUtil.java66
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/noising/SourceNoiseHandler.java143
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirect.java51
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirects.java126
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistration.java22
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistrationQueueRunner.java34
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncSourceFetcher.java15
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncTriggerFetcher.java13
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/EnqueueAsyncRegistration.java1
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/registration/FetcherUtil.java9
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportBody.java6
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/AggregateReportingJobHandler.java16
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportApi.java1
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobHandler.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java264
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportingJobHandler.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportingStatus.java8
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/util/Enrollment.java1
-rw-r--r--adservices/service-core/java/com/android/adservices/service/shell/AdServicesShellCommandHandler.java33
-rw-r--r--adservices/service-core/java/com/android/adservices/service/shell/ShellCommandServiceImpl.java59
-rw-r--r--adservices/service-core/java/com/android/adservices/service/signals/PeriodicEncodingJobWorker.java29
-rw-r--r--adservices/service-core/java/com/android/adservices/service/signals/ProtectedSignalsServiceImpl.java6
-rw-r--r--adservices/service-core/java/com/android/adservices/service/signals/SignalsMaintenanceTasksWorker.java15
-rw-r--r--adservices/service-core/java/com/android/adservices/service/signals/UpdateProcessingOrchestrator.java9
-rw-r--r--adservices/service-core/java/com/android/adservices/service/signals/UpdateSignalsOrchestrator.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/stats/ConsentMigrationStats.java11
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/EncryptionManager.java5
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/TopicsServiceImpl.java6
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierInputManager.java8
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/classifier/ModelManager.java4
-rw-r--r--adservices/service-core/java/com/android/adservices/service/ui/data/UxStatesManager.java2
-rw-r--r--adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/AlreadyEnrolledChannel.java7
-rw-r--r--adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/ConsentNotificationResetChannel.java3
-rw-r--r--adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/RvcPostOTAChannel.java5
73 files changed, 3881 insertions, 1349 deletions
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 200ccec1f5..774504370c 100644
--- a/adservices/service-core/java/com/android/adservices/service/Flags.java
+++ b/adservices/service-core/java/com/android/adservices/service/Flags.java
@@ -117,6 +117,18 @@ public interface Flags extends CommonFlags {
}
/**
+ * Flag to override base64 public key used for encryption testing.
+ *
+ * <p>Note: Default value for this flag should not be changed from empty.
+ */
+ String TOPICS_TEST_ENCRYPTION_PUBLIC_KEY = "";
+
+ /** Returns test public key used for encrypting topics for testing. */
+ default String getTopicsTestEncryptionPublicKey() {
+ return TOPICS_TEST_ENCRYPTION_PUBLIC_KEY;
+ }
+
+ /**
* Returns the number of epochs to look back when deciding if a caller has observed a topic
* before.
*/
@@ -697,7 +709,7 @@ public interface Flags extends CommonFlags {
return MEASUREMENT_FLEX_API_MAX_INFORMATION_GAIN_EVENT;
}
- float MEASUREMENT_FLEX_API_MAX_INFORMATION_GAIN_NAVIGATION = 11.46173F;
+ float MEASUREMENT_FLEX_API_MAX_INFORMATION_GAIN_NAVIGATION = 11.5F;
/** Returns max information gain in Flexible Event API for Navigation sources */
default float getMeasurementFlexApiMaxInformationGainNavigation() {
@@ -3419,14 +3431,6 @@ public interface Flags extends CommonFlags {
return DEFAULT_ADSERVICES_VERSION_MAPPINGS;
}
- /** Default Determines whether EU notification flow change is enabled. */
- boolean DEFAULT_EU_NOTIF_FLOW_CHANGE_ENABLED = true;
-
- /** Determines whether EU notification flow change is enabled. */
- default boolean getEuNotifFlowChangeEnabled() {
- return DEFAULT_EU_NOTIF_FLOW_CHANGE_ENABLED;
- }
-
/** Default value for Measurement flexible event reporting API */
boolean MEASUREMENT_FLEXIBLE_EVENT_REPORTING_API_ENABLED = false;
@@ -3518,14 +3522,6 @@ public interface Flags extends CommonFlags {
return MEASUREMENT_MIN_EVENT_REPORT_DELAY_MILLIS;
}
- /** Disable early reporting windows configurability by default. */
- boolean MEASUREMENT_ENABLE_CONFIGURABLE_EVENT_REPORTING_WINDOWS = false;
-
- /** Returns true if event reporting windows configurability is enabled, false otherwise. */
- default boolean getMeasurementEnableConfigurableEventReportingWindows() {
- return MEASUREMENT_ENABLE_CONFIGURABLE_EVENT_REPORTING_WINDOWS;
- }
-
/**
* Default early reporting windows for VTC type source. Derived from {@link
* com.android.adservices.service.measurement.PrivacyParams#EVENT_EARLY_REPORTING_WINDOW_MILLISECONDS}.
@@ -3585,18 +3581,8 @@ public interface Flags extends CommonFlags {
return MEASUREMENT_AGGREGATE_REPORT_DELAY_CONFIG;
}
- /** Disable conversions configurability by default. */
- boolean DEFAULT_MEASUREMENT_ENABLE_VTC_CONFIGURABLE_MAX_EVENT_REPORTS = false;
-
- /**
- * Returns true, if event reports max conversions configurability is enabled, false otherwise.
- */
- default boolean getMeasurementEnableVtcConfigurableMaxEventReports() {
- return DEFAULT_MEASUREMENT_ENABLE_VTC_CONFIGURABLE_MAX_EVENT_REPORTS;
- }
-
- /** Disable conversions configurability by default. */
- int DEFAULT_MEASUREMENT_VTC_CONFIGURABLE_MAX_EVENT_REPORTS_COUNT = 2;
+ /** Default max allowed number of event reports. */
+ int DEFAULT_MEASUREMENT_VTC_CONFIGURABLE_MAX_EVENT_REPORTS_COUNT = 1;
/** Returns the default max allowed number of event reports. */
default int getMeasurementVtcConfigurableMaxEventReportsCount() {
@@ -3979,11 +3965,11 @@ public interface Flags extends CommonFlags {
}
/** Default RVC NOTIFICATION feature flag.. */
- boolean DEFAULT_RVC_NOTIFICATION_ENABLED = false;
+ boolean DEFAULT_RVC_POST_OTA_NOTIFICATION_ENABLED = false;
/** RVC Notification feature flag.. */
- default boolean getEnableRvcNotification() {
- return DEFAULT_RVC_NOTIFICATION_ENABLED;
+ default boolean getEnableRvcPostOtaNotification() {
+ return DEFAULT_RVC_POST_OTA_NOTIFICATION_ENABLED;
}
/** Default enableAdServices system API feature flag.. */
@@ -4100,76 +4086,6 @@ public interface Flags extends CommonFlags {
return MEASUREMENT_MIN_REPORTING_ORIGIN_UPDATE_WINDOW;
}
- float MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY = 0.0000208f;
-
- /**
- * {@link Source} Noise probability for 'Event' when both destinations (app and web) are
- * available on the source and supports install attribution.
- */
- default float getMeasurementInstallAttrDualDestinationEventNoiseProbability() {
- return MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY;
- }
-
- float MEASUREMENT_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY = 0.0170218f;
-
- /**
- * {@link Source} Noise probability for 'Navigation' when both destinations (app and web) are
- * available on the source.
- */
- default float getMeasurementDualDestinationNavigationNoiseProbability() {
- return MEASUREMENT_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY;
- }
-
- float MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY =
- MEASUREMENT_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY;
-
- /**
- * {@link Source} Noise probability for 'Navigation' when both destinations (app and web) are
- * available on the source and supports install attribution.
- */
- default float getMeasurementInstallAttrDualDestinationNavigationNoiseProbability() {
- return MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY;
- }
-
- float MEASUREMENT_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY = 0.0000042f;
-
- /**
- * {@link Source} Noise probability for 'Event' when both destinations (app and web) are
- * available on the source.
- */
- default float getMeasurementDualDestinationEventNoiseProbability() {
- return MEASUREMENT_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY;
- }
-
- float MEASUREMENT_INSTALL_ATTR_EVENT_NOISE_PROBABILITY = 0.0000125f;
-
- /** {@link Source} Noise probability for 'Event' which supports install attribution. */
- default float getMeasurementInstallAttrEventNoiseProbability() {
- return MEASUREMENT_INSTALL_ATTR_EVENT_NOISE_PROBABILITY;
- }
-
- float MEASUREMENT_EVENT_NOISE_PROBABILITY = 0.0000025f;
-
- /** {@link Source} Noise probability for 'Event'. */
- default float getMeasurementEventNoiseProbability() {
- return MEASUREMENT_EVENT_NOISE_PROBABILITY;
- }
-
- float MEASUREMENT_NAVIGATION_NOISE_PROBABILITY = 0.0024263f;
-
- /** {@link Source} Noise probability for 'Navigation'. */
- default float getMeasurementNavigationNoiseProbability() {
- return MEASUREMENT_NAVIGATION_NOISE_PROBABILITY;
- }
-
- float MEASUREMENT_INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY =
- MEASUREMENT_NAVIGATION_NOISE_PROBABILITY;
-
- /** {@link Source} Noise probability for 'Navigation' which supports install attribution. */
- default float getMeasurementInstallAttrNavigationNoiseProbability() {
- return MEASUREMENT_INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY;
- }
-
boolean MEASUREMENT_ENABLE_PREINSTALL_CHECK = false;
/** Returns true when pre-install check is enabled. */
@@ -4281,6 +4197,16 @@ public interface Flags extends CommonFlags {
}
/**
+ * Flag to control whether redirect registration urls should be modified to prefix the path
+ * string with .well-known
+ */
+ boolean MEASUREMENT_ENABLE_REDIRECT_TO_WELL_KNOWN_PATH = false;
+
+ default boolean getMeasurementEnableRedirectToWellKnownPath() {
+ return MEASUREMENT_ENABLE_REDIRECT_TO_WELL_KNOWN_PATH;
+ }
+
+ /**
* Default whether to limit logging for enrollment metrics to avoid performance issues. This
* includes not logging data that requires database queries and downloading MDD files.
*/
@@ -4499,4 +4425,28 @@ public interface Flags extends CommonFlags {
default int getBackgroundJobSamplingLoggingRate() {
return DEFAULT_BACKGROUND_JOB_SAMPLING_LOGGING_RATE;
}
+
+ /** Default value of the timeout for AppSearch write operations */
+ int DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS = 3000;
+
+ /**
+ * Gets the value of the timeout for AppSearch write operations, in milliseconds.
+ *
+ * @return the timeout, in milliseconds, for AppSearch write operations
+ */
+ default int getAppSearchWriteTimeout() {
+ return DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS;
+ }
+
+ /** Default value of the timeout for AppSearch read operations */
+ int DEFAULT_APPSEARCH_READ_TIMEOUT_MS = 500;
+
+ /**
+ * Gets the value of the timeout for AppSearch read operations, in milliseconds.
+ *
+ * @return the timeout, in milliseconds, for AppSearch read operations
+ */
+ default int getAppSearchReadTimeout() {
+ return DEFAULT_APPSEARCH_READ_TIMEOUT_MS;
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java b/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
index 56ca5efe24..cb408cefc4 100644
--- a/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
+++ b/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
@@ -94,6 +94,8 @@ public final class FlagsConstants {
public static final String KEY_TOPICS_ENCRYPTION_ENABLED = "topics_encryption_enabled";
public static final String KEY_TOPICS_DISABLE_PLAINTEXT_RESPONSE =
"topics_disable_plaintext_response";
+ public static final String KEY_TOPICS_TEST_ENCRYPTION_PUBLIC_KEY =
+ "topics_test_encryption_public_key";
// Topics classifier keys
public static final String KEY_CLASSIFIER_TYPE = "classifier_type";
@@ -284,9 +286,6 @@ public final class FlagsConstants {
public static final String KEY_MEASUREMENT_ENABLE_COARSE_EVENT_REPORT_DESTINATIONS =
"measurement_enable_coarse_event_report_destinations";
- public static final String KEY_MEASUREMENT_ENABLE_VTC_CONFIGURABLE_MAX_EVENT_REPORTS =
- "measurement_enable_vtc_configurable_max_event_reports_count";
-
public static final String KEY_MEASUREMENT_VTC_CONFIGURABLE_MAX_EVENT_REPORTS_COUNT =
"measurement_vtc_configurable_max_event_reports_count";
@@ -811,6 +810,8 @@ public final class FlagsConstants {
public static final String KEY_PPAPI_APP_SIGNATURE_ALLOW_LIST =
"ppapi_app_signature_allow_list";
+ public static final String KEY_APPSEARCH_WRITE_TIMEOUT_MS = "appsearch_write_timeout_ms";
+ public static final String KEY_APPSEARCH_READ_TIMEOUT_MS = "appsearch_read_timeout_ms";
public static final String KEY_APPSEARCH_WRITER_ALLOW_LIST_OVERRIDE =
"appsearch_writer_allow_list_override";
@@ -1069,9 +1070,6 @@ public final class FlagsConstants {
public static final String KEY_MEASUREMENT_MIN_EVENT_REPORT_DELAY_MILLIS =
"measurement_min_event_report_delay_millis";
- public static final String KEY_MEASUREMENT_ENABLE_CONFIGURABLE_EVENT_REPORTING_WINDOWS =
- "measurement_enable_configurable_event_reporting_windows";
-
public static final String KEY_MEASUREMENT_EVENT_REPORTS_VTC_EARLY_REPORTING_WINDOWS =
"measurement_event_reports_vtc_early_reporting_windows";
@@ -1134,31 +1132,8 @@ public final class FlagsConstants {
public static final String KEY_MEASUREMENT_ENABLE_API_STATUS_ALLOW_LIST_CHECK =
"measurement_enable_api_status_allow_list_check";
- public static final String
- KEY_MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY =
- "measurement_install_attr_dual_destination_event_noise_probability";
-
- public static final String KEY_MEASUREMENT_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY =
- "measurement_dual_destination_navigation_noise_probability";
-
- public static final String
- KEY_MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY =
- "measurement_install_attr_dual_destination_navigation_noise_probability";
-
- public static final String KEY_MEASUREMENT_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY =
- "measurement_dual_destination_event_noise_probability";
-
- public static final String KEY_MEASUREMENT_INSTALL_ATTR_EVENT_NOISE_PROBABILITY =
- "measurement_install_attr_event_noise_probability";
-
- public static final String KEY_MEASUREMENT_INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY =
- "measurement_install_attr_navigation_noise_probability";
-
- public static final String KEY_MEASUREMENT_EVENT_NOISE_PROBABILITY =
- "measurement_event_noise_probability";
-
- public static final String KEY_MEASUREMENT_NAVIGATION_NOISE_PROBABILITY =
- "measurement_navigation_noise_probability";
+ public static final String KEY_MEASUREMENT_ENABLE_REDIRECT_TO_WELL_KNOWN_PATH =
+ "measurement_enable_redirect_to_well_known_path";
// Database Schema Version Flags
public static final String KEY_ENABLE_DATABASE_SCHEMA_VERSION_8 =
@@ -1166,8 +1141,6 @@ public final class FlagsConstants {
public static final String KEY_ENABLE_DATABASE_SCHEMA_VERSION_9 =
"enable_database_schema_version_9";
- public static final String KEY_EU_NOTIF_FLOW_CHANGE_ENABLED = "eu_notif_flow_change_enabled";
-
public static final String KEY_NOTIFICATION_DISMISSED_ON_CLICK =
"notification_dmsmissed_on_click";
@@ -1175,7 +1148,8 @@ public final class FlagsConstants {
public static final String KEY_RVC_UX_ENABLED = "rvc_ux_enabled";
- public static final String KEY_RVC_NOTIFICATION_ENABLED = "rvc_notification_enabled";
+ public static final String KEY_RVC_POST_OTA_NOTIFICATION_ENABLED =
+ "rvc_post_ota_notification_enabled";
public static final String KEY_ENABLE_AD_SERVICES_SYSTEM_API = "enable_ad_services_system_api";
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 28b5720929..70948e5ddc 100644
--- a/adservices/service-core/java/com/android/adservices/service/PhFlags.java
+++ b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
@@ -16,6 +16,8 @@
package com.android.adservices.service;
+import static com.android.adservices.service.FlagsConstants.KEY_APPSEARCH_READ_TIMEOUT_MS;
+import static com.android.adservices.service.FlagsConstants.KEY_APPSEARCH_WRITE_TIMEOUT_MS;
import static com.android.adservices.service.FlagsConstants.KEY_ENCRYPTION_KEY_JOB_PERIOD_MS;
import static com.android.adservices.service.FlagsConstants.KEY_ENCRYPTION_KEY_JOB_REQUIRED_NETWORK_TYPE;
import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_MEASUREMENT_REPORT_AND_REGISTER_EVENT_API_ENABLED;
@@ -245,6 +247,15 @@ public final class PhFlags extends CommonPhFlags implements Flags {
}
@Override
+ public String getTopicsTestEncryptionPublicKey() {
+ // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
+ return DeviceConfig.getString(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* flagName */ FlagsConstants.KEY_TOPICS_TEST_ENCRYPTION_PUBLIC_KEY,
+ /* defaultValue */ TOPICS_TEST_ENCRYPTION_PUBLIC_KEY);
+ }
+
+ @Override
public int getClassifierType() {
// The priority of applying the flag values: SystemProperties, PH (DeviceConfig), then
// hard-coded value.
@@ -3472,16 +3483,6 @@ public final class PhFlags extends CommonPhFlags implements Flags {
}
@Override
- public boolean getMeasurementEnableConfigurableEventReportingWindows() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getBoolean(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants
- .KEY_MEASUREMENT_ENABLE_CONFIGURABLE_EVENT_REPORTING_WINDOWS,
- /* defaultValue */ MEASUREMENT_ENABLE_CONFIGURABLE_EVENT_REPORTING_WINDOWS);
- }
-
- @Override
public String getMeasurementEventReportsVtcEarlyReportingWindows() {
// The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
return DeviceConfig.getString(
@@ -3567,6 +3568,14 @@ public final class PhFlags extends CommonPhFlags implements Flags {
}
@Override
+ public boolean getMeasurementEnableRedirectToWellKnownPath() {
+ return DeviceConfig.getBoolean(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* flagName */ FlagsConstants.KEY_MEASUREMENT_ENABLE_REDIRECT_TO_WELL_KNOWN_PATH,
+ /* defaultValue */ MEASUREMENT_ENABLE_REDIRECT_TO_WELL_KNOWN_PATH);
+ }
+
+ @Override
public boolean getFledgeMeasurementReportAndRegisterEventApiEnabled() {
return DeviceConfig.getBoolean(
FlagsConstants.NAMESPACE_ADSERVICES,
@@ -4281,49 +4290,6 @@ public final class PhFlags extends CommonPhFlags implements Flags {
+ getMeasurementMinReportingOriginUpdateWindow());
writer.println(
"\t"
- + FlagsConstants
- .KEY_MEASUREMENT_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY
- + " = "
- + getMeasurementDualDestinationNavigationNoiseProbability());
- writer.println(
- "\t"
- + FlagsConstants
- .KEY_MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY
- + " = "
- + getMeasurementInstallAttrDualDestinationEventNoiseProbability());
- writer.println(
- "\t"
- + FlagsConstants
- .KEY_MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY
- + " = "
- + getMeasurementInstallAttrDualDestinationNavigationNoiseProbability());
- writer.println(
- "\t"
- + FlagsConstants.KEY_MEASUREMENT_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY
- + " = "
- + getMeasurementDualDestinationEventNoiseProbability());
- writer.println(
- "\t"
- + FlagsConstants.KEY_MEASUREMENT_INSTALL_ATTR_EVENT_NOISE_PROBABILITY
- + " = "
- + getMeasurementInstallAttrEventNoiseProbability());
- writer.println(
- "\t"
- + FlagsConstants.KEY_MEASUREMENT_INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY
- + " = "
- + getMeasurementInstallAttrNavigationNoiseProbability());
- writer.println(
- "\t"
- + FlagsConstants.KEY_MEASUREMENT_EVENT_NOISE_PROBABILITY
- + " = "
- + getMeasurementEventNoiseProbability());
- writer.println(
- "\t"
- + FlagsConstants.KEY_MEASUREMENT_NAVIGATION_NOISE_PROBABILITY
- + " = "
- + getMeasurementNavigationNoiseProbability());
- writer.println(
- "\t"
+ FlagsConstants.KEY_MEASUREMENT_REGISTRATION_JOB_QUEUE_KILL_SWITCH
+ " = "
+ getAsyncRegistrationJobQueueKillSwitch());
@@ -4369,11 +4335,6 @@ public final class PhFlags extends CommonPhFlags implements Flags {
+ getMeasurementMinEventReportDelayMillis());
writer.println(
"\t"
- + FlagsConstants.KEY_MEASUREMENT_ENABLE_CONFIGURABLE_EVENT_REPORTING_WINDOWS
- + " = "
- + getMeasurementEnableConfigurableEventReportingWindows());
- writer.println(
- "\t"
+ FlagsConstants.KEY_MEASUREMENT_EVENT_REPORTS_VTC_EARLY_REPORTING_WINDOWS
+ " = "
+ getMeasurementEventReportsVtcEarlyReportingWindows());
@@ -5154,11 +5115,6 @@ public final class PhFlags extends CommonPhFlags implements Flags {
writer.println("==== AdServices PH Flags Dump UI Related Flags ====");
writer.println(
"\t"
- + FlagsConstants.KEY_EU_NOTIF_FLOW_CHANGE_ENABLED
- + " = "
- + getEuNotifFlowChangeEnabled());
- writer.println(
- "\t"
+ FlagsConstants.KEY_UI_FEATURE_TYPE_LOGGING_ENABLED
+ " = "
+ isUiFeatureTypeLoggingEnabled());
@@ -5500,6 +5456,8 @@ public final class PhFlags extends CommonPhFlags implements Flags {
+ KEY_MEASUREMENT_ENABLE_SESSION_STABLE_KILL_SWITCHES
+ " = "
+ getMeasurementEnableSessionStableKillSwitches());
+ writer.println("\t" + KEY_APPSEARCH_WRITE_TIMEOUT_MS + " = " + getAppSearchWriteTimeout());
+ writer.println("\t" + KEY_APPSEARCH_READ_TIMEOUT_MS + " = " + getAppSearchReadTimeout());
}
@VisibleForTesting
@@ -5667,14 +5625,6 @@ public final class PhFlags extends CommonPhFlags implements Flags {
}
@Override
- public boolean getEuNotifFlowChangeEnabled() {
- return DeviceConfig.getBoolean(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants.KEY_EU_NOTIF_FLOW_CHANGE_ENABLED,
- /* defaultValue */ DEFAULT_EU_NOTIF_FLOW_CHANGE_ENABLED);
- }
-
- @Override
public boolean getNotificationDismissedOnClick() {
return DeviceConfig.getBoolean(
FlagsConstants.NAMESPACE_ADSERVICES,
@@ -5701,12 +5651,12 @@ public final class PhFlags extends CommonPhFlags implements Flags {
}
@Override
- public boolean getEnableRvcNotification() {
+ public boolean getEnableRvcPostOtaNotification() {
return getEnableAdServicesSystemApi()
&& DeviceConfig.getBoolean(
FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants.KEY_RVC_NOTIFICATION_ENABLED,
- /* defaultValue */ DEFAULT_RVC_NOTIFICATION_ENABLED);
+ /* flagName */ FlagsConstants.KEY_RVC_POST_OTA_NOTIFICATION_ENABLED,
+ /* defaultValue */ DEFAULT_RVC_POST_OTA_NOTIFICATION_ENABLED);
}
@Override
@@ -5729,7 +5679,9 @@ public final class PhFlags extends CommonPhFlags implements Flags {
getRecordManualInteractionEnabled());
uxMap.put(FlagsConstants.KEY_GA_UX_FEATURE_ENABLED, getGaUxFeatureEnabled());
uxMap.put(FlagsConstants.KEY_RVC_UX_ENABLED, getEnableRvcUx());
- uxMap.put(FlagsConstants.KEY_RVC_NOTIFICATION_ENABLED, getEnableRvcNotification());
+ uxMap.put(
+ FlagsConstants.KEY_RVC_POST_OTA_NOTIFICATION_ENABLED,
+ getEnableRvcPostOtaNotification());
uxMap.put(
FlagsConstants.KEY_UI_OTA_STRINGS_FEATURE_ENABLED, getUiOtaStringsFeatureEnabled());
uxMap.put(
@@ -5742,7 +5694,6 @@ public final class PhFlags extends CommonPhFlags implements Flags {
uxMap.put(
FlagsConstants.KEY_CONSENT_NOTIFICATION_ACTIVITY_DEBUG_MODE,
getConsentNotificationActivityDebugMode());
- uxMap.put(FlagsConstants.KEY_EU_NOTIF_FLOW_CHANGE_ENABLED, getEuNotifFlowChangeEnabled());
uxMap.put(FlagsConstants.KEY_U18_UX_ENABLED, getU18UxEnabled());
uxMap.put(
FlagsConstants.KEY_NOTIFICATION_DISMISSED_ON_CLICK,
@@ -5863,83 +5814,6 @@ public final class PhFlags extends CommonPhFlags implements Flags {
}
@Override
- public float getMeasurementInstallAttrDualDestinationEventNoiseProbability() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getFloat(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants
- .KEY_MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY,
- /* defaultValue */ MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY);
- }
-
- @Override
- public float getMeasurementDualDestinationNavigationNoiseProbability() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getFloat(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants
- .KEY_MEASUREMENT_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY,
- /* defaultValue */ MEASUREMENT_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY);
- }
-
- @Override
- public float getMeasurementInstallAttrDualDestinationNavigationNoiseProbability() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getFloat(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants
- .KEY_MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY,
- /* defaultValue */ MEASUREMENT_INSTALL_ATTR_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY);
- }
-
- @Override
- public float getMeasurementDualDestinationEventNoiseProbability() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getFloat(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants
- .KEY_MEASUREMENT_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY,
- /* defaultValue */ MEASUREMENT_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY);
- }
-
- @Override
- public float getMeasurementInstallAttrEventNoiseProbability() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getFloat(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants.KEY_MEASUREMENT_INSTALL_ATTR_EVENT_NOISE_PROBABILITY,
- /* defaultValue */ MEASUREMENT_INSTALL_ATTR_EVENT_NOISE_PROBABILITY);
- }
-
- @Override
- public float getMeasurementInstallAttrNavigationNoiseProbability() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getFloat(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants
- .KEY_MEASUREMENT_INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY,
- /* defaultValue */ MEASUREMENT_INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY);
- }
-
- @Override
- public float getMeasurementEventNoiseProbability() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getFloat(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants.KEY_MEASUREMENT_EVENT_NOISE_PROBABILITY,
- /* defaultValue */ MEASUREMENT_EVENT_NOISE_PROBABILITY);
- }
-
- @Override
- public float getMeasurementNavigationNoiseProbability() {
- // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
- return DeviceConfig.getFloat(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants.KEY_MEASUREMENT_NAVIGATION_NOISE_PROBABILITY,
- /* defaultValue */ MEASUREMENT_NAVIGATION_NOISE_PROBABILITY);
- }
-
- @Override
public boolean getMeasurementEnablePreinstallCheck() {
// The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
return DeviceConfig.getBoolean(
@@ -5949,16 +5823,6 @@ public final class PhFlags extends CommonPhFlags implements Flags {
}
@Override
- public boolean getMeasurementEnableVtcConfigurableMaxEventReports() {
- return DeviceConfig.getBoolean(
- FlagsConstants.NAMESPACE_ADSERVICES,
- /* flagName */ FlagsConstants
- .KEY_MEASUREMENT_ENABLE_VTC_CONFIGURABLE_MAX_EVENT_REPORTS,
- /* defaultValue */
- DEFAULT_MEASUREMENT_ENABLE_VTC_CONFIGURABLE_MAX_EVENT_REPORTS);
- }
-
- @Override
public int getMeasurementVtcConfigurableMaxEventReportsCount() {
return DeviceConfig.getInt(
FlagsConstants.NAMESPACE_ADSERVICES,
@@ -6526,4 +6390,20 @@ public final class PhFlags extends CommonPhFlags implements Flags {
return loggingRatio;
}
+
+ @Override
+ public int getAppSearchWriteTimeout() {
+ return DeviceConfig.getInt(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* name= */ FlagsConstants.KEY_APPSEARCH_WRITE_TIMEOUT_MS,
+ /* defaultValue= */ DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS);
+ }
+
+ @Override
+ public int getAppSearchReadTimeout() {
+ return DeviceConfig.getInt(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* name= */ FlagsConstants.KEY_APPSEARCH_READ_TIMEOUT_MS,
+ /* defaultValue= */ DEFAULT_APPSEARCH_READ_TIMEOUT_MS);
+ }
}
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 c337bf17ba..22eea4b056 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
@@ -49,7 +49,10 @@ import com.android.adservices.data.adselection.datahandlers.AdSelectionResultBid
import com.android.adservices.data.adselection.datahandlers.WinningCustomAudience;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.data.encryptionkey.EncryptionKeyDao;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.adselection.signature.ProtectedAudienceSignatureManager;
import com.android.adservices.service.common.AdSelectionServiceFilter;
import com.android.adservices.service.common.FrequencyCapAdDataValidator;
import com.android.adservices.service.common.Throttler;
@@ -76,6 +79,7 @@ import com.google.common.util.concurrent.UncheckedTimeoutException;
import java.time.Clock;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -123,8 +127,11 @@ public abstract class AdSelectionRunner {
static final String ON_DEVICE_AUCTION_KILL_SWITCH_ENABLED =
"On device auction kill switch enabled";
+ @NonNull protected final Context mContext;
@NonNull protected final CustomAudienceDao mCustomAudienceDao;
@NonNull protected final AdSelectionEntryDao mAdSelectionEntryDao;
+ @NonNull protected final EncryptionKeyDao mEncryptionKeyDao;
+ @NonNull protected final EnrollmentDao mEnrollmentDao;
@NonNull protected final ListeningExecutorService mLightweightExecutorService;
@NonNull protected final ListeningExecutorService mBackgroundExecutorService;
@NonNull protected final ScheduledThreadPoolExecutor mScheduledExecutor;
@@ -157,6 +164,8 @@ public abstract class AdSelectionRunner {
@NonNull final Context context,
@NonNull final CustomAudienceDao customAudienceDao,
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final EncryptionKeyDao encryptionKeyDao,
+ @NonNull final EnrollmentDao enrollmentDao,
@NonNull final ExecutorService lightweightExecutorService,
@NonNull final ExecutorService backgroundExecutorService,
@NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
@@ -173,6 +182,8 @@ public abstract class AdSelectionRunner {
Objects.requireNonNull(context);
Objects.requireNonNull(customAudienceDao);
Objects.requireNonNull(adSelectionEntryDao);
+ Objects.requireNonNull(encryptionKeyDao);
+ Objects.requireNonNull(enrollmentDao);
Objects.requireNonNull(lightweightExecutorService);
Objects.requireNonNull(backgroundExecutorService);
Objects.requireNonNull(adServicesLogger);
@@ -184,8 +195,11 @@ public abstract class AdSelectionRunner {
Objects.requireNonNull(debugReporting);
Objects.requireNonNull(adSelectionExecutionLogger);
+ mContext = context;
mCustomAudienceDao = customAudienceDao;
mAdSelectionEntryDao = adSelectionEntryDao;
+ mEncryptionKeyDao = encryptionKeyDao;
+ mEnrollmentDao = enrollmentDao;
mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService);
mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService);
mScheduledExecutor = scheduledExecutor;
@@ -209,6 +223,8 @@ public abstract class AdSelectionRunner {
@NonNull final Context context,
@NonNull final CustomAudienceDao customAudienceDao,
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final EncryptionKeyDao encryptionKeyDao,
+ @NonNull final EnrollmentDao enrollmentDao,
@NonNull final ExecutorService lightweightExecutorService,
@NonNull final ExecutorService backgroundExecutorService,
@NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
@@ -227,6 +243,8 @@ public abstract class AdSelectionRunner {
Objects.requireNonNull(context);
Objects.requireNonNull(customAudienceDao);
Objects.requireNonNull(adSelectionEntryDao);
+ Objects.requireNonNull(encryptionKeyDao);
+ Objects.requireNonNull(enrollmentDao);
Objects.requireNonNull(lightweightExecutorService);
Objects.requireNonNull(backgroundExecutorService);
Objects.requireNonNull(scheduledExecutor);
@@ -240,8 +258,11 @@ public abstract class AdSelectionRunner {
Objects.requireNonNull(adCounterHistogramUpdater);
Objects.requireNonNull(debugReporting);
+ mContext = context;
mCustomAudienceDao = customAudienceDao;
mAdSelectionEntryDao = adSelectionEntryDao;
+ mEncryptionKeyDao = encryptionKeyDao;
+ mEnrollmentDao = enrollmentDao;
mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService);
mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService);
mScheduledExecutor = scheduledExecutor;
@@ -277,7 +298,6 @@ public abstract class AdSelectionRunner {
final int traceCookie = Tracing.beginAsyncSection(Tracing.RUN_AD_SELECTION);
Objects.requireNonNull(inputParams);
Objects.requireNonNull(callback);
- AdSelectionConfig adSelectionConfig = inputParams.getAdSelectionConfig();
try {
ListenableFuture<Void> filterAndValidateRequestFuture =
@@ -501,7 +521,7 @@ public abstract class AdSelectionRunner {
@NonNull final String callerPackageName) {
sLogger.v("Beginning Ad Selection Orchestration");
- AdSelectionConfig adSelectionConfigInput = adSelectionConfig;
+ AdSelectionConfig adSelectionConfigInput;
if (!mFlags.getFledgeAdSelectionContextualAdsEnabled()) {
// Empty all contextual ads if the feature is disabled
sLogger.v("Contextual flow is disabled");
@@ -594,9 +614,7 @@ public abstract class AdSelectionRunner {
private int countBuyersRequested(@NonNull AdSelectionConfig adSelectionConfig) {
Objects.requireNonNull(adSelectionConfig);
- return adSelectionConfig.getCustomAudienceBuyers().stream()
- .collect(Collectors.toSet())
- .size();
+ return new HashSet<>(adSelectionConfig.getCustomAudienceBuyers()).size();
}
private int countBuyersFromCustomAudiences(
@@ -728,10 +746,27 @@ public abstract class AdSelectionRunner {
AdSelectionConfig adSelectionConfig) {
Map<AdTechIdentifier, SignedContextualAds> filteredContextualAdsMap = new HashMap<>();
sLogger.v("Filtering contextual ads in Ad Selection Config");
+ boolean isEnrollmentCheckEnabled = !mFlags.getDisableFledgeEnrollmentCheck();
+ ProtectedAudienceSignatureManager signatureManager =
+ new ProtectedAudienceSignatureManager(
+ mEnrollmentDao, mEncryptionKeyDao, isEnrollmentCheckEnabled);
for (Map.Entry<AdTechIdentifier, SignedContextualAds> entry :
adSelectionConfig.getBuyerSignedContextualAds().entrySet()) {
+ if (!signatureManager.isVerified(entry.getKey(), entry.getValue())) {
+ sLogger.v(
+ "Contextual ads for buyer: '%s' have an invalid signature and will be"
+ + " removed from the auction",
+ entry.getKey());
+ continue;
+ }
filteredContextualAdsMap.put(
entry.getKey(), mAdFilterer.filterContextualAds(entry.getValue()));
+ sLogger.v(
+ "Buyer '%s' has a valid signature. It's contextual ads filtered from "
+ + "%s ad(s) to %s ad(s)",
+ entry.getKey(),
+ entry.getValue().getAdsWithBid().size(),
+ filteredContextualAdsMap.get(entry.getKey()).getAdsWithBid().size());
}
return adSelectionConfig
.cloneToBuilder()
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 85bad0af02..9bff5dea67 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
@@ -71,12 +71,12 @@ import com.android.adservices.data.adselection.AdSelectionDebugReportingDatabase
import com.android.adservices.data.adselection.AdSelectionEntryDao;
import com.android.adservices.data.adselection.AdSelectionServerDatabase;
import com.android.adservices.data.adselection.AppInstallDao;
-import com.android.adservices.data.adselection.EncryptionContextDao;
-import com.android.adservices.data.adselection.EncryptionKeyDao;
import com.android.adservices.data.adselection.FrequencyCapDao;
import com.android.adservices.data.adselection.SharedStorageDatabase;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.CustomAudienceDatabase;
+import com.android.adservices.data.encryptionkey.EncryptionKeyDao;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.data.signals.EncodedPayloadDao;
import com.android.adservices.data.signals.ProtectedSignalsDatabase;
import com.android.adservices.service.Flags;
@@ -137,8 +137,8 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
@NonNull private final CustomAudienceDao mCustomAudienceDao;
@NonNull private final EncodedPayloadDao mEncodedPayloadDao;
@NonNull private final FrequencyCapDao mFrequencyCapDao;
- @NonNull private final EncryptionContextDao mEncryptionContextDao;
@NonNull private final EncryptionKeyDao mEncryptionKeyDao;
+ @NonNull private final EnrollmentDao mEnrollmentDao;
@NonNull private final AdServicesHttpsClient mAdServicesHttpsClient;
@NonNull private final ExecutorService mLightweightExecutor;
@NonNull private final ExecutorService mBackgroundExecutor;
@@ -168,8 +168,8 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
@NonNull CustomAudienceDao customAudienceDao,
@NonNull EncodedPayloadDao encodedPayloadDao,
@NonNull FrequencyCapDao frequencyCapDao,
- @NonNull EncryptionContextDao encryptionContextDao,
@NonNull EncryptionKeyDao encryptionKeyDao,
+ @NonNull EnrollmentDao enrollmentDao,
@NonNull AdServicesHttpsClient adServicesHttpsClient,
@NonNull DevContextFilter devContextFilter,
@NonNull ExecutorService lightweightExecutorService,
@@ -193,8 +193,8 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
Objects.requireNonNull(customAudienceDao);
Objects.requireNonNull(encodedPayloadDao);
Objects.requireNonNull(frequencyCapDao);
- Objects.requireNonNull(encryptionContextDao);
Objects.requireNonNull(encryptionKeyDao);
+ Objects.requireNonNull(enrollmentDao);
Objects.requireNonNull(adServicesHttpsClient);
Objects.requireNonNull(devContextFilter);
Objects.requireNonNull(lightweightExecutorService);
@@ -213,8 +213,8 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
mCustomAudienceDao = customAudienceDao;
mEncodedPayloadDao = encodedPayloadDao;
mFrequencyCapDao = frequencyCapDao;
- mEncryptionContextDao = encryptionContextDao;
mEncryptionKeyDao = encryptionKeyDao;
+ mEnrollmentDao = enrollmentDao;
mAdServicesHttpsClient = adServicesHttpsClient;
mDevContextFilter = devContextFilter;
mLightweightExecutor = lightweightExecutorService;
@@ -249,8 +249,8 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
CustomAudienceDatabase.getInstance(context).customAudienceDao(),
ProtectedSignalsDatabase.getInstance(context).getEncodedPayloadDao(),
SharedStorageDatabase.getInstance(context).frequencyCapDao(),
- AdSelectionServerDatabase.getInstance(context).encryptionContextDao(),
- AdSelectionServerDatabase.getInstance(context).encryptionKeyDao(),
+ EncryptionKeyDao.getInstance(context),
+ EnrollmentDao.getInstance(context),
new AdServicesHttpsClient(
AdServicesExecutors.getBlockingExecutor(),
CacheProviderFactory.create(context, FlagsFactory.getFlags())),
@@ -332,7 +332,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, inputParams.getCallerPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -370,7 +370,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, inputParams.getCallerPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -473,7 +473,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, inputParams.getCallerPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -674,6 +674,8 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
+ mEncryptionKeyDao,
+ mEnrollmentDao,
mAdServicesHttpsClient,
mLightweightExecutor,
mBackgroundExecutor,
@@ -709,6 +711,8 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
mContext,
mCustomAudienceDao,
mAdSelectionEntryDao,
+ mEncryptionKeyDao,
+ mEnrollmentDao,
mAdServicesHttpsClient,
mLightweightExecutor,
mBackgroundExecutor,
@@ -758,7 +762,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, inputParams.getCallerPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -801,7 +805,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, requestParams.getCallerPackageName(), apiName);
DevContext devContext = mDevContextFilter.createDevContext();
@@ -867,7 +871,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, inputParams.getCallerPackageName(), apiName);
int callerUid = getCallingUid(apiName);
@@ -924,7 +928,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, request.getCallerPackageName(), apiName);
AppInstallAdvertisersSetter setter =
@@ -956,7 +960,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, inputParams.getCallerPackageName(), apiName);
final int callingUid = getCallingUid(apiName);
@@ -1029,7 +1033,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -1096,7 +1100,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -1146,7 +1150,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -1201,7 +1205,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -1252,7 +1256,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -1301,7 +1305,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
int callingUid = getCallingUid(apiName);
@@ -1351,7 +1355,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
// TODO(b/265204820): Implement service
@@ -1389,7 +1393,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
// TODO(b/265204820): Implement service
@@ -1424,7 +1428,7 @@ public class AdSelectionServiceImpl extends AdSelectionService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
// TODO(b/265204820): Implement service
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
index 4c5b280ddb..e2a93966fe 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/OnDeviceAdSelectionRunner.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/OnDeviceAdSelectionRunner.java
@@ -35,6 +35,8 @@ 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.data.encryptionkey.EncryptionKeyDao;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AdSelectionServiceFilter;
import com.android.adservices.service.common.BinderFlagReader;
@@ -79,6 +81,8 @@ public class OnDeviceAdSelectionRunner extends AdSelectionRunner {
@NonNull final Context context,
@NonNull final CustomAudienceDao customAudienceDao,
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final EncryptionKeyDao encryptionKeyDao,
+ @NonNull final EnrollmentDao enrollmentDao,
@NonNull final AdServicesHttpsClient adServicesHttpsClient,
@NonNull final ExecutorService lightweightExecutorService,
@NonNull final ExecutorService backgroundExecutorService,
@@ -99,6 +103,8 @@ public class OnDeviceAdSelectionRunner extends AdSelectionRunner {
context,
customAudienceDao,
adSelectionEntryDao,
+ encryptionKeyDao,
+ enrollmentDao,
lightweightExecutorService,
backgroundExecutorService,
scheduledExecutor,
@@ -172,6 +178,8 @@ public class OnDeviceAdSelectionRunner extends AdSelectionRunner {
@NonNull final Context context,
@NonNull final CustomAudienceDao customAudienceDao,
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final EncryptionKeyDao encryptionKeyDao,
+ @NonNull final EnrollmentDao enrollmentDao,
@NonNull final AdServicesHttpsClient adServicesHttpsClient,
@NonNull final ExecutorService lightweightExecutorService,
@NonNull final ExecutorService backgroundExecutorService,
@@ -195,6 +203,8 @@ public class OnDeviceAdSelectionRunner extends AdSelectionRunner {
context,
customAudienceDao,
adSelectionEntryDao,
+ encryptionKeyDao,
+ enrollmentDao,
lightweightExecutorService,
backgroundExecutorService,
scheduledExecutor,
@@ -360,7 +370,10 @@ public class OnDeviceAdSelectionRunner extends AdSelectionRunner {
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}
-
+ sLogger.v(
+ "Invoking score generator with %s bids and %s contextual ads.",
+ validBiddingOutcomes.size(),
+ adSelectionConfig.getBuyerSignedContextualAds().size());
return mAdsScoreGenerator
.runAdScoring(validBiddingOutcomes, adSelectionConfig)
.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
index 190ebef740..b372378b9c 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/TrustedServerAdSelectionRunner.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/TrustedServerAdSelectionRunner.java
@@ -34,6 +34,8 @@ 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.data.encryptionkey.EncryptionKeyDao;
+import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AdRenderIdValidator;
import com.android.adservices.service.common.AdSelectionServiceFilter;
@@ -97,6 +99,8 @@ public class TrustedServerAdSelectionRunner extends AdSelectionRunner {
@NonNull final Context context,
@NonNull final CustomAudienceDao customAudienceDao,
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final EncryptionKeyDao encryptionKeyDao,
+ @NonNull final EnrollmentDao enrollmentDao,
@NonNull final AdServicesHttpsClient adServicesHttpsClient,
@NonNull final ExecutorService lightweightExecutorService,
@NonNull final ExecutorService backgroundExecutorService,
@@ -116,6 +120,8 @@ public class TrustedServerAdSelectionRunner extends AdSelectionRunner {
context,
customAudienceDao,
adSelectionEntryDao,
+ encryptionKeyDao,
+ enrollmentDao,
lightweightExecutorService,
backgroundExecutorService,
scheduledExecutor,
@@ -146,6 +152,8 @@ public class TrustedServerAdSelectionRunner extends AdSelectionRunner {
@NonNull final Context context,
@NonNull final CustomAudienceDao customAudienceDao,
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
+ @NonNull final EncryptionKeyDao encryptionKeyDao,
+ @NonNull final EnrollmentDao enrollmentDao,
@NonNull final ExecutorService lightweightExecutorService,
@NonNull final ExecutorService backgroundExecutorService,
@NonNull final ScheduledThreadPoolExecutor scheduledExecutor,
@@ -165,6 +173,8 @@ public class TrustedServerAdSelectionRunner extends AdSelectionRunner {
context,
customAudienceDao,
adSelectionEntryDao,
+ encryptionKeyDao,
+ enrollmentDao,
lightweightExecutorService,
backgroundExecutorService,
scheduledExecutor,
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/signature/ProtectedAudienceSignatureManager.java b/adservices/service-core/java/com/android/adservices/service/adselection/signature/ProtectedAudienceSignatureManager.java
index 63a3d405a9..ec5511f613 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/signature/ProtectedAudienceSignatureManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/signature/ProtectedAudienceSignatureManager.java
@@ -16,9 +16,9 @@
package com.android.adservices.service.adselection.signature;
+import android.adservices.adselection.SignedContextualAds;
import android.adservices.common.AdTechIdentifier;
import android.annotation.NonNull;
-import android.content.Context;
import com.android.adservices.LoggerFactory;
import com.android.adservices.data.encryptionkey.EncryptionKeyDao;
@@ -28,6 +28,7 @@ import com.android.adservices.service.enrollment.EnrollmentData;
import com.google.common.annotations.VisibleForTesting;
+import java.util.Base64;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -41,32 +42,96 @@ import java.util.stream.Collectors;
*/
public class ProtectedAudienceSignatureManager {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
- private final Context mContext;
- private final EnrollmentDao mEnrollmentDao;
- private final EncryptionKeyDao mEncryptionKeyDao;
+ /**
+ * This P-256 ECDSA key is used to verify signatures if {@link
+ * com.android.adservices.service.FlagsConstants #KEY_DISABLE_FLEDGE_ENROLLMENT_CHECK} is set to
+ * true.
+ *
+ * <p>This enables CTS and integration testing.
+ *
+ * <p>To test with this key, {@link SignedContextualAds} should be signed with {@link
+ * ProtectedAudienceSignatureManager#PRIVATE_TEST_KEY_STRING}.
+ */
+ public static final String PUBLIC_TEST_KEY_STRING =
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+Eyo0TOllW8as2pTTzxawQ57pXJiH16VERgHqcV1/YpADt3iq6"
+ + "9vbhwW8Ksi3M0GrxacOuge/AwiM7Uh6+V3PA==";
- public ProtectedAudienceSignatureManager(@NonNull Context context) {
- Objects.requireNonNull(context);
+ /**
+ * Private key pair of the {@link ProtectedAudienceSignatureManager#PUBLIC_TEST_KEY_STRING}
+ *
+ * <p>See {@link ProtectedAudienceSignatureManager#PUBLIC_TEST_KEY_STRING}
+ */
+ public static final String PRIVATE_TEST_KEY_STRING =
+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgECetqRr9eE9DKKjILR+hP66Y1niEw/bqPD/MNx"
+ + "PTMvmhRANCAAT4TKjRM6WVbxqzalNPPFrBDnulcmIfXpURGAepxXX9ikAO3eKrr29uHBbwqyLczQ"
+ + "avFpw66B78DCIztSHr5Xc8";
- mContext = context;
+ @NonNull private final EnrollmentDao mEnrollmentDao;
+ @NonNull private final EncryptionKeyDao mEncryptionKeyDao;
+ private final boolean mIsEnrollmentCheckEnabled;
- mEnrollmentDao = EnrollmentDao.getInstance(mContext);
- mEncryptionKeyDao = EncryptionKeyDao.getInstance(mContext);
+ private final SignatureVerifier mSignatureVerifier;
+
+ public ProtectedAudienceSignatureManager(
+ @NonNull EnrollmentDao enrollmentDao,
+ @NonNull EncryptionKeyDao encryptionKeyDao,
+ boolean isEnrollmentCheckEnabled) {
+ Objects.requireNonNull(enrollmentDao);
+ Objects.requireNonNull(encryptionKeyDao);
+
+ mEnrollmentDao = enrollmentDao;
+ mEncryptionKeyDao = encryptionKeyDao;
+ mIsEnrollmentCheckEnabled = isEnrollmentCheckEnabled;
+
+ mSignatureVerifier = new ECDSASignatureVerifier();
}
@VisibleForTesting
ProtectedAudienceSignatureManager(
- @NonNull Context context,
@NonNull EnrollmentDao enrollmentDao,
- @NonNull EncryptionKeyDao encryptionKeyDao) {
- mContext = context;
+ @NonNull EncryptionKeyDao encryptionKeyDao,
+ @NonNull SignatureVerifier signatureVerifier) {
mEnrollmentDao = enrollmentDao;
mEncryptionKeyDao = encryptionKeyDao;
+ mSignatureVerifier = signatureVerifier;
+
+ mIsEnrollmentCheckEnabled = true;
+ }
+
+ /**
+ * Returns whether is the given {@link SignedContextualAds} object is valid or not
+ *
+ * @param buyer Ad tech's identifier to resolve their public key
+ * @param signedContextualAds contextual ads object to verify
+ * @return true if the object is valid else false
+ */
+ public boolean isVerified(
+ @NonNull AdTechIdentifier buyer, @NonNull SignedContextualAds signedContextualAds) {
+ Objects.requireNonNull(buyer);
+ Objects.requireNonNull(signedContextualAds);
+
+ List<byte[]> publicKeys = fetchPublicKeyForAdTech(buyer);
+ boolean isVerified = false;
+ SignedContextualAdsHashUtil contextualAdsHashUtil;
+ for (byte[] publicKey : publicKeys) {
+ contextualAdsHashUtil = new SignedContextualAdsHashUtil();
+ byte[] serialized = contextualAdsHashUtil.serialize(signedContextualAds);
+ isVerified =
+ mSignatureVerifier.verify(
+ publicKey, serialized, signedContextualAds.getSignature());
+ }
+ return isVerified;
}
@VisibleForTesting
- List<String> fetchPublicKeyForAdTech(AdTechIdentifier adTech) {
+ List<byte[]> fetchPublicKeyForAdTech(AdTechIdentifier adTech) {
+ Base64.Decoder decoder = Base64.getDecoder();
+ if (!mIsEnrollmentCheckEnabled) {
+ sLogger.v("Enrollment check is disabled, returning the default key");
+ return Collections.singletonList(decoder.decode(PUBLIC_TEST_KEY_STRING));
+ }
+
sLogger.v("Fetching EnrollmentData for %s", adTech);
EnrollmentData enrollmentData =
mEnrollmentDao.getEnrollmentDataForFledgeByAdTechIdentifier(adTech);
@@ -81,10 +146,10 @@ public class ProtectedAudienceSignatureManager {
mEncryptionKeyDao.getEncryptionKeyFromEnrollmentIdAndKeyType(
enrollmentData.getEnrollmentId(), EncryptionKey.KeyType.SIGNING);
- sLogger.v("Received %s signing keys", encryptionKeys.size());
+ sLogger.v("Received %s signing key(s)", encryptionKeys.size());
return encryptionKeys.stream()
.sorted(Comparator.comparingLong(EncryptionKey::getExpiration))
- .map(EncryptionKey::getBody)
+ .map(key -> decoder.decode(key.getBody()))
.collect(Collectors.toList());
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/signature/ThreadUnsafeByteArrayOutputStream.java b/adservices/service-core/java/com/android/adservices/service/adselection/signature/ThreadUnsafeByteArrayOutputStream.java
index 4857768a62..a9918549b8 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/signature/ThreadUnsafeByteArrayOutputStream.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/signature/ThreadUnsafeByteArrayOutputStream.java
@@ -23,13 +23,13 @@ import java.util.Arrays;
/** ByteArrayStream implementation that reduces amount of copy operation */
public class ThreadUnsafeByteArrayOutputStream {
private static final int ONE_KILOBYTE = 1024;
- private static final int INITIAL_CAPACITY = 100 * ONE_KILOBYTE;
+ private static final int DEFAULT_INITIAL_CAPACITY = 100 * ONE_KILOBYTE;
private static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
private byte[] mBuffer;
private int mCount;
public ThreadUnsafeByteArrayOutputStream() {
- this.mBuffer = new byte[INITIAL_CAPACITY];
+ this.mBuffer = new byte[DEFAULT_INITIAL_CAPACITY];
this.mCount = 0;
}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentStorageManager.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentStorageManager.java
index cbd867c0fe..04c6f60300 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentStorageManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentStorageManager.java
@@ -412,8 +412,7 @@ public class AppSearchConsentStorageManager implements IConsentStorage {
/** Saves the default consent by apiType. */
@Override
- public void recordDefaultConsent(AdServicesApiType apiType, boolean defaultConsent)
- throws IOException {
+ public void recordDefaultConsent(AdServicesApiType apiType, boolean defaultConsent) {
mAppSearchConsentWorker.setConsent(apiType.toDefaultConsentDatastoreKey(), defaultConsent);
}
@@ -436,8 +435,7 @@ public class AppSearchConsentStorageManager implements IConsentStorage {
}
/** Saves information to the storage that user interacted with consent manually. */
- public void recordUserManualInteractionWithConsent(
- @ConsentManager.UserManualInteraction int interaction) {
+ public void recordUserManualInteractionWithConsent(int interaction) {
mAppSearchConsentWorker.recordUserManualInteractionWithConsent(interaction);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java
index 42c5c1ed8b..bda9d17442 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java
@@ -65,15 +65,11 @@ import java.util.stream.Collectors;
* source of truth for S-. When a device upgrades from S- to T+, the consent is initialized from
* AppSearch.
*/
-// TODO(b/269798827): Enable for R.
@RequiresApi(Build.VERSION_CODES.S)
class AppSearchConsentWorker {
// At the worker level, we ensure that writes do not conflict with any other writes/reads.
private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock();
- // Timeout for AppSearch write query in milliseconds.
- private static final int TIMEOUT_MS = 2000;
-
private static final String CONSENT_DATABASE_NAME = "adservices_consent";
private static final String APP_CONSENT_DATABASE_NAME = "adservices_app_consent";
private static final String NOTIFICATION_DATABASE_NAME = "adservices_notification";
@@ -82,68 +78,69 @@ class AppSearchConsentWorker {
private static final String UX_STATES_DATABASE_NAME = "adservices-ux-states";
// Required for allowing AdServices apk access to read consent written by ExtServices module.
- private String mAdservicesPackageName;
- private Context mContext;
+ private final String mAdservicesPackageName;
- private ListenableFuture<AppSearchSession> mConsentSearchSession;
- private ListenableFuture<AppSearchSession> mAppConsentSearchSession;
- private ListenableFuture<AppSearchSession> mNotificationSearchSession;
- private ListenableFuture<AppSearchSession> mInteractionsSearchSession;
- private ListenableFuture<AppSearchSession> mTopicsSearchSession;
- private ListenableFuture<AppSearchSession> mUxStatesSearchSession;
+ // Timeout for AppSearch write query in milliseconds.
+ private final int mTimeoutMs;
+
+ private final ListenableFuture<AppSearchSession> mConsentSearchSession;
+ private final ListenableFuture<AppSearchSession> mAppConsentSearchSession;
+ private final ListenableFuture<AppSearchSession> mNotificationSearchSession;
+ private final ListenableFuture<AppSearchSession> mInteractionsSearchSession;
+ private final ListenableFuture<AppSearchSession> mTopicsSearchSession;
+ private final ListenableFuture<AppSearchSession> mUxStatesSearchSession;
// When reading across APKs, a GlobalSearchSession is needed, hence we use it when reading.
- private ListenableFuture<GlobalSearchSession> mGlobalSearchSession;
- private Executor mExecutor = AdServicesExecutors.getBackgroundExecutor();
+ private final ListenableFuture<GlobalSearchSession> mGlobalSearchSession;
+ private final Executor mExecutor = AdServicesExecutors.getBackgroundExecutor();
- private List<PackageIdentifier> mPackageIdentifiers = new ArrayList<>();
+ private final List<PackageIdentifier> mPackageIdentifiers = new ArrayList<>();
// There is a single user ID for a given process, so this class would not be instantiated
// across two user IDs.
- private String mUid = getUserIdentifierFromBinderCallingUid();
+ private final String mUid = getUserIdentifierFromBinderCallingUid();
private static final String SPLITTER = ",";
private AppSearchConsentWorker(@NonNull Context context) {
Objects.requireNonNull(context);
- mContext = context;
// We write with multiple schemas, so we need to initialize sessions per db.
mConsentSearchSession =
PlatformStorage.createSearchSessionAsync(
- new PlatformStorage.SearchContext.Builder(mContext, CONSENT_DATABASE_NAME)
+ new PlatformStorage.SearchContext.Builder(context, CONSENT_DATABASE_NAME)
.build());
mAppConsentSearchSession =
PlatformStorage.createSearchSessionAsync(
new PlatformStorage.SearchContext.Builder(
- mContext, APP_CONSENT_DATABASE_NAME)
+ context, APP_CONSENT_DATABASE_NAME)
.build());
mNotificationSearchSession =
PlatformStorage.createSearchSessionAsync(
new PlatformStorage.SearchContext.Builder(
- mContext, NOTIFICATION_DATABASE_NAME)
+ context, NOTIFICATION_DATABASE_NAME)
.build());
mInteractionsSearchSession =
PlatformStorage.createSearchSessionAsync(
new PlatformStorage.SearchContext.Builder(
- mContext, INTERACTIONS_DATABASE_NAME)
+ context, INTERACTIONS_DATABASE_NAME)
.build());
mTopicsSearchSession =
PlatformStorage.createSearchSessionAsync(
- new PlatformStorage.SearchContext.Builder(mContext, TOPICS_DATABASE_NAME)
+ new PlatformStorage.SearchContext.Builder(context, TOPICS_DATABASE_NAME)
.build());
mUxStatesSearchSession =
PlatformStorage.createSearchSessionAsync(
- new PlatformStorage.SearchContext.Builder(mContext, UX_STATES_DATABASE_NAME)
+ new PlatformStorage.SearchContext.Builder(context, UX_STATES_DATABASE_NAME)
.build());
// We use global session for reads since we may perform read on T+ AdServices package to
// restore consent data post OTA.
mGlobalSearchSession =
PlatformStorage.createGlobalSearchSessionAsync(
- new PlatformStorage.GlobalSearchContext.Builder(mContext).build());
+ new PlatformStorage.GlobalSearchContext.Builder(context).build());
// The package identifier of the AdServices package on T+ should always have access to read
// data written by AdExtServices package on S-.
- mAdservicesPackageName = getAdServicesPackageName(mContext);
+ mAdservicesPackageName = getAdServicesPackageName(context);
String shaCertsFlagValue = FlagsFactory.getFlags().getAdservicesApkShaCertificate();
for (String shaCert : shaCertsFlagValue.split(SPLITTER)) {
@@ -151,6 +148,8 @@ class AppSearchConsentWorker {
new PackageIdentifier(
mAdservicesPackageName, new Signature(shaCert).toByteArray()));
}
+
+ mTimeoutMs = FlagsFactory.getFlags().getAppSearchWriteTimeout();
}
/** Get an instance of AppSearchConsentWorker. */
@@ -175,8 +174,8 @@ class AppSearchConsentWorker {
/**
* Sets the consent for this user ID for this API type in AppSearch. If we do not get
- * confirmation that the write was successful, then we throw an exception so that user does not
- * incorrectly think that the consent is updated.
+ * confirmation that the write operation was successful, then we throw an exception so that user
+ * does not incorrectly think that the consent is updated.
*/
void setConsent(@NonNull String apiType, @NonNull Boolean consented) {
Objects.requireNonNull(apiType);
@@ -193,11 +192,11 @@ class AppSearchConsentWorker {
apiType,
consented.toString());
dao.writeData(mConsentSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote consent data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -235,10 +234,10 @@ class AppSearchConsentWorker {
mExecutor,
AppSearchAppConsentDao.getRowId(mUid, consentType),
AppSearchAppConsentDao.NAMESPACE)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to delete consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to delete consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -280,11 +279,11 @@ class AppSearchConsentWorker {
dao.setApps(apps);
}
dao.writeData(mAppConsentSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote app consent data to AppSearch (add): " + dao);
return true;
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
return false;
} finally {
READ_WRITE_LOCK.writeLock().unlock();
@@ -293,8 +292,8 @@ class AppSearchConsentWorker {
/**
* Removes an app from the list of apps with this consentType for this user. If we do not get
- * confirmation that the write was successful, then we throw an exception so that user does not
- * incorrectly think that the consent is updated.
+ * confirmation that the write operation was successful, then we throw an exception so that user
+ * does not incorrectly think that the consent is updated.
*/
void removeAppWithConsent(@NonNull String consentType, @NonNull String app) {
Objects.requireNonNull(consentType);
@@ -320,11 +319,11 @@ class AppSearchConsentWorker {
.filter(filterApp -> !filterApp.equals(app))
.collect(Collectors.toList()));
dao.writeData(mAppConsentSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote app consent data to AppSearch (remove): " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.readLock().unlock();
}
@@ -367,11 +366,11 @@ class AppSearchConsentWorker {
/* wasNotificationDisplayed= */ wasNotificationDisplayed,
/* wasGaUxNotificationDisplayed= */ wasGaUxNotificationDisplayed());
dao.writeData(mNotificationSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote notification data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write notification data to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write notification data to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -389,11 +388,11 @@ class AppSearchConsentWorker {
/* wasNotificationDisplayed= */ wasNotificationDisplayed(),
/* wasGaUxNotificationDisplayed= */ wasNotificationDisplayed);
dao.writeData(mNotificationSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote notification data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write notification data to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write notification data to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -426,11 +425,11 @@ class AppSearchConsentWorker {
apiType,
currentFeatureType.ordinal());
dao.writeData(mInteractionsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote feature type data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write interactions data to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write interactions data to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -466,11 +465,11 @@ class AppSearchConsentWorker {
apiType,
interaction);
dao.writeData(mInteractionsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote interactions data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write interactions data to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write interactions data to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -512,11 +511,11 @@ class AppSearchConsentWorker {
dao.addBlockedTopic(topic);
}
dao.writeData(mTopicsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote topics consent data to AppSearch (block): " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -539,11 +538,11 @@ class AppSearchConsentWorker {
}
dao.removeBlockedTopic(topic);
dao.writeData(mTopicsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote topics consent data to AppSearch (unblock): " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -554,7 +553,7 @@ class AppSearchConsentWorker {
READ_WRITE_LOCK.writeLock().lock();
try {
// We don't do {read, modify, write} here since the DAO has no other information besides
- // blocked topics so we can rewrite it.
+ // blocked topics, so we can rewrite it.
AppSearchTopicsConsentDao dao =
new AppSearchTopicsConsentDao(
mUid,
@@ -564,11 +563,11 @@ class AppSearchConsentWorker {
List.of(),
List.of());
dao.writeData(mTopicsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote topics consent data to AppSearch (clear): " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -606,7 +605,7 @@ class AppSearchConsentWorker {
AdServicesCommon.ADEXTSERVICES_PACKAGE_NAME_SUFFIX,
AdServicesCommon.ADSERVICES_APK_PACKAGE_NAME_SUFFIX);
}
- // If we don't know the AdServices package name, we can't do a write.
+ // If we don't know the AdServices package name, we can't write.
throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
}
@@ -637,11 +636,11 @@ class AppSearchConsentWorker {
}
dao.setAdIdEnabled(isAdIdEnabled);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the isAdIdEnabled bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the isAdIdEnabled to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the isAdIdEnabled to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -674,11 +673,11 @@ class AppSearchConsentWorker {
}
dao.setU18Account(isU18Account);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the isU18Account bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the isU18Account to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the isU18Account to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -711,11 +710,11 @@ class AppSearchConsentWorker {
}
dao.setEntryPointEnabled(isEntryPointEnabled);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the isEntryPointEnabled bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the isEntryPointEnabled to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the isEntryPointEnabled to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -748,11 +747,11 @@ class AppSearchConsentWorker {
}
dao.setAdultAccount(isAdultAccount);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the isAdultAccount bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the isAdultAccount to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the isAdultAccount to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -785,11 +784,11 @@ class AppSearchConsentWorker {
}
dao.setU18NotificationDisplayed(wasU18NotificationDisplayed);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the wasU18NotificationDisplayed bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the wasU18NotificationDisplayed to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the wasU18NotificationDisplayed to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -822,11 +821,11 @@ class AppSearchConsentWorker {
}
dao.setUx(ux.toString());
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote PrivacySandboxUx to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write PrivacySandboxUx to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write PrivacySandboxUx to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -865,11 +864,11 @@ class AppSearchConsentWorker {
}
dao.setEnrollmentChannel(enrollmentChannel.toString());
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote PrivacySandboxUx to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write PrivacySandboxUx to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write PrivacySandboxUx to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
index 4fdf36f675..cd52dbd72b 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.appsearch;
+import static com.android.adservices.service.consent.ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE;
+
import android.annotation.NonNull;
import android.os.Build;
@@ -31,6 +33,7 @@ import androidx.appsearch.app.RemoveByDocumentIdRequest;
import androidx.appsearch.app.SearchResults;
import androidx.appsearch.app.SearchSpec;
import androidx.appsearch.app.SetSchemaRequest;
+import androidx.appsearch.app.SetSchemaResponse.MigrationFailure;
import androidx.appsearch.exceptions.AppSearchException;
import com.android.adservices.AdServicesCommon;
@@ -38,7 +41,6 @@ import com.android.adservices.LogUtil;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.AllowLists;
-import com.android.adservices.service.consent.ConsentConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.google.common.util.concurrent.FluentFuture;
@@ -58,12 +60,8 @@ import java.util.function.BiFunction;
* Base class for all data access objects for AppSearch. This class handles the common logic for
* reading from and writing to AppSearch.
*/
-// TODO(b/269798827): Enable for R.
@RequiresApi(Build.VERSION_CODES.S)
class AppSearchDao {
- // Timeout for AppSearch search query in milliseconds.
- private static final int TIMEOUT_MS = 500;
-
/**
* Iterate over the search results returned for the search query by AppSearch.
*
@@ -88,7 +86,7 @@ class AppSearchDao {
// Converts GenericDocument object to the type of object passed in cls.
documentResult = genericDocument.toDocumentClass(cls);
} catch (AppSearchException e) {
- LogUtil.e("Failed to convert GenericDocument to " + cls.getName(), e);
+ LogUtil.e(e, "Failed to convert GenericDocument to %s", cls.getName());
}
}
@@ -203,9 +201,11 @@ class AppSearchDao {
results -> iterateSearchResults(cls, results, executor),
executor)
.transform(result -> ((T) result), executor);
- return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ int timeout = FlagsFactory.getFlags().getAppSearchReadTimeout();
+ return future.get(timeout, TimeUnit.MILLISECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
- LogUtil.e("getConsent() Appsearch lookup failed with: ", e);
+ LogUtil.e(e, "getConsent() AppSearch lookup failed");
}
return null;
}
@@ -216,7 +216,7 @@ class AppSearchDao {
* we specify the packageIdentifier as that of the T+ AdServices APK, which after OTA, needs
* access to the data written before OTA. What is written is the subclass type of DAO.
*
- * @return the result of the write.
+ * @return the result of the write operation.
*/
FluentFuture<AppSearchBatchResult<String, Void>> writeData(
@NonNull ListenableFuture<AppSearchSession> appSearchSession,
@@ -245,14 +245,17 @@ class AppSearchDao {
// If we get failures in schemaResponse then we cannot try
// to write.
if (!setSchemaResponse.getMigrationFailures().isEmpty()) {
+ MigrationFailure failure =
+ setSchemaResponse.getMigrationFailures().get(0);
LogUtil.e(
"SetSchemaResponse migration failure: "
- + setSchemaResponse
- .getMigrationFailures()
- .get(0));
- throw new RuntimeException(
- ConsentConstants
- .ERROR_MESSAGE_APPSEARCH_FAILURE);
+ + failure);
+ String message =
+ String.format(
+ "%s Migration failure: %s",
+ ERROR_MESSAGE_APPSEARCH_FAILURE,
+ failure.getAppSearchResult());
+ throw new RuntimeException(message);
}
// The database knows about this schemaType and write can
// occur.
@@ -264,17 +267,17 @@ class AppSearchDao {
executor);
return putFuture;
} catch (AppSearchException e) {
- LogUtil.e("Cannot instantiate AppSearch database: " + e.getMessage());
+ LogUtil.e(e, "Cannot instantiate AppSearch database");
+ return FluentFuture.from(
+ Futures.immediateFailedFuture(
+ new RuntimeException(ERROR_MESSAGE_APPSEARCH_FAILURE, e)));
}
- return FluentFuture.from(
- Futures.immediateFailedFuture(
- new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE)));
}
/**
* Delete a row from the database.
*
- * @return the result of the delete.
+ * @return the result of the delete operation.
*/
protected static <T> FluentFuture<AppSearchBatchResult<String, Void>> deleteData(
@NonNull Class<T> cls,
@@ -308,8 +311,7 @@ class AppSearchDao {
.getMigrationFailures()
.get(0));
throw new RuntimeException(
- ConsentConstants
- .ERROR_MESSAGE_APPSEARCH_FAILURE);
+ ERROR_MESSAGE_APPSEARCH_FAILURE);
}
// The database knows about this schemaType and write can
// occur.
@@ -325,6 +327,6 @@ class AppSearchDao {
}
return FluentFuture.from(
Futures.immediateFailedFuture(
- new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE)));
+ new RuntimeException(ERROR_MESSAGE_APPSEARCH_FAILURE)));
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java
index f81566cc2c..b282cfefa1 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java
@@ -28,6 +28,7 @@ import androidx.appsearch.platformstorage.PlatformStorage;
import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.compat.FileCompatUtils;
import com.android.adservices.service.consent.ConsentConstants;
import com.android.adservices.service.measurement.rollback.MeasurementRollbackWorker;
@@ -59,7 +60,7 @@ public final class AppSearchMeasurementRollbackWorker implements MeasurementRoll
private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock();
// Timeout for AppSearch write query in milliseconds.
- private static final int TIMEOUT_MS = 2000;
+ private final int mTimeoutMs;
private final String mUserId;
private final String mAdServicesPackageName;
@@ -75,6 +76,8 @@ public final class AppSearchMeasurementRollbackWorker implements MeasurementRoll
mSearchSession =
PlatformStorage.createSearchSessionAsync(
new PlatformStorage.SearchContext.Builder(context, DATABASE_NAME).build());
+
+ mTimeoutMs = FlagsFactory.getFlags().getAppSearchWriteTimeout();
}
/** Return an instance of {@link AppSearchMeasurementRollbackWorker} */
@@ -98,7 +101,7 @@ public final class AppSearchMeasurementRollbackWorker implements MeasurementRoll
// don't need to share it with the T package. Thus, we can send an empty list for the
// packageIdentifiers parameter.
dao.writeData(mSearchSession, List.of(), mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote measurement rollback data to AppSearch: %s", dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
LogUtil.e(e, "Failed to write measurement rollback to AppSearch");
@@ -130,7 +133,7 @@ public final class AppSearchMeasurementRollbackWorker implements MeasurementRoll
mExecutor,
storageIdentifier,
AppSearchMeasurementRollbackDao.NAMESPACE)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Deleted MeasurementRollback data from AppSearch for: %s", storageIdentifier);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
LogUtil.e(e, "Failed to delete MeasurementRollback data in AppSearch");
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 bf002effdc..ecb1e11151 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
@@ -58,6 +58,7 @@ import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
+import android.os.Trace;
import androidx.annotation.RequiresApi;
@@ -285,7 +286,9 @@ public class AdServicesCommonServiceImpl extends IAdServicesCommonService.Stub {
@NonNull IEnableAdServicesCallback callback) {
LogUtil.d(ENABLE_AD_SERVICES_API_CALLED_MESSAGE);
+ Trace.beginSection("AdServicesCommonService#EnableAdServices_PermissionCheck");
boolean authorizedCaller = PermissionHelper.hasModifyAdServicesStatePermission(mContext);
+ Trace.endSection();
sBackgroundExecutor.execute(
() -> {
@@ -308,7 +311,10 @@ public class AdServicesCommonServiceImpl extends IAdServicesCommonService.Stub {
return;
}
+ Trace.beginSection("AdServicesCommonService#EnableAdServices_UxEngineFlow");
mUxEngine.start(adServicesStates);
+ Trace.endSection();
+
LogUtil.d("enableAdServices(): UxEngine started.");
callback.onResult(
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfig.java b/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfig.java
index 5db3b171ea..672e443db4 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfig.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfig.java
@@ -16,6 +16,11 @@
package com.android.adservices.service.common;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_ALLOWED_APP_ALLOWS_ALL;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_ALLOWED_APP_ALLOWS_SPECIFIC_ID;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_ALLOWED_BY_DEFAULT_APP_HAS_CONFIG_WITHOUT_API_SECTION;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_DISALLOWED_APP_HAS_CONFIG_WITHOUT_API_SECTION;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_DISALLOWED_BY_APP;
import static com.android.adservices.service.common.AppManifestConfigParser.TAG_ADID;
import static com.android.adservices.service.common.AppManifestConfigParser.TAG_APPSETID;
import static com.android.adservices.service.common.AppManifestConfigParser.TAG_ATTRIBUTION;
@@ -26,6 +31,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.adservices.LogUtil;
+import com.android.adservices.service.common.AppManifestConfigCall.Result;
import java.util.function.Supplier;
@@ -96,9 +102,9 @@ public final class AppManifestConfig {
* Returns if the ad partner is permitted to access Attribution API for config represented by
* this object.
*
- * <p>If the tag is not found in the app manifest config, returns {@code false}.
+ * <p>See constants in {@link AppManifestConfigCall} for the returned value.
*/
- public boolean isAllowedAttributionAccess(@NonNull String enrollmentId) {
+ public @Result int isAllowedAttributionAccess(@NonNull String enrollmentId) {
return isAllowedAccess(TAG_ATTRIBUTION, mAttributionConfig, enrollmentId);
}
@@ -119,9 +125,9 @@ public final class AppManifestConfig {
* Returns {@code true} if an ad tech with the given enrollment ID is permitted to access Custom
* Audience API for config represented by this object.
*
- * <p>If the tag is not found in the app manifest config, returns {@code false}.
+ * <p>See constants in {@link AppManifestConfigCall} for the returned value.
*/
- public boolean isAllowedCustomAudiencesAccess(@NonNull String enrollmentId) {
+ public @Result int isAllowedCustomAudiencesAccess(@NonNull String enrollmentId) {
return isAllowedAccess(TAG_CUSTOM_AUDIENCES, mCustomAudiencesConfig, enrollmentId);
}
@@ -140,9 +146,9 @@ public final class AppManifestConfig {
* Returns if the ad partner is permitted to access Topics API for config represented by this
* object.
*
- * <p>If the tag is not found in the app manifest config, returns {@code false}.
+ * <p>See constants in {@link AppManifestConfigCall} for the returned value.
*/
- public boolean isAllowedTopicsAccess(@NonNull String enrollmentId) {
+ public @Result int isAllowedTopicsAccess(@NonNull String enrollmentId) {
return isAllowedAccess(TAG_TOPICS, mTopicsConfig, enrollmentId);
}
@@ -159,9 +165,9 @@ public final class AppManifestConfig {
/**
* Returns if sdk is permitted to access AdId API for config represented by this object.
*
- * <p>If the tag is not found in the app manifest config, returns {@code false}.
+ * <p>See constants in {@link AppManifestConfigCall} for the returned value.
*/
- public boolean isAllowedAdIdAccess(@NonNull String sdk) {
+ public @Result int isAllowedAdIdAccess(@NonNull String sdk) {
return isAllowedAccess(TAG_ADID, mAdIdConfig, sdk);
}
@@ -181,9 +187,9 @@ public final class AppManifestConfig {
/**
* Returns if sdk is permitted to access AppSetId API for config represented by this object.
*
- * <p>If the tag is not found in the app manifest config, returns {@code false}.
+ * <p>See constants in {@link AppManifestConfigCall} for the returned value.
*/
- public boolean isAllowedAppSetIdAccess(@NonNull String sdk) {
+ public @Result int isAllowedAppSetIdAccess(@NonNull String sdk) {
return isAllowedAccess(TAG_APPSETID, mAppSetIdConfig, sdk);
}
@@ -200,15 +206,21 @@ public final class AppManifestConfig {
return null;
}
- private boolean isAllowedAccess(
+ private @Result int isAllowedAccess(
String tag, @Nullable AppManifestApiConfig config, String partnerId) {
if (config == null) {
LogUtil.v(
"app manifest config tag '%s' not found, returning %b", tag, mEnabledByDefault);
- return mEnabledByDefault;
+ return mEnabledByDefault
+ ? RESULT_ALLOWED_BY_DEFAULT_APP_HAS_CONFIG_WITHOUT_API_SECTION
+ : RESULT_DISALLOWED_APP_HAS_CONFIG_WITHOUT_API_SECTION;
}
-
- return config.getAllowAllToAccess()
- || config.getAllowAdPartnersToAccess().contains(partnerId);
+ if (config.getAllowAllToAccess()) {
+ return RESULT_ALLOWED_APP_ALLOWS_ALL;
+ }
+ if (config.getAllowAdPartnersToAccess().contains(partnerId)) {
+ return RESULT_ALLOWED_APP_ALLOWS_SPECIFIC_ID;
+ }
+ return RESULT_DISALLOWED_BY_APP;
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigCall.java b/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigCall.java
index 449a159c35..79c93bc55c 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigCall.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigCall.java
@@ -15,33 +15,162 @@
*/
package com.android.adservices.service.common;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
import java.util.Objects;
// TODO(b/310270746): make it package-protected when TopicsServiceImplTest is refactored
/** Represents a call to a public {@link AppManifestConfigHelper} method. */
public final class AppManifestConfigCall {
+
+ // TODO(b/306417555): use values from statsd atom for constants below
+ static final int API_UNSPECIFIED = 0;
+ static final int API_TOPICS = 1;
+ static final int API_CUSTOM_AUDIENCES = 2;
+ static final int API_ATTRIBUTION = 3;
+
+ @IntDef({API_TOPICS, API_CUSTOM_AUDIENCES, API_ATTRIBUTION})
+ @Retention(SOURCE)
+ public @interface ApiType {}
+
+ // TODO(b/306417555): use values from statsd atom for constants below
+ static final int RESULT_UNSPECIFIED = 0;
+ static final int RESULT_ALLOWED_BY_DEFAULT_APP_DOES_NOT_HAVE_CONFIG = 1;
+ static final int RESULT_ALLOWED_BY_DEFAULT_APP_HAS_CONFIG_WITHOUT_API_SECTION = 2;
+ static final int RESULT_ALLOWED_APP_ALLOWS_ALL = 3;
+ static final int RESULT_ALLOWED_APP_ALLOWS_SPECIFIC_ID = 4;
+ static final int RESULT_DISALLOWED_APP_DOES_NOT_EXIST = 5;
+ static final int RESULT_DISALLOWED_APP_CONFIG_PARSING_ERROR = 6;
+ static final int RESULT_DISALLOWED_APP_DOES_NOT_HAVE_CONFIG = 7;
+ static final int RESULT_DISALLOWED_APP_HAS_CONFIG_WITHOUT_API_SECTION = 8;
+ static final int RESULT_DISALLOWED_BY_APP = 9;
+ static final int RESULT_DISALLOWED_GENERIC_ERROR = 10;
+
+ @IntDef({
+ RESULT_ALLOWED_BY_DEFAULT_APP_DOES_NOT_HAVE_CONFIG,
+ RESULT_ALLOWED_BY_DEFAULT_APP_HAS_CONFIG_WITHOUT_API_SECTION,
+ RESULT_ALLOWED_APP_ALLOWS_ALL,
+ RESULT_ALLOWED_APP_ALLOWS_SPECIFIC_ID,
+ RESULT_DISALLOWED_APP_DOES_NOT_EXIST,
+ RESULT_DISALLOWED_APP_CONFIG_PARSING_ERROR,
+ RESULT_DISALLOWED_APP_DOES_NOT_HAVE_CONFIG,
+ RESULT_DISALLOWED_APP_HAS_CONFIG_WITHOUT_API_SECTION,
+ RESULT_DISALLOWED_BY_APP,
+ RESULT_DISALLOWED_GENERIC_ERROR
+ })
+ @Retention(SOURCE)
+ public @interface Result {}
+
+ @VisibleForTesting static final String INVALID_API_TEMPLATE = "Invalid API: %d";
+
public final String packageName;
- public boolean appExists;
- public boolean appHasConfig;
- public boolean enabledByDefault;
- public boolean result;
+ public final @ApiType int api;
+ public @Result int result;
- public AppManifestConfigCall(String packageName) {
+ public AppManifestConfigCall(String packageName, @ApiType int api) {
+ switch (api) {
+ case API_TOPICS:
+ case API_CUSTOM_AUDIENCES:
+ case API_ATTRIBUTION:
+ this.api = api;
+ break;
+ default:
+ throw new IllegalArgumentException(String.format(INVALID_API_TEMPLATE, api));
+ }
this.packageName = Objects.requireNonNull(packageName, "packageName cannot be null");
}
@Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AppManifestConfigCall other = (AppManifestConfigCall) obj;
+ return api == other.api
+ && Objects.equals(packageName, other.packageName)
+ && result == other.result;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(api, packageName, result);
+ }
+
+ @Override
public String toString() {
return "AppManifestConfigCall[pkg="
+ packageName
- + ", appExists="
- + appExists
- + ", appHasConfig="
- + appHasConfig
- + ", enabledByDefault="
- + enabledByDefault
+ + ", api="
+ + apiToString(api)
+ ", result="
- + result
+ + resultToString(result)
+ "]";
}
+
+ static String resultToString(@Result int result) {
+ switch (result) {
+ case RESULT_UNSPECIFIED:
+ return "UNSPECIFIED";
+ case RESULT_ALLOWED_BY_DEFAULT_APP_DOES_NOT_HAVE_CONFIG:
+ return "ALLOWED_BY_DEFAULT_APP_DOES_NOT_HAVE_CONFIG";
+ case RESULT_ALLOWED_BY_DEFAULT_APP_HAS_CONFIG_WITHOUT_API_SECTION:
+ return "ALLOWED_BY_DEFAULT_APP_HAS_CONFIG_WITHOUT_API_SECTION";
+ case RESULT_ALLOWED_APP_ALLOWS_ALL:
+ return "ALLOWED_APP_ALLOWS_ALL";
+ case RESULT_ALLOWED_APP_ALLOWS_SPECIFIC_ID:
+ return "ALLOWED_APP_ALLOWS_SPECIFIC_ID";
+ case RESULT_DISALLOWED_APP_DOES_NOT_EXIST:
+ return "DISALLOWED_APP_DOES_NOT_EXIST";
+ case RESULT_DISALLOWED_APP_CONFIG_PARSING_ERROR:
+ return "DISALLOWED_APP_CONFIG_PARSING_ERROR";
+ case RESULT_DISALLOWED_APP_DOES_NOT_HAVE_CONFIG:
+ return "DISALLOWED_APP_DOES_NOT_HAVE_CONFIG";
+ case RESULT_DISALLOWED_APP_HAS_CONFIG_WITHOUT_API_SECTION:
+ return "DISALLOWED_APP_HAS_CONFIG_WITHOUT_API_SECTION";
+ case RESULT_DISALLOWED_BY_APP:
+ return "DISALLOWED_BY_APP";
+ case RESULT_DISALLOWED_GENERIC_ERROR:
+ return "DISALLOWED_GENERIC_ERROR";
+ default:
+ return "INVALID-" + result;
+ }
+ }
+
+ static String apiToString(@ApiType int result) {
+ switch (result) {
+ case API_UNSPECIFIED:
+ return "UNSPECIFIED";
+ case API_TOPICS:
+ return "TOPICS";
+ case API_CUSTOM_AUDIENCES:
+ return "CUSTOM_AUDIENCES";
+ case API_ATTRIBUTION:
+ return "ATTRIBUTION";
+ default:
+ return "INVALID-" + result;
+ }
+ }
+
+ static boolean isAllowed(@Result int result) {
+ switch (result) {
+ case RESULT_ALLOWED_BY_DEFAULT_APP_DOES_NOT_HAVE_CONFIG:
+ case RESULT_ALLOWED_BY_DEFAULT_APP_HAS_CONFIG_WITHOUT_API_SECTION:
+ case RESULT_ALLOWED_APP_ALLOWS_ALL:
+ case RESULT_ALLOWED_APP_ALLOWS_SPECIFIC_ID:
+ return true;
+ default:
+ return false;
+ }
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigHelper.java b/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigHelper.java
index 0fe09ff8dc..b549717eee 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigHelper.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigHelper.java
@@ -16,6 +16,18 @@
package com.android.adservices.service.common;
+import static com.android.adservices.service.common.AppManifestConfigCall.API_ATTRIBUTION;
+import static com.android.adservices.service.common.AppManifestConfigCall.API_CUSTOM_AUDIENCES;
+import static com.android.adservices.service.common.AppManifestConfigCall.API_TOPICS;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_ALLOWED_APP_ALLOWS_SPECIFIC_ID;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_ALLOWED_BY_DEFAULT_APP_DOES_NOT_HAVE_CONFIG;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_DISALLOWED_APP_CONFIG_PARSING_ERROR;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_DISALLOWED_APP_DOES_NOT_EXIST;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_DISALLOWED_APP_DOES_NOT_HAVE_CONFIG;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_DISALLOWED_BY_APP;
+import static com.android.adservices.service.common.AppManifestConfigCall.isAllowed;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_MANIFEST_CONFIG_PARSING_ERROR;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_MANIFEST_CONFIG_PARSING_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON;
@@ -62,6 +74,7 @@ public class AppManifestConfigHelper {
@NonNull String enrollmentId) {
return isAllowedApiAccess(
"isAllowedAttributionAccess()",
+ API_ATTRIBUTION,
appPackageName,
enrollmentId,
config -> config.isAllowedAttributionAccess(enrollmentId));
@@ -80,6 +93,7 @@ public class AppManifestConfigHelper {
@NonNull String enrollmentId) {
return isAllowedApiAccess(
"isAllowedCustomAudiencesAccess()",
+ API_CUSTOM_AUDIENCES,
appPackageName,
enrollmentId,
config -> config.isAllowedCustomAudiencesAccess(enrollmentId));
@@ -101,14 +115,17 @@ public class AppManifestConfigHelper {
@NonNull String enrollmentId) {
return isAllowedApiAccess(
"isAllowedTopicsAccess()",
+ API_TOPICS,
appPackageName,
enrollmentId,
config -> {
// If the request comes directly from the app, check that the app has declared
// that it includes this Sdk library.
if (!useSandboxCheck) {
- return config.getIncludesSdkLibraryConfig().contains(enrollmentId)
- && config.isAllowedTopicsAccess(enrollmentId);
+ return (config.getIncludesSdkLibraryConfig().contains(enrollmentId)
+ && isAllowed(config.isAllowedTopicsAccess(enrollmentId)))
+ ? RESULT_ALLOWED_APP_ALLOWS_SPECIFIC_ID
+ : RESULT_DISALLOWED_BY_APP;
}
// If the request comes from the SdkRuntime, then the app had to have declared
@@ -118,29 +135,22 @@ public class AppManifestConfigHelper {
}
@Nullable
- private static XmlResourceParser getXmlParser(AppManifestConfigCall call)
+ private static XmlResourceParser getXmlParser(String appPackageName)
throws NameNotFoundException, XmlParseException, XmlPullParserException, IOException {
Context context = ApplicationContextSingleton.get();
- String appPackageName = call.packageName;
- LogUtil.v("getXmlParser(%s): context=%s", call.packageName, context);
+ LogUtil.v("getXmlParser(%s): context=%s", appPackageName, context);
+
// NOTE: resources is only used pre-S, but it must be called regardless to make sure the app
// exists
Resources resources =
context.getPackageManager().getResourcesForApplication(appPackageName);
- call.appExists = true;
-
Integer resId =
SdkLevel.isAtLeastS()
? getAdServicesConfigResourceIdOnExistingPackageOnSPlus(
context, appPackageName)
: getAdServicesConfigResourceIdOnRMinus(context, resources, appPackageName);
- XmlResourceParser xmlResourceParser = null;
- if (resId != null) {
- xmlResourceParser = resources.getXml(resId);
- call.appHasConfig = true;
- }
- return xmlResourceParser;
+ return resId != null ? resources.getXml(resId) : null;
}
@Nullable
@@ -171,28 +181,37 @@ public class AppManifestConfigHelper {
private static boolean isAllowedApiAccess(
String method,
+ int api,
String appPackageName,
String enrollmentId,
ApiAccessChecker checker) {
Objects.requireNonNull(appPackageName);
Objects.requireNonNull(enrollmentId);
- AppManifestConfigCall call = new AppManifestConfigCall(appPackageName);
- call.enabledByDefault = FlagsFactory.getFlags().getAppConfigReturnsEnabledByDefault();
+
+ AppManifestConfigCall call = new AppManifestConfigCall(appPackageName, api);
+ boolean enabledByDefault = FlagsFactory.getFlags().getAppConfigReturnsEnabledByDefault();
+
try {
- XmlResourceParser in = getXmlParser(call);
+ XmlResourceParser in = getXmlParser(appPackageName);
if (in == null) {
+ call.result =
+ enabledByDefault
+ ? RESULT_ALLOWED_BY_DEFAULT_APP_DOES_NOT_HAVE_CONFIG
+ : RESULT_DISALLOWED_APP_DOES_NOT_HAVE_CONFIG;
LogUtil.v(
"%s: returning %b for app (%s) that doesn't have the AdServices XML config",
- method, call.enabledByDefault, appPackageName);
- return call.enabledByDefault;
+ method, enabledByDefault, appPackageName);
+ return enabledByDefault;
}
AppManifestConfig appManifestConfig =
- AppManifestConfigParser.getConfig(in, call.enabledByDefault);
+ AppManifestConfigParser.getConfig(in, enabledByDefault);
call.result = checker.isAllowedAccess(appManifestConfig);
} catch (NameNotFoundException e) {
+ call.result = RESULT_DISALLOWED_APP_DOES_NOT_EXIST;
LogUtil.v(
"Name not found while looking for manifest for app %s: %s", appPackageName, e);
} catch (Exception e) {
+ call.result = RESULT_DISALLOWED_APP_CONFIG_PARSING_ERROR;
LogUtil.e(e, "App manifest parse failed.");
ErrorLogUtil.e(
e,
@@ -201,10 +220,10 @@ public class AppManifestConfigHelper {
} finally {
AppManifestConfigMetricsLogger.logUsage(call);
}
- return call.result;
+ return isAllowed(call.result);
}
private interface ApiAccessChecker {
- boolean isAllowedAccess(AppManifestConfig config);
+ int isAllowedAccess(AppManifestConfig config);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigMetricsLogger.java b/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigMetricsLogger.java
index d573e05ef3..17524ee946 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigMetricsLogger.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/AppManifestConfigMetricsLogger.java
@@ -15,9 +15,13 @@
*/
package com.android.adservices.service.common;
+import static com.android.adservices.service.common.AppManifestConfigCall.RESULT_UNSPECIFIED;
+import static com.android.adservices.service.common.AppManifestConfigCall.apiToString;
+import static com.android.adservices.service.common.AppManifestConfigCall.resultToString;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_MANIFEST_CONFIG_LOGGING_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_EXCEPTION;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON;
import android.content.Context;
import android.content.SharedPreferences;
@@ -27,12 +31,15 @@ import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.errorlogging.ErrorLogUtil;
import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.AppManifestConfigCall.ApiType;
+import com.android.adservices.service.common.AppManifestConfigCall.Result;
import com.android.adservices.service.common.compat.FileCompatUtils;
import com.android.adservices.shared.common.ApplicationContextSingleton;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
import java.io.PrintWriter;
+import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -45,40 +52,39 @@ public final class AppManifestConfigMetricsLogger {
static final String PREFS_NAME =
FileCompatUtils.getAdservicesFilename("AppManifestConfigMetricsLogger");
- private static final int NOT_SET = -1;
- private static final int FLAG_APP_EXISTS = 0x1;
- private static final int FLAG_APP_HAS_CONFIG = 0x2;
- private static final int FLAG_ENABLED_BY_DEFAULT = 0x4;
+ @VisibleForTesting static final String PREFS_KEY_TEMPLATE = "%s-%d";
// TODO(b/310270746): make it package-protected when TopicsServiceImplTest is refactored
/** Represents a call to a public {@link AppManifestConfigHelper} method. */
/** Logs the app usage. */
public static void logUsage(AppManifestConfigCall call) {
Objects.requireNonNull(call, "call cannot be null");
+
+ // Cannot be RESULT_UNSPECIFIED because that's used to check if the shared preferences value
+ // doesn't exist yet
+ if (call.result == RESULT_UNSPECIFIED) {
+ LogUtil.e("invalid call result: %s", call);
+ ErrorLogUtil.e(
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_MANIFEST_CONFIG_LOGGING_ERROR,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON);
+ return;
+ }
AdServicesExecutors.getBackgroundExecutor().execute(() -> handleLogUsage(call));
}
private static void handleLogUsage(AppManifestConfigCall call) {
Context context = ApplicationContextSingleton.get();
try {
- int newValue =
- (call.appExists ? FLAG_APP_EXISTS : 0)
- | (call.appHasConfig ? FLAG_APP_HAS_CONFIG : 0)
- | (call.enabledByDefault ? FLAG_ENABLED_BY_DEFAULT : 0);
+ @Result int newValue = call.result;
LogUtil.d(
- "AppManifestConfigMetricsLogger.logUsage(): app=[name=%s, exists=%b,"
- + " hasConfig=%b], enabledByDefault=%b, newValue=%d",
- call.packageName,
- call.appExists,
- call.appHasConfig,
- call.enabledByDefault,
- newValue);
+ "AppManifestConfigMetricsLogger.logUsage(): call=%s, newValue=%d",
+ call, newValue);
SharedPreferences prefs = getPrefs(context);
- String key = call.packageName;
+ String key = String.format(Locale.US, PREFS_KEY_TEMPLATE, call.packageName, call.api);
- int currentValue = prefs.getInt(key, NOT_SET);
- if (currentValue == NOT_SET) {
+ @Result int currentValue = prefs.getInt(key, RESULT_UNSPECIFIED);
+ if (currentValue == RESULT_UNSPECIFIED) {
LogUtil.v("Logging for the first time (value=%d)", newValue);
} else if (currentValue != newValue) {
LogUtil.v("Logging as value change (was %d)", currentValue);
@@ -95,28 +101,14 @@ public final class AppManifestConfigMetricsLogger {
LogUtil.v("Changes committed");
} else {
LogUtil.e(
- "logUsage(ctx, file=%s, app=%s, appExist=%b, appHasConfig=%b,"
- + " enabledByDefault=%b, newValue=%d): failed to commit",
- PREFS_NAME,
- call.packageName,
- call.appExists,
- call.appHasConfig,
- call.enabledByDefault,
- newValue);
+ "logUsage(ctx, file=%s, call=%s, newValue=%d): failed to commit",
+ PREFS_NAME, call, newValue);
ErrorLogUtil.e(
AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON);
}
} catch (Exception e) {
- LogUtil.e(
- e,
- "logUsage(ctx, file=%s, app=%s, appExist=%b, appHasConfig=%b,"
- + " enabledByDefault=%b) failed",
- PREFS_NAME,
- call.packageName,
- call.appExists,
- call.appHasConfig,
- call.enabledByDefault);
+ LogUtil.e(e, "logUsage(ctx, file=%s, call=%s) failed", PREFS_NAME, call);
ErrorLogUtil.e(
e,
AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_EXCEPTION,
@@ -126,13 +118,13 @@ public final class AppManifestConfigMetricsLogger {
/** Dumps the internal state. */
public static void dump(Context context, PrintWriter pw) {
+ String prefix = " ";
pw.println("AppManifestConfigMetricsLogger");
- String prefix = " ";
- @SuppressWarnings("NewAdServicesFile") // PREFS_NAME already called FileCompatUtils
// NOTE: shared_prefs is hard-coded on ContextImpl, but unfortunately Context doesn't offer
// any API we could use here to get that path (getSharedPreferencesPath() is @removed and
// the available APIs return a SharedPreferences, not a File).
+ @SuppressWarnings("NewAdServicesFile") // PREFS_NAME already called FileCompatUtils
String path =
new File(context.getDataDir() + "/shared_prefs", PREFS_NAME).getAbsolutePath();
pw.printf("%sPreferences file: %s.xml\n", prefix, path);
@@ -147,19 +139,25 @@ public final class AppManifestConfigMetricsLogger {
String prefix2 = prefix + " ";
for (Entry<String, ?> pref : appPrefs.entrySet()) {
- String app = pref.getKey();
+ String key = pref.getKey();
+ String appAndApi = key;
+ try {
+ String[] keyParts = key.split("-");
+ String app = keyParts[0];
+ @ApiType int api = Integer.parseInt(keyParts[1]);
+ appAndApi = app + "-" + apiToString(api);
+ } catch (Exception e) {
+ LogUtil.e(e, "failed to parse key %s", key);
+ }
Object value = pref.getValue();
if (value instanceof Integer) {
- int flags = (Integer) value;
- boolean appExists = (flags & FLAG_APP_EXISTS) != 0;
- boolean appHasConfig = (flags & FLAG_APP_HAS_CONFIG) != 0;
- boolean enabledByDefault = (flags & FLAG_ENABLED_BY_DEFAULT) != 0;
- pw.printf(
- "%s%s: rawValue=%d, appExists=%b, appHasConfig=%b, enabledByDefault=%b\n",
- prefix2, app, flags, appExists, appHasConfig, enabledByDefault);
+ @Result int result = (Integer) value;
+ pw.printf("%s%s: %s\n", prefix2, appAndApi, resultToString(result));
} else {
// Shouldn't happen
- pw.printf(" %s: unexpected value %s (class %s):\n", app, value, value.getClass());
+ pw.printf(
+ " %s: unexpected value %s (class %s):\n",
+ appAndApi, value, value.getClass());
}
}
}
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 c99b0b9a39..7809ecd409 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
@@ -123,47 +123,22 @@ public class FledgeAuthorizationFilter {
* @param apiNameLoggingId the id of the api being called
* @throws SecurityException if the package did not declare custom audience permission
*/
- public void assertAppDeclaredCustomAudiencePermission(
+ public void assertAppDeclaredPermission(
@NonNull Context context, @NonNull String appPackageName, int apiNameLoggingId)
throws SecurityException {
Objects.requireNonNull(context);
Objects.requireNonNull(appPackageName);
if (!PermissionHelper.hasCustomAudiencesPermission(context, appPackageName)) {
- logAndThrowPermissionFailure(apiNameLoggingId);
- }
- }
-
- /**
- * Check if the app had declared the protected signals permission.
- *
- * @param context api service context
- * @param apiNameLoggingId the id of the api being called
- * @throws SecurityException if the package did not declare custom audience permission
- */
- public void assertAppDeclaredProtectedSignalsPermission(
- @NonNull Context context, @NonNull String appPackageName, int apiNameLoggingId)
- throws SecurityException {
- Objects.requireNonNull(context);
- Objects.requireNonNull(appPackageName);
-
- if (!PermissionHelper.hasProtectedSignalsPermission(context, appPackageName)) {
- /*
- * Using the same message for both since getAdSelectionData can be called with either
- * permission and we don't want the error message to depend on which is checked first.
- */
- logAndThrowPermissionFailure(apiNameLoggingId);
+ sLogger.v("Permission not declared by caller in API %d", apiNameLoggingId);
+ mAdServicesLogger.logFledgeApiCallStats(
+ apiNameLoggingId, STATUS_PERMISSION_NOT_REQUESTED, 0);
+ throw new SecurityException(
+ AdServicesStatusUtils
+ .SECURITY_EXCEPTION_PERMISSION_NOT_REQUESTED_ERROR_MESSAGE);
}
}
- private void logAndThrowPermissionFailure(int apiNameLoggingId) {
- sLogger.v("Permission not declared by caller in API %d", apiNameLoggingId);
- mAdServicesLogger.logFledgeApiCallStats(
- apiNameLoggingId, STATUS_PERMISSION_NOT_REQUESTED, 0);
- throw new SecurityException(
- AdServicesStatusUtils.SECURITY_EXCEPTION_PERMISSION_NOT_REQUESTED_ERROR_MESSAGE);
- }
-
/**
* Check if a certain ad tech is enrolled and authorized to perform the operation for the
* package.
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 e2afca1366..35d6ff658c 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
@@ -295,6 +295,10 @@ public class PackageChangedReceiver extends BroadcastReceiver {
@VisibleForTesting
void consentOnPackageFullyRemoved(
@NonNull Context context, @NonNull Uri packageUri, int packageUid) {
+ if (!SdkLevel.isAtLeastS()) {
+ LogUtil.d("consentOnPackageFullyRemoved is not needed on Android R, returning...");
+ return;
+ }
Objects.requireNonNull(context);
Objects.requireNonNull(packageUri);
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 67142b6d3e..d02225e4ed 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
@@ -101,16 +101,6 @@ public final class PermissionHelper {
}
/**
- * @return {@code true} if the caller has the permission to invoke Protected Signals APIs.
- */
- public static boolean hasProtectedSignalsPermission(
- @NonNull Context context, @NonNull String appPackageName) {
- // TODO(b/236268316): Add check for SDK permission.
- return hasPermission(
- context, appPackageName, AdServicesPermissions.ACCESS_ADSERVICES_PROTECTED_SIGNALS);
- }
-
- /**
* @return {@code true} if the caller has the permission to invoke AdService's state
* modification API.
*/
diff --git a/adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java b/adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java
index f809950c9e..7ab2ff9fac 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java
@@ -175,7 +175,7 @@ public class AdServicesHttpsClient {
// Setting true explicitly to follow redirects
Uri uri = Uri.parse(url.toString());
if (WebAddresses.isLocalhost(uri) && devContext.getDevOptionsEnabled()) {
- LogUtil.v("Using unsafe HTTPS for url ", url.toString());
+ LogUtil.v("Using unsafe HTTPS for url %s", url.toString());
urlConnection.setSSLSocketFactory(getUnsafeSslSocketFactory());
} else if (WebAddresses.isLocalhost(uri)) {
LogUtil.v(
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/AdServicesStorageManager.java b/adservices/service-core/java/com/android/adservices/service/consent/AdServicesStorageManager.java
index c32e727024..f3aa866833 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/AdServicesStorageManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/AdServicesStorageManager.java
@@ -125,13 +125,20 @@ public final class AdServicesStorageManager implements IConsentStorage {
@Override
public AdServicesApiConsent getConsent(AdServicesApiType apiType) {
int consentApiType = apiType.toConsentApiType();
- return AdServicesApiConsent.getConsent(
- mAdServicesManager.getConsent(consentApiType).isIsGiven());
+ ConsentParcel consentParcel = mAdServicesManager.getConsent(consentApiType);
+ if (consentParcel == null) {
+ return AdServicesApiConsent.REVOKED;
+ }
+ return AdServicesApiConsent.getConsent(consentParcel.isIsGiven());
}
/** Returns the current privacy sandbox feature. */
@Override
- public PrivacySandboxFeatureType getCurrentPrivacySandboxFeature() {
+ public PrivacySandboxFeatureType getCurrentPrivacySandboxFeature() throws IOException {
+ if (mAdServicesManager == null
+ || mAdServicesManager.getCurrentPrivacySandboxFeature() == null) {
+ return PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED;
+ }
return PrivacySandboxFeatureType.valueOf(
mAdServicesManager.getCurrentPrivacySandboxFeature());
}
@@ -434,7 +441,8 @@ public final class AdServicesStorageManager implements IConsentStorage {
return mAdServicesManager.wasU18NotificationDisplayed();
}
- private PrivacySandboxUxCollection convertUxString(String uxString) {
+ private PrivacySandboxUxCollection convertUxString(@NonNull String uxString) {
+ Objects.requireNonNull(uxString);
return Stream.of(PrivacySandboxUxCollection.values())
.filter(ux -> uxString.equals(ux.toString()))
.findFirst()
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/AppConsentForRStorageManager.java b/adservices/service-core/java/com/android/adservices/service/consent/AppConsentForRStorageManager.java
new file mode 100644
index 0000000000..259b57cbde
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/consent/AppConsentForRStorageManager.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2023 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.consent;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.data.common.BooleanFileDatastore;
+import com.android.adservices.data.consent.AppConsentDao;
+import com.android.adservices.service.extdata.AdServicesExtDataStorageServiceManager;
+import com.android.adservices.service.ui.data.UxStatesDao;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.IOException;
+
+/**
+ * AppConsentStorageManager to handle user's consent related Apis in Android R.
+ *
+ * <p>It shares similarities with AppConsentStorageManager's logic, but adds additional storage
+ * functionality specific to AdServicesExtDataStorageServiceManager.
+ *
+ * <p>Used in PPAPI_AND_ADEXT_SERVICE
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class AppConsentForRStorageManager extends AppConsentStorageManager {
+
+ private final AdServicesExtDataStorageServiceManager mAdExtDataManager;
+ /**
+ * Constructor of AppConsentForRStorageManager
+ *
+ * @param datastore stores consent
+ * @param appConsentDao mostly used by FLEDGE
+ * @param uxStatesDao stores ux related data
+ */
+ public AppConsentForRStorageManager(
+ BooleanFileDatastore datastore,
+ AppConsentDao appConsentDao,
+ UxStatesDao uxStatesDao,
+ AdServicesExtDataStorageServiceManager adExtDataManager) {
+ super(datastore, appConsentDao, uxStatesDao);
+ this.mAdExtDataManager = adExtDataManager;
+ }
+
+ /** Clear ConsentForUninstalledApp, not support for Measurement. */
+ @Override
+ public void clearAllAppConsentData() {
+ // PPAPI_AND_ADEXT_SERVICE is only set on R which supports only
+ // Measurement.
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(/* illegalAction= */ "reset consent for apps"));
+ }
+
+ /** Clear ConsentForUninstalledApp, not support for Measurement. */
+ @Override
+ public void clearConsentForUninstalledApp(String packageName) {
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(/* illegalAction= */ "clear consent for uninstalled app"));
+ }
+
+ /** Clear ConsentForUninstalledApp, not support for Measurement. */
+ @Override
+ public void clearConsentForUninstalledApp(String packageName, int packageUid) {
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(/* illegalAction= */ "clear consent for uninstalled app"));
+ }
+
+ /** Clear KnownAppsWithConsent flag, not support for Measurement. */
+ @Override
+ public void clearKnownAppsWithConsent() {
+ // PPAPI_AND_ADEXT_SERVICE is only set on R which supports only
+ // Measurement.
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(/* illegalAction= */ "reset apps"));
+ }
+
+ /** Gets getAppsWithRevokedConsent flag, not support for Measurement. */
+ @Override
+ public ImmutableList<String> getAppsWithRevokedConsent() {
+ // PPAPI_AND_ADEXT_SERVICE is only set on R which supports only
+ // Measurement.
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(/* illegalAction= */ "fetch apps with revoked consent"));
+ }
+
+ /** Gets Consent by api flag. */
+ @Override
+ public AdServicesApiConsent getConsent(AdServicesApiType apiType) {
+ if (apiType == AdServicesApiType.MEASUREMENTS) {
+ return AdServicesApiConsent.getConsent(mAdExtDataManager.getMsmtConsent());
+ }
+ return AdServicesApiConsent.REVOKED;
+ }
+
+ /** Gets getKnownAppsWithConsent flag, not support for Measurement. */
+ @Override
+ public ImmutableList<String> getKnownAppsWithConsent() throws IOException {
+ // PPAPI_AND_ADEXT_SERVICE is only set on R which supports only
+ // Measurement.
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(/* illegalAction= */ "fetch apps with consent"));
+ }
+
+ /** Gets UserManualInteraction flag. */
+ @Override
+ public int getUserManualInteractionWithConsent() {
+ return mAdExtDataManager.getManualInteractionWithConsentStatus();
+ }
+
+ /** Gets isAdultAccount flag. */
+ @Override
+ public boolean isAdultAccount() {
+ return mAdExtDataManager.getIsAdultAccount();
+ }
+
+ /** Gets isConsentRevokedForApp flag, not support for Measurement. */
+ @Override
+ public boolean isConsentRevokedForApp(String packageName) {
+ // PPAPI_AND_ADEXT_SERVICE is only set on R which supports only
+ // Measurement.
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(
+ /* illegalAction= */ "check if consent has been revoked for" + " app"));
+ }
+
+ /** Gets isU18 account flag. */
+ @Override
+ public boolean isU18Account() {
+ return mAdExtDataManager.getIsU18Account();
+ }
+
+ /** Records GA notification displayed. */
+ @Override
+ public void recordGaUxNotificationDisplayed(boolean wasGaUxDisplayed) {
+ // PPAPI_AND_ADEXT_SERVICE is only set on R which should never show
+ // GA UX.
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(
+ /* illegalAction= */ "store if GA notification was displayed"));
+ }
+
+ /** Records notification displayed. */
+ @Override
+ public void recordNotificationDisplayed(boolean wasNotificationDisplayed) {
+ // PPAPI_AND_ADEXT_SERVICE is only set on R which should never show
+ // Beta UX.
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(/* illegalAction= */ "store if beta notif was displayed"));
+ }
+
+ /** Records user manual interaction bit. */
+ @Override
+ public void recordUserManualInteractionWithConsent(int interaction) {
+ mAdExtDataManager.setManualInteractionWithConsentStatus(interaction);
+ }
+
+ /** Sets consent by api type. */
+ @Override
+ public void setAdultAccount(boolean isAdultAccount) {
+ mAdExtDataManager.setIsAdultAccount(isAdultAccount);
+ }
+
+ /** Sets consent by api type. */
+ @Override
+ public void setConsent(AdServicesApiType apiType, boolean isGiven) throws IOException {
+ if (apiType == AdServicesApiType.ALL_API) {
+ super.setConsent(apiType, isGiven);
+ return;
+ }
+ // PPAPI_AND_ADEXT_SERVICE is only set on R which supports only
+ // Measurement. There should never be a call to set consent for other PPAPIs.
+ if (apiType != AdServicesApiType.MEASUREMENTS) {
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(
+ /* illegalAction= */ "set consent for a non-msmt API"));
+ }
+ mAdExtDataManager.setMsmtConsent(isGiven);
+ }
+
+ /**
+ * setConsentForApp.
+ *
+ * <p>PPAPI_AND_ADEXT_SERVICE is only set on R which only supports Measurement
+ */
+ @Override
+ public void setConsentForApp(String packageName, boolean isConsentRevoked) {
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(/* illegalAction= */ "revoke consent for app"));
+ }
+
+ /**
+ * SetConsentForAppIfNew.
+ *
+ * <p>PPAPI_AND_ADEXT_SERVICE is only set on R which only supports Measurement
+ */
+ @Override
+ public boolean setConsentForAppIfNew(String packageName, boolean isConsentRevoked) {
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(
+ /* illegalAction= */ "check if consent has been revoked for" + " app"));
+ }
+
+ @Override
+ public void recordDefaultConsent(AdServicesApiType apiType, boolean defaultConsent)
+ throws IOException {
+ if (apiType == AdServicesApiType.MEASUREMENTS) {
+ super.recordDefaultConsent(apiType, defaultConsent);
+ } else {
+ throw new IllegalStateException(
+ getAdExtExceptionMessage(
+ /* illegalAction= */ "record default consent for "
+ + apiType.toString()));
+ }
+ }
+
+ /** Stores isU18Account bit in AdExtData. */
+ @Override
+ public void setU18Account(boolean isU18Account) {
+ mAdExtDataManager.setIsU18Account(isU18Account);
+ }
+
+ /** Stores U18 notification bit in AdExtData. */
+ @Override
+ public void setU18NotificationDisplayed(boolean wasU18NotificationDisplayed) {
+ mAdExtDataManager.setNotificationDisplayed(wasU18NotificationDisplayed);
+ }
+
+ /** GA UX is never shown on R, so this info is not stored. */
+ @Override
+ public boolean wasGaUxNotificationDisplayed() {
+ return false;
+ }
+
+ /** Beta UX is never shown on R, so this info is not stored. */
+ @Override
+ public boolean wasNotificationDisplayed() {
+ return false;
+ }
+
+ /** Android R only U18 notification is allowed to be displayed. */
+ @Override
+ public boolean wasU18NotificationDisplayed() {
+ return mAdExtDataManager.getNotificationDisplayed();
+ }
+
+ private static String getAdExtExceptionMessage(String illegalAction) {
+ return String.format(
+ "Attempting to %s using PPAPI_AND_ADEXT_SERVICE consent source of truth!",
+ illegalAction);
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/AppConsentStorageManager.java b/adservices/service-core/java/com/android/adservices/service/consent/AppConsentStorageManager.java
index a7dc3050a4..af1b2ddd0b 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/AppConsentStorageManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/AppConsentStorageManager.java
@@ -31,6 +31,7 @@ import com.android.adservices.service.ui.ux.collection.PrivacySandboxUxCollectio
import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -131,7 +132,8 @@ public class AppConsentStorageManager implements IConsentStorage {
*/
@Override
public AdServicesApiConsent getConsent(AdServicesApiType apiType) {
- return AdServicesApiConsent.getConsent(mDatastore.get(apiType.toPpApiDatastoreKey()));
+ return AdServicesApiConsent.getConsent(
+ Objects.requireNonNullElse(mDatastore.get(apiType.toPpApiDatastoreKey()), false));
}
/**
@@ -153,6 +155,15 @@ public class AppConsentStorageManager implements IConsentStorage {
return PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED;
}
+ /** Set the current privacy sandbox feature. */
+ @Override
+ public void setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType featureType)
+ throws IOException {
+ for (PrivacySandboxFeatureType currentFeatureType : PrivacySandboxFeatureType.values()) {
+ mDatastore.put(currentFeatureType.name(), currentFeatureType == featureType);
+ }
+ }
+
/**
* Retrieves the default AdId state.
*
@@ -160,7 +171,8 @@ public class AppConsentStorageManager implements IConsentStorage {
*/
@Override
public boolean getDefaultAdIdState() {
- return mDatastore.get(ConsentConstants.DEFAULT_AD_ID_STATE);
+ return Objects.requireNonNullElse(
+ mDatastore.get(ConsentConstants.DEFAULT_AD_ID_STATE), false);
}
/**
@@ -170,7 +182,9 @@ public class AppConsentStorageManager implements IConsentStorage {
*/
@Override
public AdServicesApiConsent getDefaultConsent(AdServicesApiType apiType) {
- return AdServicesApiConsent.getConsent(mDatastore.get(apiType.toPpApiDatastoreKey()));
+ return AdServicesApiConsent.getConsent(
+ Objects.requireNonNullElse(
+ mDatastore.get(apiType.toDefaultConsentDatastoreKey()), false));
}
/** Returns current enrollment channel. */
@@ -215,16 +229,34 @@ public class AppConsentStorageManager implements IConsentStorage {
return mUxStatesDao.getUx();
}
+ /** Set the current UX to storage. */
+ @Override
+ public void setUx(PrivacySandboxUxCollection ux) {
+ mUxStatesDao.setUx(ux);
+ }
+
/** Returns whether the isAdIdEnabled bit is true. */
@Override
public boolean isAdIdEnabled() {
- return mDatastore.get(ConsentConstants.IS_AD_ID_ENABLED);
+ return Objects.requireNonNullElse(mDatastore.get(ConsentConstants.IS_AD_ID_ENABLED), false);
+ }
+
+ /** Set the AdIdEnabled bit to storage. */
+ @Override
+ public void setAdIdEnabled(boolean isAdIdEnabled) throws IOException {
+ mDatastore.put(ConsentConstants.IS_AD_ID_ENABLED, isAdIdEnabled);
}
/** Returns whether the isAdultAccount bit is true. */
@Override
public boolean isAdultAccount() {
- return mDatastore.get(ConsentConstants.IS_ADULT_ACCOUNT);
+ return Objects.requireNonNullElse(mDatastore.get(ConsentConstants.IS_ADULT_ACCOUNT), false);
+ }
+
+ /** Set the AdultAccount bit to storage. */
+ @Override
+ public void setAdultAccount(boolean isAdultAccount) throws IOException {
+ mDatastore.put(ConsentConstants.IS_ADULT_ACCOUNT, isAdultAccount);
}
/**
@@ -241,7 +273,8 @@ public class AppConsentStorageManager implements IConsentStorage {
@Override
public boolean isConsentRevokedForApp(String packageName) throws IllegalArgumentException {
try {
- return mAppConsentDao.isConsentRevokedForApp(packageName);
+ return Objects.requireNonNullElse(
+ mAppConsentDao.isConsentRevokedForApp(packageName), false);
} catch (IOException exception) {
LogUtil.e(exception, "FLEDGE consent check failed due to IOException");
}
@@ -251,13 +284,26 @@ public class AppConsentStorageManager implements IConsentStorage {
/** Returns whether the isEntryPointEnabled bit is true. */
@Override
public boolean isEntryPointEnabled() {
- return mDatastore.get(ConsentConstants.IS_ENTRY_POINT_ENABLED);
+ return Objects.requireNonNullElse(
+ mDatastore.get(ConsentConstants.IS_ENTRY_POINT_ENABLED), false);
+ }
+
+ /** Set the EntryPointEnabled bit to storage . */
+ @Override
+ public void setEntryPointEnabled(boolean isEntryPointEnabled) throws IOException {
+ mDatastore.put(ConsentConstants.IS_ENTRY_POINT_ENABLED, isEntryPointEnabled);
}
/** Returns whether the isU18Account bit is true. */
@Override
public boolean isU18Account() {
- return mDatastore.get(ConsentConstants.IS_U18_ACCOUNT);
+ return Objects.requireNonNullElse(mDatastore.get(ConsentConstants.IS_U18_ACCOUNT), false);
+ }
+
+ /** Set the U18Account bit to storage. */
+ @Override
+ public void setU18Account(boolean isU18Account) throws IOException {
+ mDatastore.put(ConsentConstants.IS_U18_ACCOUNT, isU18Account);
}
/** Saves the default AdId state bit to data stores based on source of truth. */
@@ -270,7 +316,7 @@ public class AppConsentStorageManager implements IConsentStorage {
@Override
public void recordDefaultConsent(AdServicesApiType apiType, boolean defaultConsent)
throws IOException {
- mDatastore.put(apiType.toPpApiDatastoreKey(), defaultConsent);
+ mDatastore.put(apiType.toDefaultConsentDatastoreKey(), defaultConsent);
}
/**
@@ -311,22 +357,9 @@ public class AppConsentStorageManager implements IConsentStorage {
}
}
- /** Set the AdIdEnabled bit to storage. */
- @Override
- public void setAdIdEnabled(boolean isAdIdEnabled) throws IOException {
- // test
- mDatastore.put(ConsentConstants.IS_AD_ID_ENABLED, isAdIdEnabled);
- }
-
- /** Set the AdultAccount bit to storage. */
- @Override
- public void setAdultAccount(boolean isAdultAccount) throws IOException {
- mDatastore.put(ConsentConstants.IS_ADULT_ACCOUNT, isAdultAccount);
- }
-
/**
* Sets the consent for this user ID for this API type in AppSearch. If we do not get
- * confirmation that the write was successful, then we throw an exception so that user does not
+ * confirmation that to write was successful, then we throw an exception so that user does not
* incorrectly think that the consent is updated.
*
* @throws IOException if the operation fails
@@ -361,7 +394,7 @@ public class AppConsentStorageManager implements IConsentStorage {
@Override
public boolean setConsentForAppIfNew(String packageName, boolean isConsentRevoked)
throws IllegalArgumentException {
- // test
+ // TODO(b/317595641) clean up setConsentForAppIfNew logic
try {
return mAppConsentDao.setConsentForAppIfNew(packageName, isConsentRevoked);
} catch (IOException exception) {
@@ -370,15 +403,6 @@ public class AppConsentStorageManager implements IConsentStorage {
}
}
- /** Set the current privacy sandbox feature. */
- @Override
- public void setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType featureType)
- throws IOException {
- for (PrivacySandboxFeatureType currentFeatureType : PrivacySandboxFeatureType.values()) {
- mDatastore.put(currentFeatureType.name(), currentFeatureType == featureType);
- }
- }
-
/** Set the current enrollment channel to storage. */
@Override
public void setEnrollmentChannel(
@@ -386,18 +410,6 @@ public class AppConsentStorageManager implements IConsentStorage {
mUxStatesDao.setEnrollmentChannel(ux, channel);
}
- /** Set the EntryPointEnabled bit to storage . */
- @Override
- public void setEntryPointEnabled(boolean isEntryPointEnabled) throws IOException {
- mDatastore.put(ConsentConstants.IS_ENTRY_POINT_ENABLED, isEntryPointEnabled);
- }
-
- /** Set the U18Account bit to storage. */
- @Override
- public void setU18Account(boolean isU18Account) throws IOException {
- mDatastore.put(ConsentConstants.IS_U18_ACCOUNT, isU18Account);
- }
-
/** Set the U18NotificationDisplayed bit to storage. */
@Override
public void setU18NotificationDisplayed(boolean wasU18NotificationDisplayed)
@@ -406,12 +418,6 @@ public class AppConsentStorageManager implements IConsentStorage {
ConsentConstants.WAS_U18_NOTIFICATION_DISPLAYED, wasU18NotificationDisplayed);
}
- /** Set the current UX to storage. */
- @Override
- public void setUx(PrivacySandboxUxCollection ux) {
- mUxStatesDao.setUx(ux);
- }
-
/**
* Retrieves if GA UX notification has been displayed.
*
@@ -419,7 +425,8 @@ public class AppConsentStorageManager implements IConsentStorage {
*/
@Override
public boolean wasGaUxNotificationDisplayed() {
- return mDatastore.get(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE);
+ return Objects.requireNonNullElse(
+ mDatastore.get(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE), false);
}
/**
@@ -429,12 +436,16 @@ public class AppConsentStorageManager implements IConsentStorage {
*/
@Override
public boolean wasNotificationDisplayed() {
- return mDatastore.get(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE);
+ return Objects.requireNonNullElse(
+ mDatastore.get(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE), false);
}
/** Returns whether the wasU18NotificationDisplayed bit is true. */
@Override
public boolean wasU18NotificationDisplayed() {
- return mDatastore.get(ConsentConstants.WAS_U18_NOTIFICATION_DISPLAYED);
+ return Objects.requireNonNullElse(
+ mDatastore.get(ConsentConstants.WAS_U18_NOTIFICATION_DISPLAYED), false);
}
+
+
}
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/ConsentCompositeStorage.java b/adservices/service-core/java/com/android/adservices/service/consent/ConsentCompositeStorage.java
index 730b0788ba..75229cb1ce 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/ConsentCompositeStorage.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/ConsentCompositeStorage.java
@@ -22,13 +22,18 @@ import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICE
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PRIVACY_SANDBOX_SAVE_FAILURE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX;
+import static com.android.adservices.service.ui.ux.collection.PrivacySandboxUxCollection.U18_UX;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
import com.android.adservices.LogUtil;
import com.android.adservices.errorlogging.ErrorLogUtil;
import com.android.adservices.service.common.feature.PrivacySandboxFeatureType;
+import com.android.adservices.service.exception.ConsentStorageDeferException;
import com.android.adservices.service.ui.enrollment.collection.PrivacySandboxEnrollmentChannelCollection;
import com.android.adservices.service.ui.ux.collection.PrivacySandboxUxCollection;
import com.android.internal.annotations.VisibleForTesting;
@@ -36,14 +41,22 @@ import com.android.internal.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
-import java.util.List;
/**
* CompositeStorage to handle read/write user's to multiple source of truth
*
* <p>Every source of truth should have its own dedicated storage class that implements the
* IConsentStorage interface, and pass in the instances to ConsentCompositeStorage.
+ *
+ * <p>By default, when caller set value to the storage, CompositeStorage will iterate through every
+ * instance in mConsentStorageList and call the corresponding method. For getter, CompositeStorage
+ * will only return the first one.
+ *
+ * <p>If the method not available in some implementation, the implementation class should throw
+ * {code ConsentStorageDeferException}, CompositeStorage will try to get the result for next
+ * instance.
*/
+@RequiresApi(Build.VERSION_CODES.S)
public class ConsentCompositeStorage implements IConsentStorage {
private static final int UNKNOWN = 0;
private final ImmutableList<IConsentStorage> mConsentStorageList;
@@ -54,7 +67,6 @@ public class ConsentCompositeStorage implements IConsentStorage {
* @param consentStorageList storage implementation instance list.
*/
public ConsentCompositeStorage(ImmutableList<IConsentStorage> consentStorageList) {
- assert (consentStorageList.size() > 0);
if (consentStorageList == null || consentStorageList.isEmpty()) {
throw new IllegalArgumentException("consent storage list can not be empty!");
}
@@ -67,9 +79,21 @@ public class ConsentCompositeStorage implements IConsentStorage {
* <p>This should be called when the Privacy Sandbox has been disabled.
*/
@Override
- public void clearAllAppConsentData() throws IOException {
+ public void clearAllAppConsentData() {
for (IConsentStorage storage : getConsentStorageList()) {
- storage.clearAllAppConsentData();
+ try {
+ storage.clearAllAppConsentData();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ throw new RuntimeException(e);
+ }
}
}
@@ -80,12 +104,19 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public void clearConsentForUninstalledApp(@NonNull String packageName) {
- try {
- for (IConsentStorage storage : getConsentStorageList()) {
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
storage.clearConsentForUninstalledApp(packageName);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
}
- } catch (IOException e) {
- throw new RuntimeException(e);
}
}
@@ -97,12 +128,18 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public void clearConsentForUninstalledApp(String packageName, int packageUid) {
-
for (IConsentStorage storage : getConsentStorageList()) {
try {
storage.clearConsentForUninstalledApp(packageName, packageUid);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
- LogUtil.e(getClass().getSimpleName() + " failed. " + e.getMessage());
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
}
}
}
@@ -117,8 +154,15 @@ public class ConsentCompositeStorage implements IConsentStorage {
for (IConsentStorage storage : getConsentStorageList()) {
try {
storage.clearKnownAppsWithConsent();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
- throw new RuntimeException(e);
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
}
}
}
@@ -126,15 +170,24 @@ public class ConsentCompositeStorage implements IConsentStorage {
/**
* @return an {@link ImmutableList} of all known apps in the database that have had user consent
* revoked
- * @throws IOException if the operation fails
*/
@Override
public ImmutableList<String> getAppsWithRevokedConsent() {
- try {
- return getPrimaryStorage().getAppsWithRevokedConsent();
- } catch (IOException e) {
- throw new RuntimeException(e);
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getAppsWithRevokedConsent();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ }
}
+ return ImmutableList.of();
}
/**
@@ -147,15 +200,22 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public AdServicesApiConsent getConsent(AdServicesApiType apiType) {
- try {
- return getPrimaryStorage().getConsent(apiType);
- } catch (IOException e) {
- ErrorLogUtil.e(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
- return AdServicesApiConsent.REVOKED;
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getConsent(apiType);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException | RuntimeException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ return AdServicesApiConsent.REVOKED;
+ }
}
+ return AdServicesApiConsent.REVOKED;
}
/**
@@ -178,15 +238,42 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public PrivacySandboxFeatureType getCurrentPrivacySandboxFeature() {
- try {
- return getPrimaryStorage().getCurrentPrivacySandboxFeature();
- } catch (IOException e) {
- ErrorLogUtil.e(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PRIVACY_SANDBOX_SAVE_FAILURE,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
- LogUtil.e(getClass().getSimpleName() + " failed. " + e.getMessage());
- return PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED;
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getCurrentPrivacySandboxFeature();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException | RuntimeException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PRIVACY_SANDBOX_SAVE_FAILURE,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ return PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED;
+ }
+ }
+ return PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED;
+ }
+
+ /** Sets the current privacy sandbox feature. */
+ @Override
+ public void setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType featureType) {
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ storage.setCurrentPrivacySandboxFeature(featureType);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PRIVACY_SANDBOX_SAVE_FAILURE,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ throw new RuntimeException(
+ getClass().getSimpleName() + " failed. " + e.getMessage());
+ }
}
}
@@ -197,7 +284,19 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public boolean getDefaultAdIdState() {
- return getPrimaryStorage().getDefaultAdIdState();
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getDefaultAdIdState();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreWhileRecordingDefaultConsent(e);
+ return false;
+ }
+ }
+ return false;
}
/**
@@ -207,22 +306,37 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public AdServicesApiConsent getDefaultConsent(AdServicesApiType apiType) {
- try {
- return getPrimaryStorage().getDefaultConsent(apiType);
- } catch (IOException e) {
- ErrorLogUtil.e(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_DEFAULT_CONSENT,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
- return AdServicesApiConsent.REVOKED;
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getDefaultConsent(apiType);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreWhileRecordingDefaultConsent(e);
+ return AdServicesApiConsent.REVOKED;
+ }
}
+ return AdServicesApiConsent.REVOKED;
}
/** Returns current enrollment channel. */
@Override
public PrivacySandboxEnrollmentChannelCollection getEnrollmentChannel(
PrivacySandboxUxCollection ux) {
- return getPrimaryStorage().getEnrollmentChannel(ux);
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getEnrollmentChannel(ux);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreWhileRecordingDefaultConsent(e);
+ }
+ }
+ return null;
}
/**
@@ -233,26 +347,20 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public ImmutableList<String> getKnownAppsWithConsent() {
- try {
- return getPrimaryStorage().getKnownAppsWithConsent();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Gets first storage instance, for read operations, should always return from the first source
- * of truth.
- *
- * @return first instance of IConsentStorage.
- */
- @VisibleForTesting
- public IConsentStorage getPrimaryStorage() {
- List<IConsentStorage> storageList = getConsentStorageList();
- if (storageList.isEmpty()) {
- throw new IllegalStateException("Consent Storage List is empty.");
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getKnownAppsWithConsent();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreWhileRecordingDefaultConsent(e);
+ } catch (IllegalStateException e) {
+ LogUtil.i("IllegalStateException" + e);
+ }
}
- return storageList.get(0);
+ return ImmutableList.of();
}
/**
@@ -262,33 +370,121 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public int getUserManualInteractionWithConsent() {
- try {
- return getPrimaryStorage().getUserManualInteractionWithConsent();
- } catch (IOException e) {
- ErrorLogUtil.e(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_MANUAL_CONSENT_INTERACTION,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
- return UNKNOWN;
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getUserManualInteractionWithConsent();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreManualInteractionException(e);
+ return UNKNOWN;
+ }
}
+ return 0;
}
/** Returns current UX. */
@Override
public PrivacySandboxUxCollection getUx() {
- return getPrimaryStorage().getUx();
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.getUx();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreManualInteractionException(e);
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+
+ /** Sets the current UX to storage. */
+ @Override
+ public void setUx(PrivacySandboxUxCollection ux) {
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ storage.setUx(ux);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
+ }
+ }
}
/** Returns whether the isAdIdEnabled bit is true. */
@Override
public boolean isAdIdEnabled() {
- return getPrimaryStorage().isAdIdEnabled();
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.isAdIdEnabled();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreManualInteractionException(e);
+ throw new RuntimeException(e);
+ }
+ }
+ return false;
+ }
+
+ /** Set the AdIdEnabled bit to storage. */
+ @Override
+ public void setAdIdEnabled(boolean isAdIdEnabled) {
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ storage.setAdIdEnabled(isAdIdEnabled);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
+ }
+ }
}
/** Returns whether the isAdultAccount bit is true. */
@Override
public boolean isAdultAccount() {
- return getPrimaryStorage().isAdultAccount();
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.isAdultAccount();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreManualInteractionException(e);
+ throw new RuntimeException(e);
+ }
+ }
+ return false;
+ }
+
+ /** Set the AdultAccount bit to storage. */
+ @Override
+ public void setAdultAccount(boolean isAdultAccount) {
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ storage.setAdultAccount(isAdultAccount);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
+ }
+ }
}
/**
@@ -300,23 +496,90 @@ public class ConsentCompositeStorage implements IConsentStorage {
*
* @throws IllegalArgumentException if the package name is invalid or not found as an installed
* application
- * @throws IOException if the operation fails
*/
@Override
public boolean isConsentRevokedForApp(String packageName) throws IllegalArgumentException {
- return getPrimaryStorage().isConsentRevokedForApp(packageName);
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.isConsentRevokedForApp(packageName);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreManualInteractionException(e);
+ throw new RuntimeException(e);
+ }
+ }
+ return false;
}
/** Returns whether the isEntryPointEnabled bit is true. */
@Override
public boolean isEntryPointEnabled() {
- return getPrimaryStorage().isEntryPointEnabled();
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.isEntryPointEnabled();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreManualInteractionException(e);
+ throw new RuntimeException(e);
+ }
+ }
+ return false;
+ }
+
+ /** Sets the EntryPointEnabled bit to storage . */
+ @Override
+ public void setEntryPointEnabled(boolean isEntryPointEnabled) {
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ storage.setEntryPointEnabled(isEntryPointEnabled);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
+ }
+ }
}
/** Returns whether the isU18Account bit is true. */
@Override
public boolean isU18Account() {
- return getPrimaryStorage().isU18Account();
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.isU18Account();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDatastoreManualInteractionException(e);
+ throw new RuntimeException(e);
+ }
+ }
+ return false;
+ }
+
+ /** Sets the U18Account bit to storage. */
+ @Override
+ public void setU18Account(boolean isU18Account) {
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ storage.setU18Account(isU18Account);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
+ }
+ }
}
/** Saves the default AdId state bit to data stores based on source of truth. */
@@ -325,7 +588,12 @@ public class ConsentCompositeStorage implements IConsentStorage {
for (IConsentStorage storage : getConsentStorageList()) {
try {
storage.recordDefaultAdIdState(defaultAdIdState);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
+ logDatastoreManualInteractionException(e);
throw new RuntimeException(e);
}
}
@@ -337,7 +605,12 @@ public class ConsentCompositeStorage implements IConsentStorage {
for (IConsentStorage storage : getConsentStorageList()) {
try {
storage.recordDefaultConsent(apiType, defaultConsent);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
+ logDatastoreManualInteractionException(e);
throw new RuntimeException(e);
}
}
@@ -352,7 +625,12 @@ public class ConsentCompositeStorage implements IConsentStorage {
for (IConsentStorage storage : getConsentStorageList()) {
try {
storage.recordGaUxNotificationDisplayed(wasGaUxDisplayed);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
+ logDatastoreManualInteractionException(e);
throw new RuntimeException(e);
}
}
@@ -366,10 +644,13 @@ public class ConsentCompositeStorage implements IConsentStorage {
public void recordNotificationDisplayed(boolean wasNotificationDisplayed) {
for (IConsentStorage storage : getConsentStorageList()) {
try {
-
storage.recordNotificationDisplayed(wasNotificationDisplayed);
-
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
+ logDatastoreManualInteractionException(e);
throw new RuntimeException(e);
}
}
@@ -381,53 +662,66 @@ public class ConsentCompositeStorage implements IConsentStorage {
for (IConsentStorage storage : getConsentStorageList()) {
try {
storage.recordUserManualInteractionWithConsent(interaction);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
- ErrorLogUtil.e(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_MANUAL_CONSENT_INTERACTION,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ logDatastoreManualInteractionException(e);
throw new RuntimeException(
getClass().getSimpleName() + " failed. " + e.getMessage());
}
}
}
- /** Set the AdIdEnabled bit to storage. */
+ /**
+ * Sets the consent for this user ID for this API type in AppSearch. If we do not get
+ * confirmation that the write was successful, then we throw an exception so that user does not
+ * incorrectly think that the consent is updated.
+ */
@Override
- public void setAdIdEnabled(boolean isAdIdEnabled) {
- for (IConsentStorage storage : getConsentStorageList()) {
- try {
- storage.setAdIdEnabled(isAdIdEnabled);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ public void setConsent(AdServicesApiType apiType, boolean isGiven) {
+ setConsentToApiType(apiType, isGiven);
+ if (apiType == AdServicesApiType.ALL_API) {
+ return;
}
+ setAggregatedConsent();
}
- /** Set the AdultAccount bit to storage. */
- @Override
- public void setAdultAccount(boolean isAdultAccount) {
+ private void setConsentToApiType(AdServicesApiType apiType, boolean isGiven) {
for (IConsentStorage storage : getConsentStorageList()) {
try {
- storage.setAdultAccount(isAdultAccount);
+ storage.setConsent(apiType, isGiven);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
- throw new RuntimeException(e);
+ logDataStoreWhileRecordingException(e);
}
}
}
- /**
- * Sets the consent for this user ID for this API type in AppSearch. If we do not get
- * confirmation that the write was successful, then we throw an exception so that user does not
- * incorrectly think that the consent is updated.
- *
- * @throws IOException if the operation fails
- */
- @Override
- public void setConsent(AdServicesApiType apiType, boolean isGiven) throws IOException {
- for (IConsentStorage storage : getConsentStorageList()) {
- storage.setConsent(apiType, isGiven);
+ // Set the aggregated consent so that after the rollback of the module
+ // and the flag which controls the consent flow everything works as expected.
+ // The problematic edge case which is covered:
+ // T1: AdServices is installed in pre-GA UX version and the consent is given
+ // T2: AdServices got upgraded to GA UX binary and GA UX feature flag is enabled
+ // T3: Consent for the Topics API got revoked
+ // T4: AdServices got rolledback and the feature flags which controls consent flow
+ // (SYSTEM_SERVER_ONLY and DUAL_WRITE) also got rolledback
+ // T5: Restored consent should be revoked
+ @VisibleForTesting
+ void setAggregatedConsent() {
+ if (getUx() == U18_UX) {
+ // The edge case does not apply to U18 UX.
+ return;
}
+ setConsentToApiType(
+ AdServicesApiType.ALL_API,
+ getConsent(AdServicesApiType.TOPICS).isGiven()
+ && getConsent(AdServicesApiType.MEASUREMENTS).isGiven()
+ && getConsent(AdServicesApiType.FLEDGE).isGiven());
}
/**
@@ -441,7 +735,12 @@ public class ConsentCompositeStorage implements IConsentStorage {
for (IConsentStorage storage : getConsentStorageList()) {
try {
storage.setConsentForApp(packageName, isConsentRevoked);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
throw new RuntimeException(e);
}
}
@@ -459,34 +758,25 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public boolean setConsentForAppIfNew(String packageName, boolean isConsentRevoked) {
- boolean ret = false;
- for (IConsentStorage storage : getConsentStorageList()) {
- try {
- // Same as original logic, only return the last one.
- ret = storage.setConsentForAppIfNew(packageName, isConsentRevoked);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- // LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
- return ret;
- }
-
- /** Sets the current privacy sandbox feature. */
- @Override
- public void setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType featureType) {
- for (IConsentStorage storage : getConsentStorageList()) {
+ for (IConsentStorage storage : getConsentStorageList().reverse()) {
try {
- storage.setCurrentPrivacySandboxFeature(featureType);
+ // Same as original logic, call the PPAPI first
+ // TODO(b/317595641): clean up the logic
+ boolean ret = storage.setConsentForAppIfNew(packageName, isConsentRevoked);
+ if (ret) {
+ return true;
+ }
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
- ErrorLogUtil.e(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PRIVACY_SANDBOX_SAVE_FAILURE,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
- throw new RuntimeException(
- getClass().getSimpleName() + " failed. " + e.getMessage());
+ LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
+ logDataStoreWhileRecordingException(e);
+ return false;
}
}
+ return false;
}
/** Sets the current enrollment channel to storage. */
@@ -494,50 +784,35 @@ public class ConsentCompositeStorage implements IConsentStorage {
public void setEnrollmentChannel(
PrivacySandboxUxCollection ux, PrivacySandboxEnrollmentChannelCollection channel) {
for (IConsentStorage storage : getConsentStorageList()) {
- storage.setEnrollmentChannel(ux, channel);
- }
- }
-
- /** Sets the EntryPointEnabled bit to storage . */
- @Override
- public void setEntryPointEnabled(boolean isEntryPointEnabled) {
- for (IConsentStorage storage : getConsentStorageList()) {
try {
- storage.setEntryPointEnabled(isEntryPointEnabled);
+ storage.setEnrollmentChannel(ux, channel);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
- throw new RuntimeException(e);
+ logDataStoreWhileRecordingException(e);
}
}
}
- /** Sets the U18Account bit to storage. */
- @Override
- public void setU18Account(boolean isU18Account) throws IOException {
- for (IConsentStorage storage : getConsentStorageList()) {
- storage.setU18Account(isU18Account);
- }
- }
-
/** Sets the U18NotificationDisplayed bit to storage. */
@Override
public void setU18NotificationDisplayed(boolean wasU18NotificationDisplayed) {
for (IConsentStorage storage : getConsentStorageList()) {
try {
storage.setU18NotificationDisplayed(wasU18NotificationDisplayed);
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
} catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
throw new RuntimeException(e);
}
}
}
- /** Sets the current UX to storage. */
- @Override
- public void setUx(PrivacySandboxUxCollection ux) {
- for (IConsentStorage storage : getConsentStorageList()) {
- storage.setUx(ux);
- }
- }
-
/**
* Retrieves if GA UX notification has been displayed.
*
@@ -545,7 +820,19 @@ public class ConsentCompositeStorage implements IConsentStorage {
*/
@Override
public boolean wasGaUxNotificationDisplayed() {
- return getPrimaryStorage().wasGaUxNotificationDisplayed();
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.wasGaUxNotificationDisplayed();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
+ return false;
+ }
+ }
+ return false;
}
/**
@@ -556,20 +843,57 @@ public class ConsentCompositeStorage implements IConsentStorage {
@SuppressLint("NameOfTheRuleToSuppress")
@Override
public boolean wasNotificationDisplayed() {
- try {
- return getPrimaryStorage().wasNotificationDisplayed();
- } catch (IOException e) {
- ErrorLogUtil.e(
- e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_NOTIFICATION,
- AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
- return false;
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.wasNotificationDisplayed();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
+ return false;
+ }
}
+ return false;
}
/** Returns whether the wasU18NotificationDisplayed bit is true. */
@Override
public boolean wasU18NotificationDisplayed() {
- return getPrimaryStorage().wasU18NotificationDisplayed();
+ for (IConsentStorage storage : getConsentStorageList()) {
+ try {
+ return storage.wasU18NotificationDisplayed();
+ } catch (ConsentStorageDeferException e) {
+ LogUtil.i(
+ "Skip current storage manager %s. Defer to next one",
+ storage.getClass().getSimpleName());
+ } catch (IOException e) {
+ logDataStoreWhileRecordingException(e);
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private static void logDataStoreWhileRecordingException(IOException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_NOTIFICATION,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ }
+
+ private static void logDatastoreManualInteractionException(IOException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_MANUAL_CONSENT_INTERACTION,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ }
+
+ private static void logDatastoreWhileRecordingDefaultConsent(IOException e) {
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_DEFAULT_CONSENT,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java b/adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java
index 8407ed08e4..582f2a1413 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java
@@ -17,7 +17,6 @@
package com.android.adservices.service.consent;
import com.android.adservices.service.common.compat.FileCompatUtils;
-import com.android.internal.annotations.VisibleForTesting;
/** ConsentManager related Constants. */
public class ConsentConstants {
@@ -37,8 +36,7 @@ public class ConsentConstants {
public static final String DEFAULT_AD_ID_STATE = "DEFAULT_AD_ID_STATE";
- @VisibleForTesting
- static final String MANUAL_INTERACTION_WITH_CONSENT_RECORDED =
+ public static final String MANUAL_INTERACTION_WITH_CONSENT_RECORDED =
"MANUAL_INTERACTION_WITH_CONSENT_RECORDED";
public static final String CONSENT_KEY = "CONSENT";
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 900ffd58f3..171970b3b6 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
@@ -39,6 +39,7 @@ import android.app.job.JobScheduler;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
+import android.os.Trace;
import androidx.annotation.RequiresApi;
@@ -206,6 +207,7 @@ public class ConsentManager {
public static ConsentManager getInstance(@NonNull Context context) {
Objects.requireNonNull(context);
+ Trace.beginSection("ConsentManager#Initialization");
if (sConsentManager == null) {
synchronized (LOCK) {
if (sConsentManager == null) {
@@ -253,7 +255,8 @@ public class ConsentManager {
context,
datastore,
appSearchConsentManager,
- adServicesExtDataManager);
+ adServicesExtDataManager,
+ statsdAdServicesLogger);
}
}
@@ -288,6 +291,7 @@ public class ConsentManager {
}
}
}
+ Trace.endSection();
return sConsentManager;
}
@@ -2154,6 +2158,7 @@ public class ConsentManager {
for back compat. */
ThrowableSetter ppapiAndAdExtDataServiceSetter,
ErrorLogger errorLogger) {
+ Trace.beginSection("ConsentManager#WriteOperation");
mReadWriteLock.writeLock().lock();
try {
switch (mConsentSourceOfTruth) {
@@ -2185,11 +2190,12 @@ public class ConsentManager {
if (errorLogger != null) {
errorLogger.apply(e);
}
- throw new RuntimeException(getClass().getSimpleName() + " failed. " + e.getMessage());
+ throw new RuntimeException(
+ getClass().getSimpleName() + " failed. " + e.getMessage(), e);
} finally {
mReadWriteLock.writeLock().unlock();
+ Trace.endSection();
}
-
}
@FunctionalInterface
@@ -2218,6 +2224,7 @@ public class ConsentManager {
for back compat. */
ThrowableGetter<T> ppapiAndAdExtDataServiceGetter,
ErrorLogger errorLogger) {
+ Trace.beginSection("ConsentManager#ReadOperation");
mReadWriteLock.readLock().lock();
try {
switch (mConsentSourceOfTruth) {
@@ -2248,6 +2255,7 @@ public class ConsentManager {
LogUtil.e(getClass().getSimpleName() + " failed. " + e.getMessage());
} finally {
mReadWriteLock.readLock().unlock();
+ Trace.endSection();
}
return defaultReturn;
@@ -2260,8 +2268,20 @@ public class ConsentManager {
: AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
}
- /* Returns an object of ConsentMigrationStats */
- private static ConsentMigrationStats getConsentManagerStatsForLogging(
+ /***
+ * Returns an object of ConsentMigrationStats for logging
+ *
+ * @param appConsents AppConsents consents per API (fledge, msmt, topics, default)
+ * @param migrationStatus Status of migration ( FAILURE, SUCCESS_WITH_SHARED_PREF_UPDATED,
+ * SUCCESS_WITH_SHARED_PREF_NOT_UPDATED)
+ * @param migrationType Type of migration ( PPAPI_TO_SYSTEM_SERVICE,
+ * APPSEARCH_TO_SYSTEM_SERVICE,
+ * ADEXT_SERVICE_TO_SYSTEM_SERVICE,
+ * ADEXT_SERVICE_TO_APPSEARCH)
+ * @param context Context of the application
+ * @return consentMigrationStats returns ConsentMigrationStats for logging
+ */
+ public static ConsentMigrationStats getConsentManagerStatsForLogging(
AppConsents appConsents,
ConsentMigrationStats.MigrationStatus migrationStatus,
ConsentMigrationStats.MigrationType migrationType,
@@ -2269,7 +2289,6 @@ public class ConsentManager {
ConsentMigrationStats consentMigrationStats =
ConsentMigrationStats.builder()
.setMigrationType(migrationType)
- .setMigrationStatus(migrationStatus)
// When appConsents is null we log it as a failure
.setMigrationStatus(
appConsents != null
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/ConsentManagerV2.java b/adservices/service-core/java/com/android/adservices/service/consent/ConsentManagerV2.java
new file mode 100644
index 0000000000..b321c6754c
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/consent/ConsentManagerV2.java
@@ -0,0 +1,1445 @@
+/*
+ * 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.consent;
+
+import static com.android.adservices.AdServicesCommon.ADEXTSERVICES_PACKAGE_NAME_SUFFIX;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_SEARCH_DATA_MIGRATION_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_RESET_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_WIPEOUT;
+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.IntDef;
+import android.annotation.NonNull;
+import android.app.adservices.AdServicesManager;
+import android.app.job.JobScheduler;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.data.adselection.AppInstallDao;
+import com.android.adservices.data.adselection.FrequencyCapDao;
+import com.android.adservices.data.adselection.SharedStorageDatabase;
+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.errorlogging.ErrorLogUtil;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.appsearch.AppSearchConsentStorageManager;
+import com.android.adservices.service.common.BackgroundJobsManager;
+import com.android.adservices.service.common.UserProfileIdManager;
+import com.android.adservices.service.common.compat.FileCompatUtils;
+import com.android.adservices.service.common.feature.PrivacySandboxFeatureType;
+import com.android.adservices.service.extdata.AdServicesExtDataStorageServiceManager;
+import com.android.adservices.service.measurement.MeasurementImpl;
+import com.android.adservices.service.measurement.WipeoutStatus;
+import com.android.adservices.service.stats.AdServicesLoggerImpl;
+import com.android.adservices.service.stats.ConsentMigrationStats;
+import com.android.adservices.service.stats.MeasurementWipeoutStats;
+import com.android.adservices.service.stats.StatsdAdServicesLogger;
+import com.android.adservices.service.stats.UiStatsLogger;
+import com.android.adservices.service.topics.TopicsWorker;
+import com.android.adservices.service.ui.data.UxStatesDao;
+import com.android.adservices.service.ui.enrollment.collection.PrivacySandboxEnrollmentChannelCollection;
+import com.android.adservices.service.ui.ux.collection.PrivacySandboxUxCollection;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Manager all critical user data such as per API consent.
+ *
+ * <p>For Beta the consent is given for all {@link AdServicesApiType} or for none.
+ *
+ * <p>Currently there are three types of source of truth to store consent data,
+ *
+ * <ul>
+ * <li>SYSTEM_SERVER_ONLY: Write and read consent from system server only.
+ * <li>PPAPI_ONLY: Write and read consent from PPAPI only.
+ * <li>PPAPI_AND_SYSTEM_SERVER: Write consent to both PPAPI and system server. Read consent from
+ * system server only.
+ * <li>APPSEARCH_ONLY: Write and read consent from appSearch only for back compat.
+ * <li>PPAPI_AND_ADEXT_SERVICE: Write and read consent from PPAPI and AdExt service..
+ * </ul>
+ */
+// TODO(b/279042385): move UI logs to UI.
+@RequiresApi(Build.VERSION_CODES.S)
+public class ConsentManagerV2 {
+ private static volatile ConsentManagerV2 sConsentManager;
+
+ @IntDef(value = {NO_MANUAL_INTERACTIONS_RECORDED, UNKNOWN, MANUAL_INTERACTIONS_RECORDED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserManualInteraction {}
+
+ public static final int NO_MANUAL_INTERACTIONS_RECORDED = -1;
+ public static final int UNKNOWN = 0;
+ public static final int MANUAL_INTERACTIONS_RECORDED = 1;
+
+ private final Flags mFlags;
+ private final TopicsWorker mTopicsWorker;
+ private final BooleanFileDatastore mDatastore;
+ private final EnrollmentDao mEnrollmentDao;
+ private final MeasurementImpl mMeasurementImpl;
+ private final CustomAudienceDao mCustomAudienceDao;
+ private final AppInstallDao mAppInstallDao;
+ private final FrequencyCapDao mFrequencyCapDao;
+ private final AdServicesStorageManager mAdServicesStorageManager;
+ private final AppSearchConsentStorageManager mAppSearchConsentStorageManager;
+ private final UserProfileIdManager mUserProfileIdManager;
+
+ private final AppConsentForRStorageManager mAppConsentForRStorageManager;
+
+ private static final Object LOCK = new Object();
+
+ private ConsentCompositeStorage mConsentCompositeStorage;
+
+ private AppConsentStorageManager mAppConsentStorageManager;
+
+ ConsentManagerV2(
+ @NonNull TopicsWorker topicsWorker,
+ @NonNull AppConsentDao appConsentDao,
+ @NonNull EnrollmentDao enrollmentDao,
+ @NonNull MeasurementImpl measurementImpl,
+ @NonNull CustomAudienceDao customAudienceDao,
+ @NonNull AppConsentStorageManager appConsentStorageManager,
+ @NonNull AppInstallDao appInstallDao,
+ @NonNull FrequencyCapDao frequencyCapDao,
+ @NonNull AdServicesStorageManager adServicesStorageManager,
+ @NonNull BooleanFileDatastore booleanFileDatastore,
+ @NonNull AppSearchConsentStorageManager appSearchConsentStorageManager,
+ @NonNull UserProfileIdManager userProfileIdManager,
+ @NonNull AppConsentForRStorageManager appConsentForRStorageManager,
+ @NonNull Flags flags,
+ @Flags.ConsentSourceOfTruth int consentSourceOfTruth,
+ boolean enableAppsearchConsentData,
+ boolean enableAdExtServiceConsentData) {
+ Objects.requireNonNull(topicsWorker);
+ Objects.requireNonNull(appConsentDao);
+ Objects.requireNonNull(measurementImpl);
+ Objects.requireNonNull(customAudienceDao);
+ Objects.requireNonNull(appInstallDao);
+ Objects.requireNonNull(frequencyCapDao);
+ Objects.requireNonNull(booleanFileDatastore);
+ Objects.requireNonNull(userProfileIdManager);
+
+ if (consentSourceOfTruth != Flags.PPAPI_ONLY
+ && consentSourceOfTruth != Flags.APPSEARCH_ONLY) {
+ Objects.requireNonNull(adServicesStorageManager);
+ }
+
+ if (enableAppsearchConsentData) {
+ Objects.requireNonNull(appSearchConsentStorageManager);
+ }
+
+ if (enableAdExtServiceConsentData) {
+ Objects.requireNonNull(appConsentForRStorageManager);
+ }
+
+ mAdServicesStorageManager = adServicesStorageManager;
+ mTopicsWorker = topicsWorker;
+ mDatastore = booleanFileDatastore;
+ mEnrollmentDao = enrollmentDao;
+ mMeasurementImpl = measurementImpl;
+ mCustomAudienceDao = customAudienceDao;
+ mAppInstallDao = appInstallDao;
+ mFrequencyCapDao = frequencyCapDao;
+
+ mAppSearchConsentStorageManager = appSearchConsentStorageManager;
+ mUserProfileIdManager = userProfileIdManager;
+
+ mFlags = flags;
+ mAppConsentStorageManager = appConsentStorageManager;
+ mAppConsentForRStorageManager = appConsentForRStorageManager;
+
+ mConsentCompositeStorage =
+ new ConsentCompositeStorage(getStorageListBySourceOfTruth(consentSourceOfTruth));
+ }
+
+ private ImmutableList<IConsentStorage> getStorageListBySourceOfTruth(
+ @Flags.ConsentSourceOfTruth int consentSourceOfTruth) {
+ switch (consentSourceOfTruth) {
+ case Flags.PPAPI_ONLY:
+ return ImmutableList.of(mAppConsentStorageManager);
+ case Flags.SYSTEM_SERVER_ONLY:
+ return ImmutableList.of(mAdServicesStorageManager);
+ case Flags.PPAPI_AND_SYSTEM_SERVER:
+ // System storage has higher priority
+ return ImmutableList.of(mAdServicesStorageManager, mAppConsentStorageManager);
+ case Flags.APPSEARCH_ONLY:
+ return ImmutableList.of(mAppSearchConsentStorageManager);
+ case Flags.PPAPI_AND_ADEXT_SERVICE:
+ return ImmutableList.of(mAppConsentForRStorageManager);
+ default:
+ LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
+ return ImmutableList.of();
+ }
+ }
+
+ /**
+ * Gets an instance of {@link ConsentManagerV2} to be used.
+ *
+ * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
+ * existing instance will be returned.
+ */
+ @NonNull
+ public static ConsentManagerV2 getInstance(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ if (sConsentManager == null) {
+ synchronized (LOCK) {
+ if (sConsentManager == null) {
+ // Execute one-time consent migration if needed.
+ int consentSourceOfTruth = FlagsFactory.getFlags().getConsentSourceOfTruth();
+ BooleanFileDatastore datastore = createAndInitializeDataStore(context);
+ AdServicesStorageManager adServicesManager =
+ AdServicesStorageManager.getInstance(
+ AdServicesManager.getInstance(context));
+ AppConsentDao appConsentDao = AppConsentDao.getInstance(context);
+
+ // It is possible that the old value of the flag lingers after OTA until the
+ // first PH sync. In that case, we should not use the stale value, but use the
+ // default instead. The next PH sync will restore the T+ value.
+ if (SdkLevel.isAtLeastT() && consentSourceOfTruth == Flags.APPSEARCH_ONLY) {
+ consentSourceOfTruth = Flags.DEFAULT_CONSENT_SOURCE_OF_TRUTH;
+ }
+ AppSearchConsentStorageManager appSearchConsentStorageManager = null;
+ StatsdAdServicesLogger statsdAdServicesLogger =
+ StatsdAdServicesLogger.getInstance();
+ // Flag enable_appsearch_consent_data is true on S- and T+ only when we want to
+ // use AppSearch to write to or read from.
+ boolean enableAppsearchConsentData =
+ FlagsFactory.getFlags().getEnableAppsearchConsentData();
+ if (enableAppsearchConsentData) {
+ appSearchConsentStorageManager =
+ AppSearchConsentStorageManager.getInstance();
+ handleConsentMigrationFromAppSearchIfNeeded(
+ context,
+ datastore,
+ appConsentDao,
+ appSearchConsentStorageManager,
+ adServicesManager,
+ statsdAdServicesLogger);
+ }
+ UxStatesDao uxStatesDao = UxStatesDao.getInstance(context);
+ AppConsentForRStorageManager mAppConsentForRStorageManager = null;
+ // Flag enable_adext_service_consent_data is true on R and S+ only when
+ // we want to use AdServicesExtDataStorageService to write to or read from.
+ boolean enableAdExtServiceConsentData =
+ FlagsFactory.getFlags().getEnableAdExtServiceConsentData();
+ if (enableAdExtServiceConsentData) {
+ AdServicesExtDataStorageServiceManager adServicesExtDataManager =
+ AdServicesExtDataStorageServiceManager.getInstance(context);
+ if (FlagsFactory.getFlags().getEnableAdExtServiceToAppSearchMigration()) {
+ ConsentMigrationUtils.handleConsentMigrationToAppSearchIfNeededV2(
+ context,
+ datastore,
+ appSearchConsentStorageManager,
+ adServicesExtDataManager);
+ }
+ mAppConsentForRStorageManager =
+ new AppConsentForRStorageManager(
+ datastore,
+ appConsentDao,
+ uxStatesDao,
+ adServicesExtDataManager);
+ }
+
+ // Attempt to migrate consent data from PPAPI to System server if needed.
+ handleConsentMigrationIfNeeded(
+ context,
+ datastore,
+ adServicesManager,
+ statsdAdServicesLogger,
+ consentSourceOfTruth);
+
+ AppConsentStorageManager appConsentStorageManager =
+ new AppConsentStorageManager(datastore, appConsentDao, uxStatesDao);
+ sConsentManager =
+ new ConsentManagerV2(
+ TopicsWorker.getInstance(context),
+ appConsentDao,
+ EnrollmentDao.getInstance(context),
+ MeasurementImpl.getInstance(context),
+ CustomAudienceDatabase.getInstance(context).customAudienceDao(),
+ appConsentStorageManager,
+ SharedStorageDatabase.getInstance(context).appInstallDao(),
+ SharedStorageDatabase.getInstance(context).frequencyCapDao(),
+ adServicesManager,
+ datastore,
+ appSearchConsentStorageManager,
+ UserProfileIdManager.getInstance(context),
+ // TODO(b/260601944): Remove Flag Instance.
+ mAppConsentForRStorageManager,
+ FlagsFactory.getFlags(),
+ consentSourceOfTruth,
+ enableAppsearchConsentData,
+ enableAdExtServiceConsentData);
+ }
+ }
+ }
+ return sConsentManager;
+ }
+
+ /**
+ * Enables all PP API services. It gives consent to Topics, Fledge and Measurements services.
+ *
+ * <p>To write consent to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To
+ * write to system server consent if source of truth is system server or dual sources.
+ */
+ public void enable(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ // Check current value, if it is already enabled, skip this enable process. so that the Api
+ // won't be reset. Only add this logic to "enable" not "disable", since if it already
+ // disabled, there is no harm to reset the api again.
+ if (mFlags.getConsentManagerLazyEnableMode() && getConsentFromSourceOfTruth()) {
+ LogUtil.d("CONSENT_KEY already enable. Skipping enable process.");
+ return;
+ }
+ UiStatsLogger.logOptInSelected();
+
+ BackgroundJobsManager.scheduleAllBackgroundJobs(context);
+ try {
+ // reset all state data which should be removed
+ resetTopicsAndBlockedTopics();
+ clearAllAppConsentData();
+ resetMeasurement();
+ resetUserProfileId();
+ mUserProfileIdManager.getOrCreateId();
+ } catch (IOException e) {
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e);
+ }
+ setConsentToSourceOfTruth(/* isGiven */ true);
+ }
+
+ /**
+ * Disables all PP API services. It revokes consent to Topics, Fledge and Measurements services.
+ *
+ * <p>To write consent to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To
+ * write to system server consent if source of truth is system server or dual sources.
+ */
+ public void disable(@NonNull Context context) {
+ Objects.requireNonNull(context);
+ UiStatsLogger.logOptOutSelected();
+ // Disable all the APIs
+ try {
+ // reset all data
+ resetTopicsAndBlockedTopics();
+ clearAllAppConsentData();
+ resetMeasurement();
+ resetEnrollment();
+ resetUserProfileId();
+
+ BackgroundJobsManager.unscheduleAllBackgroundJobs(
+ context.getSystemService(JobScheduler.class));
+ } catch (IOException e) {
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e);
+ }
+ setConsentToSourceOfTruth(/* isGiven */ false);
+ }
+
+ /**
+ * Enables the {@code apiType} PP API service. It gives consent to an API which is provided in
+ * the parameter.
+ *
+ * <p>To write consent to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To
+ * write to system server consent if source of truth is system server or dual sources.
+ *
+ * @param context Context of the application.
+ * @param apiType Type of the API (Topics, Fledge, Measurement) which should be enabled.
+ */
+ public void enable(@NonNull Context context, AdServicesApiType apiType) {
+ Objects.requireNonNull(context);
+ // Check current value, if it is already enabled, skip this enable process. so that the Api
+ // won't be reset.
+ if (mFlags.getConsentManagerLazyEnableMode()
+ && getPerApiConsentFromSourceOfTruth(apiType)) {
+ LogUtil.d(
+ "ApiType: is %s already enable. Skipping enable process.",
+ apiType.toPpApiDatastoreKey());
+ return;
+ }
+
+ UiStatsLogger.logOptInSelected(apiType);
+
+ BackgroundJobsManager.scheduleJobsPerApi(context, apiType);
+
+ try {
+ // reset all state data which should be removed
+ resetByApi(apiType);
+
+ if (AdServicesApiType.FLEDGE == apiType) {
+ mUserProfileIdManager.getOrCreateId();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e);
+ }
+
+ setPerApiConsentToSourceOfTruth(/* isGiven */ true, apiType);
+ }
+
+ /**
+ * Disables {@code apiType} PP API service. It revokes consent to an API which is provided in
+ * the parameter.
+ *
+ * <p>To write consent to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To
+ * write to system server consent if source of truth is system server or dual sources.
+ */
+ public void disable(@NonNull Context context, AdServicesApiType apiType) {
+ Objects.requireNonNull(context);
+
+ UiStatsLogger.logOptOutSelected(apiType);
+
+ try {
+ resetByApi(apiType);
+ BackgroundJobsManager.unscheduleJobsPerApi(
+ context.getSystemService(JobScheduler.class), apiType);
+ } catch (IOException e) {
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e);
+ }
+
+ setPerApiConsentToSourceOfTruth(/* isGiven */ false, apiType);
+
+ if (areAllApisDisabled()) {
+ BackgroundJobsManager.unscheduleAllBackgroundJobs(
+ context.getSystemService(JobScheduler.class));
+ }
+ }
+
+ private boolean areAllApisDisabled() {
+ if (getConsent(AdServicesApiType.TOPICS).isGiven()
+ || getConsent(AdServicesApiType.MEASUREMENTS).isGiven()
+ || getConsent(AdServicesApiType.FLEDGE).isGiven()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Retrieves the consent for all PP API services.
+ *
+ * <p>To read from PPAPI consent if source of truth is PPAPI. To read from system server consent
+ * if source of truth is system server or dual sources.
+ *
+ * @return AdServicesApiConsent the consent
+ */
+ public AdServicesApiConsent getConsent() {
+ if (mFlags.getConsentManagerDebugMode()) {
+ return AdServicesApiConsent.GIVEN;
+ }
+ return mConsentCompositeStorage.getConsent(AdServicesApiType.ALL_API);
+ }
+
+ /**
+ * Retrieves the consent per API.
+ *
+ * @param apiType apiType for which the consent should be provided
+ * @return {@link AdServicesApiConsent} providing information whether the consent was given or
+ * revoked.
+ */
+ public AdServicesApiConsent getConsent(AdServicesApiType apiType) {
+ if (mFlags.getConsentManagerDebugMode()) {
+ return AdServicesApiConsent.GIVEN;
+ }
+ return mConsentCompositeStorage.getConsent(apiType);
+ }
+
+ /**
+ * Returns whether the user is adult user who OTA from R.
+ *
+ * @return true if user is adult user who OTA from R, otherwise false.
+ */
+ public boolean isOtaAdultUserFromRvc() {
+ if (mFlags.getConsentManagerOTADebugMode()) {
+ return true;
+ }
+ // TODO(313672368) clean up getRvcPostOtaNotifAgeCheck flag after u18 is qualified on R/S
+ return mAppConsentForRStorageManager != null
+ && mAppConsentForRStorageManager.wasU18NotificationDisplayed()
+ && (mFlags.getRvcPostOtaNotifAgeCheck()
+ ? !mAppConsentForRStorageManager.isU18Account()
+ && mAppConsentForRStorageManager.isAdultAccount()
+ : true);
+ }
+
+ /**
+ * Proxy call to {@link TopicsWorker} to get {@link ImmutableList} of {@link Topic}s which could
+ * be returned to the {@link TopicsWorker} clients.
+ *
+ * @return {@link ImmutableList} of {@link Topic}s.
+ */
+ @NonNull
+ public ImmutableList<Topic> getKnownTopicsWithConsent() {
+ return mTopicsWorker.getKnownTopicsWithConsent();
+ }
+
+ /**
+ * Proxy call to {@link TopicsWorker} to get {@link ImmutableList} of {@link Topic}s which were
+ * blocked by the user.
+ *
+ * @return {@link ImmutableList} of blocked {@link Topic}s.
+ */
+ @NonNull
+ public ImmutableList<Topic> getTopicsWithRevokedConsent() {
+ return mTopicsWorker.getTopicsWithRevokedConsent();
+ }
+
+ /**
+ * Proxy call to {@link TopicsWorker} to revoke consent for provided {@link Topic} (block
+ * topic).
+ *
+ * @param topic {@link Topic} to block.
+ */
+ @NonNull
+ public void revokeConsentForTopic(@NonNull Topic topic) {
+ mTopicsWorker.revokeConsentForTopic(topic);
+ }
+
+ /**
+ * Proxy call to {@link TopicsWorker} to restore consent for provided {@link Topic} (unblock the
+ * topic).
+ *
+ * @param topic {@link Topic} to restore consent for.
+ */
+ @NonNull
+ public void restoreConsentForTopic(@NonNull Topic topic) {
+ mTopicsWorker.restoreConsentForTopic(topic);
+ }
+
+ /** Wipes out all the data gathered by Topics API but blocked topics. */
+ public void resetTopics() {
+ 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(new ArrayList<>());
+ }
+
+ /**
+ * @return an {@link ImmutableList} of all known apps in the database that have not had user
+ * consent revoked
+ */
+ public ImmutableList<App> getKnownAppsWithConsent() {
+ return ImmutableList.copyOf(
+ mConsentCompositeStorage.getKnownAppsWithConsent().stream()
+ .map(App::create)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * @return an {@link ImmutableList} of all known apps in the database that have had user consent
+ * revoked
+ */
+ public ImmutableList<App> getAppsWithRevokedConsent() {
+ return ImmutableList.copyOf(
+ mConsentCompositeStorage.getAppsWithRevokedConsent().stream()
+ .map(App::create)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * Proxy call to {@link AppConsentDao} to revoke consent for provided {@link App}.
+ *
+ * <p>Also clears all app data related to the provided {@link App}.
+ *
+ * @param app {@link App} to block.
+ * @throws IOException if the operation fails
+ */
+ public void revokeConsentForApp(@NonNull App app) throws IOException {
+ mConsentCompositeStorage.setConsentForApp(app.getPackageName(), true);
+
+ asyncExecute(
+ () -> mCustomAudienceDao.deleteCustomAudienceDataByOwner(app.getPackageName()));
+ if (mFlags.getFledgeAdSelectionFilteringEnabled()) {
+ asyncExecute(() -> mAppInstallDao.deleteByPackageName(app.getPackageName()));
+ asyncExecute(
+ () -> mFrequencyCapDao.deleteHistogramDataBySourceApp(app.getPackageName()));
+ }
+ }
+
+ /**
+ * Proxy call to {@link AppConsentDao} to restore consent for provided {@link App}.
+ *
+ * @param app {@link App} to restore consent for.
+ * @throws IOException if the operation fails
+ */
+ public void restoreConsentForApp(@NonNull App app) throws IOException {
+ mConsentCompositeStorage.setConsentForApp(app.getPackageName(), false);
+ }
+
+ /**
+ * Deletes all app consent data and all app data gathered or generated by the Privacy Sandbox.
+ *
+ * <p>This should be called when the Privacy Sandbox has been disabled.
+ *
+ * @throws IOException if the operation fails
+ */
+ public void clearAllAppConsentData() throws IOException {
+ mConsentCompositeStorage.clearAllAppConsentData();
+
+ asyncExecute(mCustomAudienceDao::deleteAllCustomAudienceData);
+ if (mFlags.getFledgeAdSelectionFilteringEnabled()) {
+ asyncExecute(mAppInstallDao::deleteAllAppInstallData);
+ asyncExecute(mFrequencyCapDao::deleteAllHistogramData);
+ }
+ }
+
+ /**
+ * Deletes the list of known allowed apps as well as all app data from the Privacy Sandbox.
+ *
+ * <p>The list of blocked apps is not reset.
+ *
+ * @throws IOException if the operation fails
+ */
+ public void clearKnownAppsWithConsent() throws IOException {
+ mConsentCompositeStorage.clearKnownAppsWithConsent();
+ asyncExecute(mCustomAudienceDao::deleteAllCustomAudienceData);
+ if (mFlags.getFledgeAdSelectionFilteringEnabled()) {
+ asyncExecute(mAppInstallDao::deleteAllAppInstallData);
+ asyncExecute(mFrequencyCapDao::deleteAllHistogramData);
+ }
+ }
+
+ /**
+ * Checks whether a single given installed application (identified by its package name) has had
+ * user consent to use the FLEDGE APIs revoked.
+ *
+ * <p>This method also checks whether a user has opted out of the FLEDGE Privacy Sandbox
+ * initiative.
+ *
+ * @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
+ * the user has revoked consent for the given application to use the FLEDGE APIs
+ * @throws IllegalArgumentException if the package name is invalid or not found as an installed
+ * application
+ */
+ public boolean isFledgeConsentRevokedForApp(@NonNull String packageName)
+ throws IllegalArgumentException {
+ AdServicesApiConsent consent = getConsent(AdServicesApiType.FLEDGE);
+
+ if (!consent.isGiven()) {
+ return true;
+ }
+
+ return mConsentCompositeStorage.isConsentRevokedForApp(packageName);
+ }
+
+ /**
+ * Persists the use of a FLEDGE API by a single given installed application (identified by its
+ * package name) if the app has not already had its consent revoked.
+ *
+ * <p>This method also checks whether a user has opted out of the FLEDGE Privacy Sandbox
+ * initiative.
+ *
+ * <p>This is only meant to be called by the FLEDGE APIs.
+ *
+ * @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
+ * false} otherwise
+ * @throws IllegalArgumentException if the package name is invalid or not found as an installed
+ * application
+ */
+ public boolean isFledgeConsentRevokedForAppAfterSettingFledgeUse(@NonNull String packageName)
+ throws IllegalArgumentException {
+ AdServicesApiConsent consent = getConsent(AdServicesApiType.FLEDGE);
+
+ if (!consent.isGiven()) {
+ return true;
+ }
+
+ return mConsentCompositeStorage.setConsentForAppIfNew(packageName, false);
+ }
+
+ /**
+ * Clear consent data after an app was uninstalled.
+ *
+ * @param packageName the package name that had been uninstalled.
+ */
+ public void clearConsentForUninstalledApp(String packageName, int packageUid) {
+ mConsentCompositeStorage.clearConsentForUninstalledApp(packageName, packageUid);
+ }
+
+ /**
+ * Clear consent data after an app was uninstalled, but the package Uid is unavailable. This
+ * could happen because the INTERACT_ACROSS_USERS_FULL permission is not available on Android
+ * versions prior to T.
+ *
+ * <p><strong>This method should only be used for R/S back-compat scenarios.</strong>
+ *
+ * @param packageName the package name that had been uninstalled.
+ */
+ public void clearConsentForUninstalledApp(@NonNull String packageName) {
+ mConsentCompositeStorage.clearConsentForUninstalledApp(packageName);
+ }
+
+ /** Wipes out all the data gathered by Measurement API. */
+ public void resetMeasurement() {
+ mMeasurementImpl.deleteAllMeasurementData(List.of());
+ // Log wipeout event triggered by consent flip to delete data of package
+ WipeoutStatus wipeoutStatus = new WipeoutStatus();
+ wipeoutStatus.setWipeoutType(WipeoutStatus.WipeoutType.CONSENT_FLIP);
+ logWipeoutStats(wipeoutStatus);
+ }
+
+ /** Wipes out all the Enrollment data */
+ @VisibleForTesting
+ void resetEnrollment() {
+ mEnrollmentDao.deleteAll();
+ }
+
+ /**
+ * Saves information to the storage that notification was displayed for the first time to the
+ * user.
+ */
+ public void recordNotificationDisplayed(boolean wasNotificationDisplayed) {
+ mConsentCompositeStorage.recordNotificationDisplayed(wasNotificationDisplayed);
+ }
+
+ /**
+ * Retrieves if notification has been displayed.
+ *
+ * @return true if Consent Notification was displayed, otherwise false.
+ */
+ public Boolean wasNotificationDisplayed() {
+ return mConsentCompositeStorage.wasNotificationDisplayed();
+ }
+
+ /**
+ * Saves information to the storage that GA UX notification was displayed for the first time to
+ * the user.
+ */
+ public void recordGaUxNotificationDisplayed(boolean wasGaUxDisplayed) {
+ mConsentCompositeStorage.recordGaUxNotificationDisplayed(wasGaUxDisplayed);
+ }
+
+ /**
+ * Retrieves if GA UX notification has been displayed.
+ *
+ * @return true if GA UX Consent Notification was displayed, otherwise false.
+ */
+ public Boolean wasGaUxNotificationDisplayed() {
+ return mConsentCompositeStorage.wasGaUxNotificationDisplayed();
+ }
+
+ /**
+ * Retrieves the PP API default consent.
+ *
+ * @return true if the topics default consent is true, false otherwise.
+ */
+ public Boolean getDefaultConsent() {
+ return mConsentCompositeStorage.getDefaultConsent(AdServicesApiType.ALL_API).isGiven();
+ }
+
+ /**
+ * Retrieves the topics default consent.
+ *
+ * @return true if the topics default consent is true, false otherwise.
+ */
+ public Boolean getTopicsDefaultConsent() {
+ return mConsentCompositeStorage.getDefaultConsent(AdServicesApiType.TOPICS).isGiven();
+ }
+
+ /**
+ * Retrieves the FLEDGE default consent.
+ *
+ * @return true if the FLEDGE default consent is true, false otherwise.
+ */
+ public Boolean getFledgeDefaultConsent() {
+ return mConsentCompositeStorage.getDefaultConsent(AdServicesApiType.FLEDGE).isGiven();
+ }
+
+ /**
+ * Retrieves the measurement default consent.
+ *
+ * @return true if the measurement default consent is true, false otherwise.
+ */
+ public Boolean getMeasurementDefaultConsent() {
+ return mConsentCompositeStorage.getDefaultConsent(AdServicesApiType.MEASUREMENTS).isGiven();
+ }
+
+ /**
+ * Retrieves the default AdId state.
+ *
+ * @return true if the AdId is enabled by default, false otherwise.
+ */
+ public Boolean getDefaultAdIdState() {
+ return mConsentCompositeStorage.getDefaultAdIdState();
+ }
+
+ /** Saves the default consent bit to data stores based on source of truth. */
+ public void recordDefaultConsent(boolean defaultConsent) {
+ mConsentCompositeStorage.recordDefaultConsent(AdServicesApiType.ALL_API, defaultConsent);
+ }
+
+ /** Saves the topics default consent bit to data stores based on source of truth. */
+ public void recordTopicsDefaultConsent(boolean defaultConsent) {
+ mConsentCompositeStorage.recordDefaultConsent(AdServicesApiType.TOPICS, defaultConsent);
+ }
+
+ /** Saves the FLEDGE default consent bit to data stores based on source of truth. */
+ public void recordFledgeDefaultConsent(boolean defaultConsent) {
+ mConsentCompositeStorage.recordDefaultConsent(AdServicesApiType.FLEDGE, defaultConsent);
+ }
+
+ /** Saves the measurement default consent bit to data stores based on source of truth. */
+ public void recordMeasurementDefaultConsent(boolean defaultConsent) {
+ mConsentCompositeStorage.recordDefaultConsent(
+ AdServicesApiType.MEASUREMENTS, defaultConsent);
+ }
+
+ /** Saves the default AdId state bit to data stores based on source of truth. */
+ public void recordDefaultAdIdState(boolean defaultAdIdState) {
+ mConsentCompositeStorage.recordDefaultAdIdState(defaultAdIdState);
+ }
+
+ /** Set the current privacy sandbox feature. */
+ public void setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType currentFeatureType) {
+ mConsentCompositeStorage.setCurrentPrivacySandboxFeature(currentFeatureType);
+ }
+
+ /** Saves information to the storage that user interacted with consent manually. */
+ public void recordUserManualInteractionWithConsent(@UserManualInteraction int interaction) {
+ mConsentCompositeStorage.recordUserManualInteractionWithConsent(interaction);
+ }
+
+ /**
+ * Get the current privacy sandbox feature.
+ *
+ * <p>To write to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To write to
+ * system server if consent source of truth is SYSTEM_SERVER_ONLY or dual sources.
+ */
+ public PrivacySandboxFeatureType getCurrentPrivacySandboxFeature() {
+ return mConsentCompositeStorage.getCurrentPrivacySandboxFeature();
+ }
+
+ /**
+ * Returns information whether user interacted with consent manually.
+ *
+ * @return true if the user interacted with the consent manually, otherwise false.
+ */
+ public @UserManualInteraction int getUserManualInteractionWithConsent() {
+ return mConsentCompositeStorage.getUserManualInteractionWithConsent();
+ }
+
+ @VisibleForTesting
+ static BooleanFileDatastore createAndInitializeDataStore(@NonNull Context context) {
+ BooleanFileDatastore booleanFileDatastore =
+ new BooleanFileDatastore(
+ context,
+ ConsentConstants.STORAGE_XML_IDENTIFIER,
+ ConsentConstants.STORAGE_VERSION);
+
+ try {
+ booleanFileDatastore.initialize();
+ // TODO(b/259607624): implement a method in the datastore which would support
+ // this exact scenario - if the value is null, return default value provided
+ // in the parameter (similar to SP apply etc.)
+ if (booleanFileDatastore.get(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE) == null) {
+ booleanFileDatastore.put(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE, false);
+ }
+ if (booleanFileDatastore.get(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE)
+ == null) {
+ booleanFileDatastore.put(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE, false);
+ }
+ } catch (IOException | IllegalArgumentException | NullPointerException e) {
+ throw new RuntimeException("Failed to initialize the File Datastore!", e);
+ }
+
+ return booleanFileDatastore;
+ }
+
+ // Handle different migration requests based on current consent source of Truth
+ // PPAPI_ONLY: reset the shared preference to reset status of migrating consent from PPAPI to
+ // system server.
+ // PPAPI_AND_SYSTEM_SERVER: migrate consent from PPAPI to system server.
+ // SYSTEM_SERVER_ONLY: migrate consent from PPAPI to system server and clear PPAPI consent
+ @VisibleForTesting
+ static void handleConsentMigrationIfNeeded(
+ @NonNull Context context,
+ @NonNull BooleanFileDatastore datastore,
+ AdServicesStorageManager adServicesManager,
+ @NonNull StatsdAdServicesLogger statsdAdServicesLogger,
+ @Flags.ConsentSourceOfTruth int consentSourceOfTruth) {
+ Objects.requireNonNull(context);
+ // On R/S, handleConsentMigrationIfNeeded should never be executed.
+ // It is a T+ feature. On T+, this function should only execute if it's within the
+ // AdServices
+ // APK and not ExtServices. So check if it's within ExtServices, and bail out if that's the
+ // case on any platform.
+ String packageName = context.getPackageName();
+ if (packageName != null && packageName.endsWith(ADEXTSERVICES_PACKAGE_NAME_SUFFIX)) {
+ LogUtil.d("Aborting attempt to migrate consent in ExtServices");
+ return;
+ }
+ Objects.requireNonNull(datastore);
+ if (consentSourceOfTruth == Flags.PPAPI_AND_SYSTEM_SERVER
+ || consentSourceOfTruth == Flags.SYSTEM_SERVER_ONLY) {
+ Objects.requireNonNull(adServicesManager);
+ }
+
+ switch (consentSourceOfTruth) {
+ case Flags.PPAPI_ONLY:
+ // Technically we only need to reset the SHARED_PREFS_KEY_HAS_MIGRATED bit once.
+ // What we need is clearIfSet operation which is not available in SP. So here we
+ // always reset the bit since otherwise we need to read the SP to read the value and
+ // the clear the value.
+ // The only flow we would do are:
+ // Case 1: DUAL-> PPAPI if there is a bug in System Server
+ // Case 2: DUAL -> SYSTEM_SERVER_ONLY: if everything goes smoothly.
+ resetSharedPreference(context, ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED);
+ break;
+ case Flags.PPAPI_AND_SYSTEM_SERVER:
+ migratePpApiConsentToSystemService(
+ context, datastore, adServicesManager, statsdAdServicesLogger);
+ break;
+ case Flags.SYSTEM_SERVER_ONLY:
+ migratePpApiConsentToSystemService(
+ context, datastore, adServicesManager, statsdAdServicesLogger);
+ clearPpApiConsent(context, datastore);
+ break;
+ case Flags.APPSEARCH_ONLY:
+ // If this is an S- device, the consent source of truth is always APPSEARCH_ONLY.
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Reset data for the specific AdServicesApiType
+ @VisibleForTesting
+ void resetByApi(AdServicesApiType apiType) throws IOException {
+ switch (apiType) {
+ case TOPICS:
+ resetTopicsAndBlockedTopics();
+ break;
+ case FLEDGE:
+ clearAllAppConsentData();
+ resetUserProfileId();
+ break;
+ case MEASUREMENTS:
+ resetMeasurement();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void resetUserProfileId() {
+ mUserProfileIdManager.deleteId();
+ }
+
+ // Perform a one-time migration to migrate existing PPAPI Consent
+ @VisibleForTesting
+ // Suppress lint warning for context.getUser in R since this code is unused in R
+ @SuppressWarnings("NewApi")
+ static void migratePpApiConsentToSystemService(
+ @NonNull Context context,
+ @NonNull BooleanFileDatastore datastore,
+ @NonNull AdServicesStorageManager adServicesManager,
+ @NonNull StatsdAdServicesLogger statsdAdServicesLogger) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(datastore);
+ Objects.requireNonNull(adServicesManager);
+
+ AppConsents appConsents = null;
+ try {
+ // Exit if migration has happened.
+ SharedPreferences sharedPreferences =
+ FileCompatUtils.getSharedPreferencesHelper(
+ context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
+ // If we migrated data to system server either from PPAPI or from AppSearch, do not
+ // attempt another migration of data to system server.
+ boolean shouldSkipMigration =
+ sharedPreferences.getBoolean(
+ ConsentConstants.SHARED_PREFS_KEY_APPSEARCH_HAS_MIGRATED,
+ /* default= */ false)
+ || sharedPreferences.getBoolean(
+ ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED,
+ /* default= */ false);
+ if (shouldSkipMigration) {
+ LogUtil.v(
+ "Consent migration has happened to user %d, skip...",
+ context.getUser().getIdentifier());
+ return;
+ }
+ LogUtil.d("Started migrating Consent from PPAPI to System Service");
+
+ Boolean consentKey = Boolean.TRUE.equals(datastore.get(ConsentConstants.CONSENT_KEY));
+
+ // Migrate Consent and Notification Displayed to System Service.
+ // Set consent enabled only when value is TRUE. FALSE and null are regarded as disabled.
+ adServicesManager.setConsent(AdServicesApiType.ALL_API, consentKey);
+ // Set notification displayed only when value is TRUE. FALSE and null are regarded as
+ // not displayed.
+ if (Boolean.TRUE.equals(datastore.get(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE))) {
+ adServicesManager.recordNotificationDisplayed(true);
+ }
+
+ Boolean manualInteractionRecorded =
+ datastore.get(ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED);
+ if (manualInteractionRecorded != null) {
+ adServicesManager.recordUserManualInteractionWithConsent(
+ manualInteractionRecorded ? 1 : -1);
+ }
+
+ // Save migration has happened into shared preferences.
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED, true);
+ appConsents =
+ AppConsents.builder()
+ .setDefaultConsent(consentKey)
+ .setMsmtConsent(consentKey)
+ .setFledgeConsent(consentKey)
+ .setTopicsConsent(consentKey)
+ .build();
+
+ if (editor.commit()) {
+ LogUtil.d("Finished migrating Consent from PPAPI to System Service");
+ statsdAdServicesLogger.logConsentMigrationStats(
+ getConsentManagerStatsForLogging(
+ appConsents,
+ ConsentMigrationStats.MigrationStatus
+ .SUCCESS_WITH_SHARED_PREF_UPDATED,
+ ConsentMigrationStats.MigrationType.PPAPI_TO_SYSTEM_SERVICE,
+ context));
+ } else {
+ LogUtil.e(
+ "Finished migrating Consent from PPAPI to System Service but shared"
+ + " preference is not updated.");
+ ErrorLogUtil.e(
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ statsdAdServicesLogger.logConsentMigrationStats(
+ getConsentManagerStatsForLogging(
+ appConsents,
+ ConsentMigrationStats.MigrationStatus
+ .SUCCESS_WITH_SHARED_PREF_NOT_UPDATED,
+ ConsentMigrationStats.MigrationType.PPAPI_TO_SYSTEM_SERVICE,
+ context));
+ }
+ } catch (Exception e) {
+ LogUtil.e("PPAPI consent data migration failed: ", e);
+ statsdAdServicesLogger.logConsentMigrationStats(
+ getConsentManagerStatsForLogging(
+ appConsents,
+ ConsentMigrationStats.MigrationStatus.FAILURE,
+ ConsentMigrationStats.MigrationType.PPAPI_TO_SYSTEM_SERVICE,
+ context));
+ }
+ }
+
+ // Clear PPAPI Consent if fully migrated to use system server consent. This is because system
+ // consent cannot be migrated back to PPAPI. This data clearing should only happen once.
+ @VisibleForTesting
+ static void clearPpApiConsent(
+ @NonNull Context context, @NonNull BooleanFileDatastore datastore) {
+ // Exit if PPAPI consent has cleared.
+ SharedPreferences sharedPreferences =
+ FileCompatUtils.getSharedPreferencesHelper(
+ context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
+ if (sharedPreferences.getBoolean(
+ ConsentConstants.SHARED_PREFS_KEY_PPAPI_HAS_CLEARED, /* defValue */ false)) {
+ return;
+ }
+
+ LogUtil.d("Started clearing Consent in PPAPI.");
+
+ try {
+ datastore.clear();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to clear PPAPI Consent", e);
+ }
+
+ // Save that PPAPI consent has cleared into shared preferences.
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(ConsentConstants.SHARED_PREFS_KEY_PPAPI_HAS_CLEARED, true);
+
+ if (editor.commit()) {
+ LogUtil.d("Finished clearing Consent in PPAPI.");
+ } else {
+ LogUtil.e("Finished clearing Consent in PPAPI but shared preference is not updated.");
+ ErrorLogUtil.e(
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ }
+ }
+
+ // Set the shared preference to false for given key.
+ @VisibleForTesting
+ static void resetSharedPreference(
+ @NonNull Context context, @NonNull String sharedPreferenceKey) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(sharedPreferenceKey);
+
+ SharedPreferences sharedPreferences =
+ FileCompatUtils.getSharedPreferencesHelper(
+ context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(sharedPreferenceKey, false);
+
+ if (editor.commit()) {
+ LogUtil.d("Finished resetting shared preference for " + sharedPreferenceKey);
+ } else {
+ LogUtil.e("Failed to reset shared preference for " + sharedPreferenceKey);
+ ErrorLogUtil.e(
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_RESET_FAILURE,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ }
+ }
+
+ // To write to PPAPI if consent source of truth is PPAPI_ONLY or dual sources.
+ // To write to system server if consent source of truth is SYSTEM_SERVER_ONLY or dual sources.
+ @VisibleForTesting
+ void setConsentToSourceOfTruth(boolean isGiven) {
+ mConsentCompositeStorage.setConsent(AdServicesApiType.ALL_API, isGiven);
+ }
+
+ @VisibleForTesting
+ boolean getConsentFromSourceOfTruth() {
+ return mConsentCompositeStorage.getConsent(AdServicesApiType.ALL_API).isGiven();
+ }
+
+ @VisibleForTesting
+ boolean getPerApiConsentFromSourceOfTruth(AdServicesApiType apiType) {
+ return mConsentCompositeStorage.getConsent(apiType).isGiven();
+ }
+
+ @VisibleForTesting
+ void setPerApiConsentToSourceOfTruth(boolean isGiven, AdServicesApiType apiType) {
+ mConsentCompositeStorage.setConsent(apiType, isGiven);
+ }
+
+ private static void storeUserManualInteractionToPpApi(
+ @ConsentManagerV2.UserManualInteraction int interaction, BooleanFileDatastore datastore)
+ throws IOException {
+ switch (interaction) {
+ case NO_MANUAL_INTERACTIONS_RECORDED:
+ datastore.put(ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED, false);
+ break;
+ case UNKNOWN:
+ datastore.remove(ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED);
+ break;
+ case MANUAL_INTERACTIONS_RECORDED:
+ datastore.put(ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED, true);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ String.format("InteractionId < %d > can not be handled.", interaction));
+ }
+ }
+
+ /**
+ * This method handles migration of consent data from AppSearch to AdServices. Consent data is
+ * written to AppSearch on S- and ported to AdServices after OTA to T. If any new data is
+ * written for consent, we need to make sure it is migrated correctly post-OTA in this method.
+ */
+ @VisibleForTesting
+ static void handleConsentMigrationFromAppSearchIfNeeded(
+ @NonNull Context context,
+ @NonNull BooleanFileDatastore datastore,
+ @NonNull AppConsentDao appConsentDao,
+ @NonNull AppSearchConsentStorageManager appSearchConsentStorageManager,
+ @NonNull AdServicesStorageManager adServicesStorageManager,
+ @NonNull StatsdAdServicesLogger statsdAdServicesLogger) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(appSearchConsentStorageManager);
+ LogUtil.d("Check migrating Consent from AppSearch to PPAPI and System Service");
+
+ // On R/S, this function should never be executed because AppSearch to PPAPI and
+ // System Server migration is a T+ feature. On T+, this function should only execute
+ // if it's within the AdServices APK and not ExtServices. So check if it's within
+ // ExtServices, and bail out if that's the case on any platform.
+ String packageName = context.getPackageName();
+ if (packageName != null && packageName.endsWith(ADEXTSERVICES_PACKAGE_NAME_SUFFIX)) {
+ LogUtil.d(
+ "Aborting attempt to migrate AppSearch to PPAPI and System Service in"
+ + " ExtServices");
+ return;
+ }
+
+ AppConsents appConsents = null;
+ try {
+ // This should be called only once after OTA (if flag is enabled). If we did not record
+ // showing the notification on T+ yet and we have shown the notification on S- (as
+ // recorded
+ // in AppSearch), initialize T+ consent data so that we don't show notification twice
+ // (after
+ // OTA upgrade).
+ SharedPreferences sharedPreferences =
+ FileCompatUtils.getSharedPreferencesHelper(
+ context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
+ // If we did not migrate notification data, we should not attempt to migrate anything.
+ if (!appSearchConsentStorageManager.migrateConsentDataIfNeeded(
+ sharedPreferences, datastore, adServicesStorageManager, appConsentDao)) {
+ LogUtil.d("Skipping consent migration from AppSearch");
+ return;
+ }
+ // Migrate Consent for all APIs and per API to PP API and System Service.
+ appConsents =
+ migrateAppSearchConsents(
+ appSearchConsentStorageManager, adServicesStorageManager, datastore);
+ // Record interactions data only if we recorded an interaction in AppSearch.
+ int manualInteractionRecorded =
+ appSearchConsentStorageManager.getUserManualInteractionWithConsent();
+ if (manualInteractionRecorded == MANUAL_INTERACTIONS_RECORDED) {
+ // Initialize PP API datastore.
+ storeUserManualInteractionToPpApi(MANUAL_INTERACTIONS_RECORDED, datastore);
+ // Initialize system service.
+ adServicesStorageManager.recordUserManualInteractionWithConsent(
+ manualInteractionRecorded);
+ }
+
+ // Record that we migrated consent data from AppSearch. We write the notification data
+ // to system server and perform migration only if system server did not record any
+ // notification having been displayed.
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(ConsentConstants.SHARED_PREFS_KEY_APPSEARCH_HAS_MIGRATED, true);
+ if (editor.commit()) {
+ LogUtil.d("Finished migrating Consent from AppSearch to PPAPI + System Service");
+ statsdAdServicesLogger.logConsentMigrationStats(
+ getConsentManagerStatsForLogging(
+ appConsents,
+ ConsentMigrationStats.MigrationStatus
+ .SUCCESS_WITH_SHARED_PREF_UPDATED,
+ ConsentMigrationStats.MigrationType.APPSEARCH_TO_SYSTEM_SERVICE,
+ context));
+ } else {
+ LogUtil.e(
+ "Finished migrating Consent from AppSearch to PPAPI + System Service "
+ + "but shared preference is not updated.");
+ statsdAdServicesLogger.logConsentMigrationStats(
+ getConsentManagerStatsForLogging(
+ appConsents,
+ ConsentMigrationStats.MigrationStatus
+ .SUCCESS_WITH_SHARED_PREF_NOT_UPDATED,
+ ConsentMigrationStats.MigrationType.APPSEARCH_TO_SYSTEM_SERVICE,
+ context));
+ }
+ } catch (IOException e) {
+ LogUtil.e("AppSearch consent data migration failed: ", e);
+ ErrorLogUtil.e(
+ e,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_SEARCH_DATA_MIGRATION_FAILURE,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
+ statsdAdServicesLogger.logConsentMigrationStats(
+ getConsentManagerStatsForLogging(
+ appConsents,
+ ConsentMigrationStats.MigrationStatus.FAILURE,
+ ConsentMigrationStats.MigrationType.APPSEARCH_TO_SYSTEM_SERVICE,
+ context));
+ }
+ }
+
+ /**
+ * This method returns and migrates the consent states (opt in/out) for all PPAPIs, each API and
+ * their default consent values.
+ */
+ @VisibleForTesting
+ static AppConsents migrateAppSearchConsents(
+ AppSearchConsentStorageManager appSearchConsentManager,
+ AdServicesStorageManager adServicesManager,
+ BooleanFileDatastore datastore)
+ throws IOException {
+ Map<String, Boolean> consentMap = new HashMap<>();
+ for (AdServicesApiType apiType : AdServicesApiType.values()) {
+ if (apiType == AdServicesApiType.UNKNOWN) {
+ continue;
+ }
+ boolean consented = appSearchConsentManager.getConsent(apiType).isGiven();
+ datastore.put(apiType.toPpApiDatastoreKey(), consented);
+ adServicesManager.setConsent(apiType, consented);
+ boolean defaultConsent = appSearchConsentManager.getDefaultConsent(apiType).isGiven();
+ datastore.put(apiType.toDefaultConsentDatastoreKey(), defaultConsent);
+ adServicesManager.recordDefaultConsent(apiType, defaultConsent);
+ consentMap.put(apiType.toPpApiDatastoreKey(), consented);
+ consentMap.put(apiType.toDefaultConsentDatastoreKey(), defaultConsent);
+ }
+ return AppConsents.builder()
+ .setMsmtConsent(
+ consentMap.get(AdServicesApiType.MEASUREMENTS.toPpApiDatastoreKey()))
+ .setTopicsConsent(consentMap.get(AdServicesApiType.TOPICS.toPpApiDatastoreKey()))
+ .setFledgeConsent(consentMap.get(AdServicesApiType.FLEDGE.toPpApiDatastoreKey()))
+ .setDefaultConsent(
+ consentMap.get(AdServicesApiType.ALL_API.toDefaultConsentDatastoreKey()))
+ .build();
+ }
+
+ /**
+ * Represents revoked consent as internally determined by the PP APIs.
+ *
+ * <p>This is an internal-only exception and is not meant to be returned to external callers.
+ */
+ public static class RevokedConsentException extends IllegalStateException {
+ public static final String REVOKED_CONSENT_ERROR_MESSAGE =
+ "Error caused by revoked user consent";
+
+ /** Creates an instance of a {@link RevokedConsentException}. */
+ public RevokedConsentException() {
+ super(REVOKED_CONSENT_ERROR_MESSAGE);
+ }
+ }
+
+ private void asyncExecute(Runnable runnable) {
+ AdServicesExecutors.getBackgroundExecutor().execute(runnable);
+ }
+
+ private void logWipeoutStats(WipeoutStatus wipeoutStatus) {
+ AdServicesLoggerImpl.getInstance()
+ .logMeasurementWipeoutStats(
+ new MeasurementWipeoutStats.Builder()
+ .setCode(AD_SERVICES_MEASUREMENT_WIPEOUT)
+ .setWipeoutType(wipeoutStatus.getWipeoutType().getValue())
+ .build());
+ }
+
+ /** Returns whether the isAdIdEnabled bit is true based on consent_source_of_truth. */
+ public Boolean isAdIdEnabled() {
+ return mConsentCompositeStorage.isAdIdEnabled();
+ }
+
+ /** Set the AdIdEnabled bit to storage based on consent_source_of_truth. */
+ public void setAdIdEnabled(boolean isAdIdEnabled) {
+ mConsentCompositeStorage.setAdIdEnabled(isAdIdEnabled);
+ }
+
+ /** Returns whether the isU18Account bit is true based on consent_source_of_truth. */
+ public Boolean isU18Account() {
+ return mConsentCompositeStorage.isU18Account();
+ }
+
+ /** Set the U18Account bit to storage based on consent_source_of_truth. */
+ public void setU18Account(boolean isU18Account) {
+ mConsentCompositeStorage.setU18Account(isU18Account);
+ }
+
+ /** Returns whether the isEntryPointEnabled bit is true based on consent_source_of_truth. */
+ public Boolean isEntryPointEnabled() {
+ return mConsentCompositeStorage.isEntryPointEnabled();
+ }
+
+ /** Set the EntryPointEnabled bit to storage based on consent_source_of_truth. */
+ public void setEntryPointEnabled(boolean isEntryPointEnabled) {
+ mConsentCompositeStorage.setEntryPointEnabled(isEntryPointEnabled);
+ }
+
+ /** Returns whether the isAdultAccount bit is true based on consent_source_of_truth. */
+ public Boolean isAdultAccount() {
+ return mConsentCompositeStorage.isAdultAccount();
+ }
+
+ /** Set the AdultAccount bit to storage based on consent_source_of_truth. */
+ public void setAdultAccount(boolean isAdultAccount) {
+ mConsentCompositeStorage.setAdultAccount(isAdultAccount);
+ }
+
+ /**
+ * Returns whether the wasU18NotificationDisplayed bit is true based on consent_source_of_truth.
+ */
+ public Boolean wasU18NotificationDisplayed() {
+ return mConsentCompositeStorage.wasU18NotificationDisplayed();
+ }
+
+ /** Set the U18NotificationDisplayed bit to storage based on consent_source_of_truth. */
+ public void setU18NotificationDisplayed(boolean wasU18NotificationDisplayed) {
+ mConsentCompositeStorage.setU18NotificationDisplayed(wasU18NotificationDisplayed);
+ }
+
+ /** Returns current UX based on consent_source_of_truth. */
+ public PrivacySandboxUxCollection getUx() {
+ return mConsentCompositeStorage.getUx();
+ }
+
+ /** Set the current UX to storage based on consent_source_of_truth. */
+ public void setUx(PrivacySandboxUxCollection ux) {
+ mConsentCompositeStorage.setUx(ux);
+ }
+
+ /** Returns current enrollment channel based on consent_source_of_truth. */
+ public PrivacySandboxEnrollmentChannelCollection getEnrollmentChannel(
+ PrivacySandboxUxCollection ux) {
+ return mConsentCompositeStorage.getEnrollmentChannel(ux);
+ }
+
+ /** Set the current enrollment channel to storage based on consent_source_of_truth. */
+ public void setEnrollmentChannel(
+ PrivacySandboxUxCollection ux, PrivacySandboxEnrollmentChannelCollection channel) {
+ mConsentCompositeStorage.setEnrollmentChannel(ux, channel);
+ }
+
+ @VisibleForTesting
+ void setConsentToPpApi(boolean isGiven) throws IOException {
+ mDatastore.put(ConsentConstants.CONSENT_KEY, isGiven);
+ }
+
+ /* Returns the region od the device */
+ private static int getConsentRegion(@NonNull Context context) {
+ return DeviceRegionProvider.isEuDevice(context)
+ ? AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU
+ : AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
+ }
+
+ /* Returns an object of ConsentMigrationStats */
+ private static ConsentMigrationStats getConsentManagerStatsForLogging(
+ AppConsents appConsents,
+ ConsentMigrationStats.MigrationStatus migrationStatus,
+ ConsentMigrationStats.MigrationType migrationType,
+ Context context) {
+ ConsentMigrationStats consentMigrationStats =
+ ConsentMigrationStats.builder()
+ .setMigrationType(migrationType)
+ .setMigrationStatus(migrationStatus)
+ // When appConsents is null we log it as a failure
+ .setMigrationStatus(
+ appConsents != null
+ ? migrationStatus
+ : ConsentMigrationStats.MigrationStatus.FAILURE)
+ .setMsmtConsent(appConsents == null || appConsents.getMsmtConsent())
+ .setTopicsConsent(appConsents == null || appConsents.getTopicsConsent())
+ .setFledgeConsent(appConsents == null || appConsents.getFledgeConsent())
+ .setDefaultConsent(appConsents == null || appConsents.getDefaultConsent())
+ .setRegion(getConsentRegion(context))
+ .build();
+ return consentMigrationStats;
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/ConsentMigrationUtils.java b/adservices/service-core/java/com/android/adservices/service/consent/ConsentMigrationUtils.java
index d5ccb28f37..9bd7afd617 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/ConsentMigrationUtils.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/ConsentMigrationUtils.java
@@ -20,6 +20,8 @@ import static android.adservices.extdata.AdServicesExtDataParams.BOOLEAN_TRUE;
import static android.adservices.extdata.AdServicesExtDataParams.BOOLEAN_UNKNOWN;
import static android.adservices.extdata.AdServicesExtDataParams.STATE_MANUAL_INTERACTIONS_RECORDED;
+import static com.android.adservices.service.consent.ConsentManager.getConsentManagerStatsForLogging;
+
import android.adservices.extdata.AdServicesExtDataParams;
import android.annotation.NonNull;
import android.annotation.TargetApi;
@@ -32,8 +34,11 @@ import androidx.annotation.Nullable;
import com.android.adservices.LogUtil;
import com.android.adservices.data.common.BooleanFileDatastore;
import com.android.adservices.service.appsearch.AppSearchConsentManager;
+import com.android.adservices.service.appsearch.AppSearchConsentStorageManager;
import com.android.adservices.service.common.compat.FileCompatUtils;
import com.android.adservices.service.extdata.AdServicesExtDataStorageServiceManager;
+import com.android.adservices.service.stats.ConsentMigrationStats;
+import com.android.adservices.service.stats.StatsdAdServicesLogger;
import com.android.modules.utils.build.SdkLevel;
import java.util.Objects;
@@ -50,10 +55,77 @@ public final class ConsentMigrationUtils {
* as it's the new consent source of truth. If any new data is written for consent, we need to
* make sure it is migrated correctly post-OTA in this method.
*/
+ @TargetApi(Build.VERSION_CODES.S)
public static void handleConsentMigrationToAppSearchIfNeeded(
@NonNull Context context,
@NonNull BooleanFileDatastore datastore,
@Nullable AppSearchConsentManager appSearchConsentManager,
+ @Nullable AdServicesExtDataStorageServiceManager adExtDataManager,
+ @Nullable StatsdAdServicesLogger statsdAdServicesLogger) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(datastore);
+ Objects.requireNonNull(statsdAdServicesLogger);
+ LogUtil.d("Check if consent migration to AppSearch is needed.");
+ AppConsents appConsents = null;
+ try {
+ SharedPreferences sharedPreferences =
+ FileCompatUtils.getSharedPreferencesHelper(
+ context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
+
+ if (!isMigrationToAppSearchNeeded(
+ context, sharedPreferences, appSearchConsentManager, adExtDataManager)) {
+ LogUtil.d("Skipping consent migration to AppSearch");
+ return;
+ }
+
+ // Reduce number of read calls by fetching all the AdExt data at once.
+ AdServicesExtDataParams dataFromR = adExtDataManager.getAdServicesExtData();
+ if (dataFromR.getIsNotificationDisplayed() != BOOLEAN_TRUE) {
+ LogUtil.d("Skipping consent migration to AppSearch; notification not shown on R");
+ return;
+ }
+
+ appConsents = migrateDataToAppSearch(appSearchConsentManager, dataFromR, datastore);
+
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED_TO_APP_SEARCH, true);
+ if (editor.commit()) {
+ LogUtil.d("Finished migrating consent to AppSearch.");
+ logMigrationToAppSearch(
+ statsdAdServicesLogger,
+ appConsents,
+ ConsentMigrationStats.MigrationStatus.SUCCESS_WITH_SHARED_PREF_UPDATED,
+ context);
+ } else {
+ LogUtil.e("Finished migrating consent to AppSearch. Shared prefs not updated.");
+ logMigrationToAppSearch(
+ statsdAdServicesLogger,
+ appConsents,
+ ConsentMigrationStats.MigrationStatus.SUCCESS_WITH_SHARED_PREF_NOT_UPDATED,
+ context);
+ }
+ // No longer need access to Android R data. Safe to clear here.
+ adExtDataManager.clearDataOnOtaAsync();
+ } catch (Exception e) {
+ LogUtil.e("Consent migration to AppSearch failed: ", e);
+ logMigrationToAppSearch(
+ statsdAdServicesLogger,
+ appConsents,
+ ConsentMigrationStats.MigrationStatus.FAILURE,
+ context);
+ }
+ }
+
+ /**
+ * This method handles migration of consent data to AppSearch post-OTA R -> S. Consent data is
+ * written to AdServicesExtDataStorageService on R and ported over to AppSearch after OTA to S
+ * as it's the new consent source of truth. If any new data is written for consent, we need to
+ * make sure it is migrated correctly post-OTA in this method.
+ */
+ public static void handleConsentMigrationToAppSearchIfNeededV2(
+ @NonNull Context context,
+ @NonNull BooleanFileDatastore datastore,
+ @Nullable AppSearchConsentStorageManager appSearchConsentManager,
@Nullable AdServicesExtDataStorageServiceManager adExtDataManager) {
Objects.requireNonNull(context);
Objects.requireNonNull(datastore);
@@ -65,7 +137,7 @@ public final class ConsentMigrationUtils {
FileCompatUtils.getSharedPreferencesHelper(
context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
- if (!isMigrationToAppSearchNeeded(
+ if (!isMigrationToAppSearchNeededV2(
context, sharedPreferences, appSearchConsentManager, adExtDataManager)) {
LogUtil.d("Skipping consent migration to AppSearch");
return;
@@ -78,7 +150,7 @@ public final class ConsentMigrationUtils {
return;
}
- migrateDataToAppSearch(appSearchConsentManager, dataFromR, datastore);
+ migrateDataToAppSearchV2(appSearchConsentManager, dataFromR, datastore);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED_TO_APP_SEARCH, true);
@@ -143,8 +215,56 @@ public final class ConsentMigrationUtils {
return !isNotificationDisplayedOnS;
}
+ private static boolean isMigrationToAppSearchNeededV2(
+ Context context,
+ SharedPreferences sharedPreferences,
+ AppSearchConsentStorageManager appSearchConsentManager,
+ AdServicesExtDataStorageServiceManager adExtDataManager) {
+ if (SdkLevel.isAtLeastT() || !SdkLevel.isAtLeastS()) {
+ LogUtil.d("Not S device. Consent migration to AppSearch not needed");
+ return false;
+ }
+
+ // Cannot be null on S since the consent source of truth has to be APPSEARCH_ONLY.
+ Objects.requireNonNull(appSearchConsentManager);
+
+ // There could be a case where we may need to ramp down enable_adext_service_consent_data
+ // flag on S, in which case we should gracefully handle consent migration by skipping.
+ if (adExtDataManager == null) {
+ LogUtil.d("AdExtDataManager is null. Consent migration to AppSearch not needed");
+ return false;
+ }
+
+ boolean isMigrationToAppSearchDone =
+ sharedPreferences.getBoolean(
+ ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED_TO_APP_SEARCH,
+ /* defValue= */ false);
+ if (isMigrationToAppSearchDone) {
+ LogUtil.d(
+ "Consent migration to AppSearch is already done for user %d.",
+ context.getUser().getIdentifier());
+ return false;
+ }
+
+ // Just in case, check all notification types to ensure notification is not shown. We do not
+ // want to override consent if notification is already shown.
+ boolean isNotificationDisplayedOnS =
+ appSearchConsentManager.wasU18NotificationDisplayed()
+ || appSearchConsentManager.wasNotificationDisplayed()
+ || appSearchConsentManager.wasGaUxNotificationDisplayed();
+ LogUtil.d(
+ "Notification shown status on S for migrating consent to AppSearch: "
+ + isNotificationDisplayedOnS);
+
+ // If notification is not shown, we will need to perform another check to ensure
+ // notification was shown on R before performing migration. This check will be performed
+ // later in order to reduce number of calls to AdExtDataService in the consent migration
+ // process.
+ return !isNotificationDisplayedOnS;
+ }
+
@TargetApi(Build.VERSION_CODES.S)
- private static void migrateDataToAppSearch(
+ private static AppConsents migrateDataToAppSearch(
AppSearchConsentManager appSearchConsentManager,
AdServicesExtDataParams dataFromR,
BooleanFileDatastore datastore) {
@@ -178,5 +298,72 @@ public final class ConsentMigrationUtils {
if (dataFromR.getIsAdultAccount() != BOOLEAN_UNKNOWN) {
appSearchConsentManager.setAdultAccount(dataFromR.getIsAdultAccount() == BOOLEAN_TRUE);
}
+
+ // Logging false for fledge and topics consent by default because only measurement is
+ // supported on R.
+ AppConsents appConsents =
+ AppConsents.builder()
+ .setDefaultConsent(
+ measurementDefaultConsent != null
+ ? measurementDefaultConsent
+ : false)
+ .setMsmtConsent(isMeasurementConsented)
+ .setFledgeConsent(false)
+ .setTopicsConsent(false)
+ .build();
+ return appConsents;
+ }
+
+ @TargetApi(Build.VERSION_CODES.S)
+ private static void logMigrationToAppSearch(
+ StatsdAdServicesLogger statsdAdServicesLogger,
+ AppConsents appConsents,
+ ConsentMigrationStats.MigrationStatus migrationStatus,
+ Context context) {
+ statsdAdServicesLogger.logConsentMigrationStats(
+ getConsentManagerStatsForLogging(
+ appConsents,
+ migrationStatus,
+ ConsentMigrationStats.MigrationType.ADEXT_SERVICE_TO_APPSEARCH,
+ context));
+ }
+
+ @TargetApi(Build.VERSION_CODES.S)
+ private static void migrateDataToAppSearchV2(
+ AppSearchConsentStorageManager appSearchConsentStorageManager,
+ AdServicesExtDataParams dataFromR,
+ BooleanFileDatastore datastore) {
+ // Default measurement consent is stored using PPAPI_ONLY source on R.
+ Boolean measurementDefaultConsent =
+ datastore.get(ConsentConstants.MEASUREMENT_DEFAULT_CONSENT);
+ if (measurementDefaultConsent != null) {
+ appSearchConsentStorageManager.recordDefaultConsent(
+ AdServicesApiType.MEASUREMENTS, measurementDefaultConsent);
+ }
+
+ boolean isMeasurementConsented = dataFromR.getIsMeasurementConsented() == BOOLEAN_TRUE;
+ appSearchConsentStorageManager.setConsent(
+ AdServicesApiType.MEASUREMENTS, isMeasurementConsented);
+
+ appSearchConsentStorageManager.setU18NotificationDisplayed(
+ dataFromR.getIsNotificationDisplayed() == BOOLEAN_TRUE);
+
+ // Record interaction data only if we recorded an interaction in
+ // AdServicesExtDataStorageService.
+ int manualInteractionRecorded = dataFromR.getManualInteractionWithConsentStatus();
+ if (manualInteractionRecorded == STATE_MANUAL_INTERACTIONS_RECORDED) {
+ appSearchConsentStorageManager.recordUserManualInteractionWithConsent(
+ manualInteractionRecorded);
+ }
+
+ if (dataFromR.getIsU18Account() != BOOLEAN_UNKNOWN) {
+ appSearchConsentStorageManager.setU18Account(
+ dataFromR.getIsU18Account() == BOOLEAN_TRUE);
+ }
+
+ if (dataFromR.getIsAdultAccount() != BOOLEAN_UNKNOWN) {
+ appSearchConsentStorageManager.setAdultAccount(
+ dataFromR.getIsAdultAccount() == BOOLEAN_TRUE);
+ }
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/IConsentStorage.java b/adservices/service-core/java/com/android/adservices/service/consent/IConsentStorage.java
index 0c21645d7b..ebd47b8da1 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/IConsentStorage.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/IConsentStorage.java
@@ -117,7 +117,7 @@ public interface IConsentStorage {
*
* @return true if the AdId is enabled by default, false otherwise.
*/
- boolean getDefaultAdIdState();
+ boolean getDefaultAdIdState() throws IOException;
/**
* Retrieves the default consent.
@@ -130,7 +130,7 @@ public interface IConsentStorage {
/** Returns current enrollment channel. */
@NonNull
PrivacySandboxEnrollmentChannelCollection getEnrollmentChannel(
- @NonNull PrivacySandboxUxCollection ux);
+ @NonNull PrivacySandboxUxCollection ux) throws IOException;
/**
* @return an {@link ImmutableList} of all known apps in the database that have not had user
@@ -148,13 +148,13 @@ public interface IConsentStorage {
/** Returns current UX. */
@NonNull
- PrivacySandboxUxCollection getUx();
+ PrivacySandboxUxCollection getUx() throws IOException;
/** Returns whether the isAdIdEnabled bit is true. */
- boolean isAdIdEnabled();
+ boolean isAdIdEnabled() throws IOException;
/** Returns whether the isAdultAccount bit is true. */
- boolean isAdultAccount();
+ boolean isAdultAccount() throws IOException;
/**
* Returns whether a given application (identified by package name) has had user consent
@@ -167,13 +167,14 @@ public interface IConsentStorage {
* application
* @throws IOException if the operation fails
*/
- boolean isConsentRevokedForApp(@NonNull String packageName) throws IllegalArgumentException;
+ boolean isConsentRevokedForApp(@NonNull String packageName)
+ throws IllegalArgumentException, IOException;
/** Returns whether the isEntryPointEnabled bit is true. */
- boolean isEntryPointEnabled();
+ boolean isEntryPointEnabled() throws IOException;
/** Returns whether the isU18Account bit is true. */
- boolean isU18Account();
+ boolean isU18Account() throws IOException;
/** Saves the default AdId state bit to data stores based on source of truth. */
void recordDefaultAdIdState(boolean defaultAdIdState) throws IOException;
@@ -242,7 +243,8 @@ public interface IConsentStorage {
/** Sets the current enrollment channel to storage. */
void setEnrollmentChannel(
@NonNull PrivacySandboxUxCollection ux,
- @NonNull PrivacySandboxEnrollmentChannelCollection channel);
+ @NonNull PrivacySandboxEnrollmentChannelCollection channel)
+ throws IOException;
/** Sets the EntryPointEnabled bit to storage . */
void setEntryPointEnabled(boolean isEntryPointEnabled) throws IOException;
@@ -254,14 +256,14 @@ public interface IConsentStorage {
void setU18NotificationDisplayed(boolean wasU18NotificationDisplayed) throws IOException;
/** Sets the current UX to storage. */
- void setUx(PrivacySandboxUxCollection ux);
+ void setUx(PrivacySandboxUxCollection ux) throws IOException;
/**
* Retrieves if GA UX notification has been displayed.
*
* @return true if GA UX Consent Notification was displayed, otherwise false.
*/
- boolean wasGaUxNotificationDisplayed();
+ boolean wasGaUxNotificationDisplayed() throws IOException;
/**
* Retrieves if notification has been displayed.
@@ -271,5 +273,5 @@ public interface IConsentStorage {
boolean wasNotificationDisplayed() throws IOException;
/** Returns whether the wasU18NotificationDisplayed bit is true. */
- boolean wasU18NotificationDisplayed();
+ boolean wasU18NotificationDisplayed() throws IOException;
}
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 8373bb5ebb..fcab6c9a88 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
@@ -203,8 +203,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
- mContext, ownerPackageName, apiName);
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(mContext, ownerPackageName, apiName);
final int callerUid = getCallingUid(apiName);
final DevContext devContext = mDevContextFilter.createDevContext();
@@ -301,7 +300,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, input.getCallerPackageName(), apiName);
final int callerUid = getCallingUid(apiName);
@@ -386,8 +385,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
- mContext, ownerPackageName, apiName);
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(mContext, ownerPackageName, apiName);
final int callerUid = getCallingUid(apiName);
final DevContext devContext = mDevContextFilter.createDevContext();
@@ -506,7 +504,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
CustomAudienceDao customAudienceDao = mCustomAudienceImpl.getCustomAudienceDao();
@@ -567,7 +565,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
CustomAudienceDao customAudienceDao = mCustomAudienceImpl.getCustomAudienceDao();
@@ -616,7 +614,7 @@ public class CustomAudienceServiceImpl extends ICustomAudienceService.Stub {
}
// Caller permissions must be checked with a non-null callingAppPackageName
- mFledgeAuthorizationFilter.assertAppDeclaredCustomAudiencePermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, devContext.getCallingAppPackageName(), apiName);
CustomAudienceDao customAudienceDao = mCustomAudienceImpl.getCustomAudienceDao();
diff --git a/adservices/service-core/java/com/android/adservices/service/devapi/DevContextFilter.java b/adservices/service-core/java/com/android/adservices/service/devapi/DevContextFilter.java
index 9e4b05ddc2..5a78bdaea2 100644
--- a/adservices/service-core/java/com/android/adservices/service/devapi/DevContextFilter.java
+++ b/adservices/service-core/java/com/android/adservices/service/devapi/DevContextFilter.java
@@ -147,7 +147,7 @@ public class DevContextFilter {
} catch (PackageManager.NameNotFoundException e) {
LogUtil.w(
- "Unable to retrieve application info for app with ID %d and resolved package "
+ "Unable to retrieve application info for app with ID %s and resolved package "
+ "name '%s', considering not debuggable for safety.",
callingAppPackage, callingAppPackage);
return false;
diff --git a/adservices/service-core/java/com/android/adservices/service/encryptionkey/EncryptionKey.java b/adservices/service-core/java/com/android/adservices/service/encryptionkey/EncryptionKey.java
index 1938979634..07dc2aa9c9 100644
--- a/adservices/service-core/java/com/android/adservices/service/encryptionkey/EncryptionKey.java
+++ b/adservices/service-core/java/com/android/adservices/service/encryptionkey/EncryptionKey.java
@@ -135,6 +135,21 @@ public class EncryptionKey {
return mLastFetchTime;
}
+ /** Returns the builder for the instance */
+ public EncryptionKey.Builder cloneToBuilder() {
+ return new EncryptionKey.Builder()
+ .setId(this.mId)
+ .setKeyType(this.mKeyType)
+ .setEnrollmentId(this.mEnrollmentId)
+ .setReportingOrigin(this.mReportingOrigin)
+ .setEncryptionKeyUrl(this.mEncryptionKeyUrl)
+ .setProtocolType(this.mProtocolType)
+ .setKeyCommitmentId(this.mKeyCommitmentId)
+ .setBody(this.mBody)
+ .setExpiration(this.mExpiration)
+ .setLastFetchTime(this.mLastFetchTime);
+ }
+
/** Builder for {@link EncryptionKey}. */
public static final class Builder {
private final EncryptionKey mBuilding;
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 d166f3b6a5..6cb1f8c0a2 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
@@ -140,6 +140,21 @@ public class EnrollmentData {
return Arrays.asList(input.trim().split(SEPARATOR));
}
+ /** Returns the builder for the instance */
+ @NonNull
+ public EnrollmentData.Builder cloneToBuilder() {
+ return new EnrollmentData.Builder()
+ .setEnrollmentId(this.mEnrollmentId)
+ .setCompanyId(this.mCompanyId)
+ .setSdkNames(this.mSdkNames)
+ .setAttributionSourceRegistrationUrl(this.mAttributionSourceRegistrationUrl)
+ .setAttributionTriggerRegistrationUrl(this.mAttributionTriggerRegistrationUrl)
+ .setAttributionReportingUrl(this.mAttributionReportingUrl)
+ .setRemarketingResponseBasedRegistrationUrl(
+ this.mRemarketingResponseBasedRegistrationUrl)
+ .setEncryptionKeyUrl(this.mEncryptionKeyUrl);
+ }
+
/** Builder for {@link EnrollmentData}. */
public static final class Builder {
private final EnrollmentData mBuilding;
diff --git a/adservices/service-core/java/com/android/adservices/service/exception/ConsentStorageDeferException.java b/adservices/service-core/java/com/android/adservices/service/exception/ConsentStorageDeferException.java
new file mode 100644
index 0000000000..5edf8e68fb
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/exception/ConsentStorageDeferException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.exception;
+
+import java.io.IOException;
+
+/**
+ * Exception class to indicate operation is not supported by current storage manager. This exception
+ * will always be caught in {@link ConsentCompositeStorage}, and ConsentCompositeStorage will call
+ * the next IConsentStorage instance to get/set the right value.
+ */
+public final class ConsentStorageDeferException extends IOException {}
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 00a945fa96..b62df02677 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
@@ -37,11 +37,6 @@ public final class PrivacyParams {
public static final int EVENT_SOURCE_MAX_REPORTS = 1;
/**
- * Max reports for Install Attributed 'Navigation' {@link Source}.
- */
- public static final int INSTALL_ATTR_NAVIGATION_SOURCE_MAX_REPORTS = 3;
-
- /**
* Max reports for Install Attributed 'Event' {@link Source}.
*/
public static final int INSTALL_ATTR_EVENT_SOURCE_MAX_REPORTS = 2;
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 2fe5e16b0d..35da5c7058 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
@@ -121,6 +121,7 @@ public class Source {
if (mParsedEventReportWindows != null) {
return mParsedEventReportWindows;
}
+
if (mEventReportWindows == null) {
return null;
}
@@ -140,7 +141,8 @@ public class Source {
}
/**
- * Returns parsed or default value of event report windows.
+ * Returns parsed or default value of event report windows (can be used during {@code Source}
+ * construction since the method does not require a {@code Source} object).
*
* @param eventReportWindows string to be parsed
* @param sourceType Source's Type
@@ -149,13 +151,13 @@ public class Source {
* @return parsed or default value
*/
@Nullable
- public static List<Pair<Long, Long>> getOrDefaultEventReportWindows(
+ public static List<Pair<Long, Long>> getOrDefaultEventReportWindowsForFlex(
@Nullable JSONObject eventReportWindows,
@NonNull SourceType sourceType,
long expiryDelta,
@NonNull Flags flags) {
if (eventReportWindows == null) {
- return getDefaultEventReportWindows(expiryDelta, sourceType, flags);
+ return getDefaultEventReportWindowsForFlex(expiryDelta, sourceType, flags);
}
return parseEventReportWindows(eventReportWindows);
}
@@ -180,17 +182,17 @@ public class Source {
@NonNull JSONObject jsonObject) {
List<Pair<Long, Long>> result = new ArrayList<>();
try {
- long startDuration = 0;
+ long startTime = 0L;
if (!jsonObject.isNull("start_time")) {
- startDuration = jsonObject.getLong("start_time");
+ startTime = jsonObject.getLong("start_time");
}
JSONArray endTimesJSON = jsonObject.getJSONArray("end_times");
for (int i = 0; i < endTimesJSON.length(); i++) {
- long endDuration = endTimesJSON.getLong(i);
- Pair<Long, Long> window = new Pair<>(startDuration, endDuration);
+ long endTime = endTimesJSON.getLong(i);
+ Pair<Long, Long> window = Pair.create(startTime, endTime);
result.add(window);
- startDuration = endDuration;
+ startTime = endTime;
}
} catch (JSONException e) {
LoggerFactory.getMeasurementLogger()
@@ -200,24 +202,25 @@ public class Source {
return result;
}
- private static List<Pair<Long, Long>> getDefaultEventReportWindows(
+ private static List<Pair<Long, Long>> getDefaultEventReportWindowsForFlex(
long expiryDelta, SourceType sourceType, Flags flags) {
List<Pair<Long, Long>> result = new ArrayList<>();
- List<Long> defaultEarlyWindows =
- EventReportWindowCalcDelegate.getDefaultEarlyReportingWindows(sourceType, false);
- List<Long> earlyWindows =
+ // Obtain default early report windows without regard to install-related behaviour.
+ List<Long> defaultEarlyWindowEnds =
+ EventReportWindowCalcDelegate.getDefaultEarlyReportingWindowEnds(sourceType, false);
+ List<Long> earlyWindowEnds =
new EventReportWindowCalcDelegate(flags)
- .getConfiguredOrDefaultEarlyReportingWindows(
- sourceType, defaultEarlyWindows, false);
+ .getConfiguredOrDefaultEarlyReportingWindowEnds(
+ sourceType, defaultEarlyWindowEnds);
long windowStart = 0;
- for (long earlyWindow : earlyWindows) {
- if (earlyWindow >= expiryDelta) {
- continue;
+ for (long earlyWindowEnd : earlyWindowEnds) {
+ if (earlyWindowEnd >= expiryDelta) {
+ break;
}
- result.add(new Pair<>(windowStart, earlyWindow));
- windowStart = earlyWindow;
+ result.add(Pair.create(windowStart, earlyWindowEnd));
+ windowStart = earlyWindowEnd;
}
- result.add(new Pair<>(windowStart, expiryDelta));
+ result.add(Pair.create(windowStart, expiryDelta));
return result;
}
@@ -234,8 +237,7 @@ public class Source {
}
private double getInformationGainThreshold(Flags flags) {
- int destinationMultiplier = getDestinationTypeMultiplier(flags);
- if (destinationMultiplier == 2) {
+ if (getDestinationTypeMultiplier(flags) == 2) {
return mSourceType == SourceType.EVENT
? flags.getMeasurementFlexApiMaxInformationGainDualDestinationEvent()
: flags.getMeasurementFlexApiMaxInformationGainDualDestinationNavigation();
@@ -262,32 +264,31 @@ public class Source {
setFlipProbability(mTriggerSpecs.getFlipProbability(this, flags));
return;
}
- boolean installCase = SourceNoiseHandler.isInstallDetectionEnabled(this);
EventReportWindowCalcDelegate eventReportWindowCalcDelegate =
new EventReportWindowCalcDelegate(flags);
int reportingWindowCountForNoising =
- eventReportWindowCalcDelegate.getReportingWindowCountForNoising(this, installCase);
- int maxReportCount =
- eventReportWindowCalcDelegate.getMaxReportCount(this, installCase);
- int destinationMultiplier = getDestinationTypeMultiplier(flags);
+ eventReportWindowCalcDelegate.getReportingWindowCountForNoising(this);
long numberOfStates =
Combinatorics.getNumberOfStarsAndBarsSequences(
- /*numStars=*/ maxReportCount,
+ /*numStars=*/ eventReportWindowCalcDelegate.getMaxReportCount(this),
/*numBars=*/ getTriggerDataCardinality()
* reportingWindowCountForNoising
- * destinationMultiplier);
+ * getDestinationTypeMultiplier(flags));
setNumStates(numberOfStates);
setFlipProbability(Combinatorics.getFlipProbability(numberOfStates));
}
+ /** Should source report coarse destinations */
+ public boolean shouldReportCoarseDestinations(Flags flags) {
+ return flags.getMeasurementEnableCoarseEventReportDestinations()
+ && hasCoarseEventReportDestinations();
+ }
+
/**
* Returns the number of destination types to use in privacy computations.
*/
public int getDestinationTypeMultiplier(Flags flags) {
- boolean shouldReportCoarseDestinations =
- flags.getMeasurementEnableCoarseEventReportDestinations()
- && hasCoarseEventReportDestinations();
- return !shouldReportCoarseDestinations && hasAppDestinations()
+ return !shouldReportCoarseDestinations(flags) && hasAppDestinations()
&& hasWebDestinations()
? SourceNoiseHandler.DUAL_DESTINATION_IMPRESSION_NOISE_MULTIPLIER
: SourceNoiseHandler.SINGLE_DESTINATION_IMPRESSION_NOISE_MULTIPLIER;
@@ -650,9 +651,9 @@ public class Source {
/**
* Time when {@link Source} event report window will expire. (Appends the Event Time to window)
*/
- public Long getProcessedEventReportWindow() {
+ public long getEffectiveEventReportWindow() {
if (mEventReportWindow == null) {
- return null;
+ return getExpiryTime();
}
// TODO(b/290098169): Cleanup after a few releases
// Handling cases where ReportWindow is already stored as mEventTime + mEventReportWindow
@@ -744,6 +745,11 @@ public class Source {
return mInstallCooldownWindow;
}
+ /** Check if install detection is enabled for the source. */
+ public boolean isInstallDetectionEnabled() {
+ return getInstallCooldownWindow() > 0 && hasAppDestinations();
+ }
+
/**
* Is an App-install attributed to the {@link Source}.
*/
@@ -971,10 +977,9 @@ public class Source {
@Nullable Integer maxEventLevelReports,
@NonNull Flags flags) {
if (maxEventLevelReports == null) {
- maxEventLevelReports =
- sourceType == Source.SourceType.NAVIGATION
- ? PrivacyParams.NAVIGATION_SOURCE_MAX_REPORTS
- : flags.getMeasurementVtcConfigurableMaxEventReportsCount();
+ return sourceType == Source.SourceType.NAVIGATION
+ ? PrivacyParams.NAVIGATION_SOURCE_MAX_REPORTS
+ : flags.getMeasurementVtcConfigurableMaxEventReportsCount();
}
return maxEventLevelReports;
}
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 20dbe424e6..3d2eec8c2c 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
@@ -57,6 +57,7 @@ import com.android.adservices.service.measurement.reporting.DebugKeyAccessor;
import com.android.adservices.service.measurement.reporting.DebugReportApi;
import com.android.adservices.service.measurement.reporting.DebugReportApi.Type;
import com.android.adservices.service.measurement.reporting.EventReportWindowCalcDelegate;
+import com.android.adservices.service.measurement.reporting.EventReportWindowCalcDelegate.MomentPlacement;
import com.android.adservices.service.measurement.util.BaseUriExtractor;
import com.android.adservices.service.measurement.util.Filter;
import com.android.adservices.service.measurement.util.UnsignedLong;
@@ -813,12 +814,8 @@ class AttributionJobHandler {
return TriggeringStatus.DROPPED;
}
- if (mEventReportWindowCalcDelegate.getReportingTime(
- source, trigger.getTriggerTime(), trigger.getDestinationType()) == -1
- && (source.getTriggerSpecsString() == null
- || source.getTriggerSpecsString().isEmpty())) {
- mDebugReportApi.scheduleTriggerDebugReport(
- source, trigger, null, measurementDao, Type.TRIGGER_EVENT_REPORT_WINDOW_PASSED);
+ if (source.getTriggerSpecs() == null
+ && !isTriggerFallsWithinWindow(source, trigger, measurementDao)) {
return TriggeringStatus.DROPPED;
}
@@ -1332,16 +1329,10 @@ class AttributionJobHandler {
private boolean isWithinReportLimit(
Source source, int existingReportCount, @EventSurfaceType int destinationType) {
- return mEventReportWindowCalcDelegate.getMaxReportCount(
- source, hasAppInstallAttributionOccurred(source, destinationType))
+ return mEventReportWindowCalcDelegate.getMaxReportCount(source, destinationType)
> existingReportCount;
}
- private static boolean hasAppInstallAttributionOccurred(
- Source source, @EventSurfaceType int destinationType) {
- return destinationType == EventSurfaceType.APP && source.isInstallAttributed();
- }
-
private static boolean isWithinInstallCooldownWindow(Source source, Trigger trigger) {
return trigger.getTriggerTime()
< (source.getEventTime() + source.getInstallCooldownWindow());
@@ -1754,4 +1745,27 @@ class AttributionJobHandler {
attributionStatus.setAggregateDebugReportCount(
attributionStatus.getAggregateDebugReportCount() + count);
}
+
+ private boolean isTriggerFallsWithinWindow(
+ Source source, Trigger trigger, IMeasurementDao measurementDao)
+ throws DatastoreException {
+ MomentPlacement momentPlacement =
+ mEventReportWindowCalcDelegate.fallsWithinWindow(
+ source, trigger.getTriggerTime(), trigger.getDestinationType());
+ if (momentPlacement == MomentPlacement.BEFORE) {
+ mDebugReportApi.scheduleTriggerDebugReport(
+ source,
+ trigger,
+ null,
+ measurementDao,
+ Type.TRIGGER_EVENT_REPORT_WINDOW_NOT_STARTED);
+ return false;
+ }
+ if (momentPlacement == MomentPlacement.AFTER) {
+ mDebugReportApi.scheduleTriggerDebugReport(
+ source, trigger, null, measurementDao, Type.TRIGGER_EVENT_REPORT_WINDOW_PASSED);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/noising/ImpressionNoiseUtil.java b/adservices/service-core/java/com/android/adservices/service/measurement/noising/ImpressionNoiseUtil.java
index 0a456919f9..c83f608b64 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/noising/ImpressionNoiseUtil.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/noising/ImpressionNoiseUtil.java
@@ -29,72 +29,6 @@ import java.util.concurrent.ThreadLocalRandom;
*/
public final class ImpressionNoiseUtil {
- /**
- * This is used in the scenario where both app and web destinations are available with the
- * {@link com.android.adservices.service.measurement.Source} and the source is of {@link
- * com.android.adservices.service.measurement.Source.SourceType#EVENT} type and post-install
- * detection is enabled (cooldown window being available). It's a special case because in this
- * condition an extra early window is added only if install detection is enabled (install
- * cool-down window is available) and only app conversions are relevant in that window.
- *
- * <p>Reading guide - The outermost array signifies different states of reporting - one of them
- * will be picked at a time. The middle array holds reports in 1st and 2nd window, so there will
- * be either 0 elements (no conversions in either window), 1 element (a conversion report in one
- * of the windows) and 2 elements (conversions in both windows). The innermost array represents
- * a single report. 3 elements in the innermost array are trigger metadata (0 - trigger1 or 1 -
- * trigger2), window index (0 - window1, 1 - window2) and destination type (0 - app or 1 - web).
- *
- * <p>E.g. The element at index 5 is {{0, 1, 0}, {0, 1, 0}}, it means 2 conversions with trigger
- * metadata as 0 in window2 (1) of app destination type(0).
- */
- public static final int[][][] DUAL_DESTINATION_POST_INSTALL_FAKE_REPORT_CONFIG =
- new int[][][] {
- // window1 - no conversion, window 2 - no conversion
- {},
- // window1 - no conversion, window 2 - 1 conversion with metadata 0
- {{0, 1, 0}},
- {{0, 1, 1}},
- // window1 - no conversion, window 2 - 1 conversion with metadata 1
- {{1, 1, 0}},
- {{1, 1, 1}},
- // window1 - no conversion, window 2 - 2 conversions with metadata 0 and 0
- {{0, 1, 0}, {0, 1, 0}},
- {{0, 1, 0}, {0, 1, 1}},
- // window1 - no conversion, window 2 - 2 conversions with metadata 0 and 1
- {{0, 1, 0}, {1, 1, 0}},
- {{0, 1, 0}, {1, 1, 1}},
- {{0, 1, 1}, {1, 1, 0}},
- // window1 - no conversion, window 2 - 2 conversions with metadata 1 and 1
- {{1, 1, 0}, {1, 1, 0}},
- {{1, 1, 0}, {1, 1, 1}},
- // window1 - 1 app conversion with metadata 0, window 2 - no conversion
- {{0, 0, 0}},
- // window1 - 1 app conversion with metadata 1, window 2 - no conversion
- {{1, 0, 0}},
- // window1 - 2 conversions with metadata 0 and 0, window 2 - no conversion
- {{0, 0, 0}, {0, 0, 0}},
- // window1 - 2 app conversions with metadata 0 and 1, window 2 - no conversion
- {{0, 0, 0}, {1, 0, 0}},
- // window1 - 2 app conversions with metadata 1 and 1, window 2 - no conversion
- {{1, 0, 0}, {1, 0, 0}},
- // window1 - 1 app conversion with metadata 0, window 2 - 1 conversion with
- // metadata 0
- {{0, 0, 0}, {0, 1, 0}},
- {{0, 0, 0}, {0, 1, 1}},
- // window1 - 1 app conversion with metadata 0, window 2 - 1 conversion with
- // metadata 1
- {{0, 0, 0}, {1, 1, 0}},
- {{0, 0, 0}, {1, 1, 1}},
- // window1 - 1 app conversion with metadata 1, window 2 - 1 conversions with
- // metadata 0
- {{1, 0, 0}, {0, 1, 0}},
- {{1, 0, 0}, {0, 1, 1}},
- // window1 - 1 app conversion with metadata 1, window 2 - 1 conversions with
- // metadata 1
- {{1, 0, 0}, {1, 1, 0}},
- {{1, 0, 0}, {1, 1, 1}}
- };
-
private ImpressionNoiseUtil() {}
/**
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/noising/SourceNoiseHandler.java b/adservices/service-core/java/com/android/adservices/service/measurement/noising/SourceNoiseHandler.java
index 2c28fa51e5..8897662a95 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/noising/SourceNoiseHandler.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/noising/SourceNoiseHandler.java
@@ -30,11 +30,9 @@ import com.google.common.collect.ImmutableList;
import java.math.BigDecimal;
import java.math.RoundingMode;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
-import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
@@ -85,33 +83,24 @@ public class SourceNoiseHandler {
List<Source.FakeReport> fakeReports;
TriggerSpecs triggerSpecs = source.getTriggerSpecs();
if (triggerSpecs == null) {
- if (isVtcDualDestinationModeWithPostInstallEnabled(source)) {
- // Source is 'EVENT' type, both app and web destination are set and install
- // exclusivity
- // window is provided. Pick one of the static reporting states randomly.
- fakeReports = generateVtcDualDestinationPostInstallFakeReports(source);
- } else {
- // There will at least be one (app or web) destination available
- ImpressionNoiseParams noiseParams = getImpressionNoiseParams(source);
- fakeReports =
- ImpressionNoiseUtil.selectRandomStateAndGenerateReportConfigs(
- noiseParams, rand)
- .stream()
- .map(
- reportConfig ->
- new Source.FakeReport(
- new UnsignedLong(
- Long.valueOf(reportConfig[0])),
- mEventReportWindowCalcDelegate
- .getReportingTimeForNoising(
- source,
- reportConfig[1],
- isInstallDetectionEnabled(
- source)),
- resolveFakeReportDestinations(
- source, reportConfig[2])))
- .collect(Collectors.toList());
- }
+ // There will at least be one (app or web) destination available
+ ImpressionNoiseParams noiseParams = getImpressionNoiseParams(source);
+ fakeReports =
+ ImpressionNoiseUtil.selectRandomStateAndGenerateReportConfigs(
+ noiseParams, rand)
+ .stream()
+ .map(
+ reportConfig ->
+ new Source.FakeReport(
+ new UnsignedLong(
+ Long.valueOf(reportConfig[0])),
+ mEventReportWindowCalcDelegate
+ .getReportingTimeForNoising(
+ source,
+ reportConfig[1]),
+ resolveFakeReportDestinations(
+ source, reportConfig[2])))
+ .collect(Collectors.toList());
} else {
int destinationTypeMultiplier = source.getDestinationTypeMultiplier(mFlags);
List<int[]> fakeReportConfigs =
@@ -144,51 +133,11 @@ public class SourceNoiseHandler {
/** @return Probability of selecting random state for attribution */
public double getRandomAttributionProbability(@NonNull Source source) {
- if (source.getTriggerSpecs() != null
- || mFlags.getMeasurementEnableConfigurableEventReportingWindows()
- || mFlags.getMeasurementEnableVtcConfigurableMaxEventReports()
- || (mFlags.getMeasurementFlexLiteApiEnabled()
- && (source.getMaxEventLevelReports() != null
- || source.hasManualEventReportWindows()))) {
- return convertToDoubleAndLimitDecimal(source.getFlipProbability(mFlags));
- }
- // TODO(b/290117352): Remove Hardcoded noise values
-
- // Both destinations are set and install attribution is supported
- if (!shouldReportCoarseDestinations(source)
- && source.hasWebDestinations()
- && isInstallDetectionEnabled(source)) {
- return source.getSourceType() == Source.SourceType.EVENT
- ? convertToDoubleAndLimitDecimal(
- mFlags.getMeasurementInstallAttrDualDestinationEventNoiseProbability())
- : convertToDoubleAndLimitDecimal(
- mFlags.getMeasurementInstallAttrDualDestinationNavigationNoiseProbability());
- }
-
- // Both destinations are set but install attribution isn't supported
- if (!shouldReportCoarseDestinations(source)
- && source.hasAppDestinations()
- && source.hasWebDestinations()) {
- return source.getSourceType() == Source.SourceType.EVENT
- ? convertToDoubleAndLimitDecimal(
- mFlags.getMeasurementDualDestinationEventNoiseProbability())
- : convertToDoubleAndLimitDecimal(
- mFlags.getMeasurementDualDestinationNavigationNoiseProbability());
- }
-
- // App destination is set and install attribution is supported
- if (isInstallDetectionEnabled(source)) {
- return source.getSourceType() == Source.SourceType.EVENT
- ? convertToDoubleAndLimitDecimal(
- mFlags.getMeasurementInstallAttrEventNoiseProbability())
- : convertToDoubleAndLimitDecimal(
- mFlags.getMeasurementInstallAttrNavigationNoiseProbability());
- }
-
- // One of the destinations is available without install attribution support
- return source.getSourceType() == Source.SourceType.EVENT
- ? convertToDoubleAndLimitDecimal(mFlags.getMeasurementEventNoiseProbability())
- : convertToDoubleAndLimitDecimal(mFlags.getMeasurementNavigationNoiseProbability());
+ // Methods on Source and EventReportWindowCalcDelegate that calculate flip probability for
+ // the source rely on reporting windows and max reports that are obtained with consideration
+ // to install-state and its interaction with configurable report windows and configurable
+ // max reports.
+ return convertToDoubleAndLimitDecimal(source.getFlipProbability(mFlags));
}
private double convertToDoubleAndLimitDecimal(double probability) {
@@ -197,15 +146,6 @@ public class SourceNoiseHandler {
.doubleValue();
}
- private boolean isVtcDualDestinationModeWithPostInstallEnabled(Source source) {
- return !shouldReportCoarseDestinations(source)
- && !source.hasManualEventReportWindows()
- && source.getMaxEventLevelReports() == null
- && source.getSourceType() == Source.SourceType.EVENT
- && source.hasWebDestinations()
- && isInstallDetectionEnabled(source);
- }
-
/**
* Either both app and web destinations can be available or one of them will be available. When
* both destinations are available, we double the number of states at noise generation to be
@@ -216,7 +156,7 @@ public class SourceNoiseHandler {
* @return app or web destination {@link Uri}
*/
private List<Uri> resolveFakeReportDestinations(Source source, int destinationIdentifier) {
- if (shouldReportCoarseDestinations(source)) {
+ if (source.shouldReportCoarseDestinations(mFlags)) {
ImmutableList.Builder<Uri> destinations = new ImmutableList.Builder<>();
Optional.ofNullable(source.getAppDestinations()).ifPresent(destinations::addAll);
Optional.ofNullable(source.getWebDestinations()).ifPresent(destinations::addAll);
@@ -234,44 +174,13 @@ public class SourceNoiseHandler {
: source.getWebDestinations();
}
- /** Check if install detection is enabled for the source. */
- public static boolean isInstallDetectionEnabled(@NonNull Source source) {
- return source.getInstallCooldownWindow() > 0 && source.hasAppDestinations();
- }
-
- private boolean shouldReportCoarseDestinations(Source source) {
- return mFlags.getMeasurementEnableCoarseEventReportDestinations()
- && source.hasCoarseEventReportDestinations();
- }
-
- private List<Source.FakeReport> generateVtcDualDestinationPostInstallFakeReports(
- Source source) {
- int[][][] fakeReportsConfig =
- ImpressionNoiseUtil.DUAL_DESTINATION_POST_INSTALL_FAKE_REPORT_CONFIG;
- int randomIndex = new Random().nextInt(fakeReportsConfig.length);
- int[][] reportsConfig = fakeReportsConfig[randomIndex];
- return Arrays.stream(reportsConfig)
- .map(
- reportConfig ->
- new Source.FakeReport(
- new UnsignedLong(Long.valueOf(reportConfig[0])),
- mEventReportWindowCalcDelegate.getReportingTimeForNoising(
- source,
- /* window index */ reportConfig[1],
- isInstallDetectionEnabled(source)),
- resolveFakeReportDestinations(source, reportConfig[2])))
- .collect(Collectors.toList());
- }
-
@VisibleForTesting
ImpressionNoiseParams getImpressionNoiseParams(Source source) {
int destinationTypeMultiplier = source.getDestinationTypeMultiplier(mFlags);
return new ImpressionNoiseParams(
- mEventReportWindowCalcDelegate.getMaxReportCount(
- source, isInstallDetectionEnabled(source)),
+ mEventReportWindowCalcDelegate.getMaxReportCount(source),
source.getTriggerDataCardinality(),
- mEventReportWindowCalcDelegate.getReportingWindowCountForNoising(
- source, isInstallDetectionEnabled(source)),
+ mEventReportWindowCalcDelegate.getReportingWindowCountForNoising(source),
destinationTypeMultiplier);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirect.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirect.java
index 1c76ade570..6106b1a520 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirect.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirect.java
@@ -18,46 +18,31 @@ package com.android.adservices.service.measurement.registration;
import android.net.Uri;
-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> mLocationRedirects;
- private final List<Uri> mListRedirects;
-
- public AsyncRedirect() {
- mLocationRedirects = new ArrayList<>();
- mListRedirects = new ArrayList<>();
+ public enum RedirectBehavior {
+ /** Do not modify redirect path */
+ AS_IS,
+ /** Prepend well known path prefix to path of redirect url for Location type redirects */
+ LOCATION_TO_WELL_KNOWN,
}
- public AsyncRedirect(List<Uri> locationRedirects, List<Uri> listRedirects) {
- mLocationRedirects = locationRedirects;
- mListRedirects = listRedirects;
- }
+ private final Uri mUri;
+ private final RedirectBehavior mRedirectBehavior;
- /** The list the redirect Uris */
- public List<Uri> getRedirects() {
- List<Uri> allRedirects = new ArrayList<>(mListRedirects);
- allRedirects.addAll(mLocationRedirects);
- return allRedirects;
+ public AsyncRedirect(Uri uri, RedirectBehavior redirectBehavior) {
+ mUri = uri;
+ mRedirectBehavior = redirectBehavior;
}
- /** Get list by redirect type */
- public List<Uri> getRedirectsByType(AsyncRegistration.RedirectType redirectType) {
- if (redirectType == AsyncRegistration.RedirectType.LOCATION) {
- return new ArrayList<>(mLocationRedirects);
- } else {
- return new ArrayList<>(mListRedirects);
- }
+ public Uri getUri() {
+ return mUri;
}
- /** Add to the list the redirect Uris based on type */
- public void addToRedirects(AsyncRegistration.RedirectType redirectType, List<Uri> uris) {
- if (redirectType == AsyncRegistration.RedirectType.LOCATION) {
- mLocationRedirects.addAll(uris);
- } else {
- mListRedirects.addAll(uris);
- }
+ public RedirectBehavior getRedirectBehavior() {
+ return mRedirectBehavior;
}
+
+
+
+
}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirects.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirects.java
new file mode 100644
index 0000000000..d05cb29032
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRedirects.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 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.net.Uri;
+
+import com.android.adservices.service.Flags;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** Class for handling redirects */
+public class AsyncRedirects {
+ public static final String HEADER_ATTRIBUTION_REPORTING_REDIRECT_CONFIG =
+ "Attribution-Reporting-Redirect-Config";
+ public static final String REDIRECT_302_TO_WELL_KNOWN = "redirect-302-to-well-known";
+ public static final String WELL_KNOWN_PATH_SEGMENT =
+ ".well-known/attribution-reporting/register-redirect";
+ public static final String WELL_KNOWN_QUERY_PARAM = "302_url";
+ public static final String REDIRECT_LIST_HEADER_KEY = "Attribution-Reporting-Redirect";
+ public static final String REDIRECT_LOCATION_HEADER_KEY = "Location";
+ private final List<AsyncRedirect> mLocationRedirects;
+ private final List<AsyncRedirect> mListRedirects;
+
+ public AsyncRedirects() {
+ mLocationRedirects = new ArrayList<>();
+ mListRedirects = new ArrayList<>();
+ }
+
+ /** Return flattened list of {@link AsyncRedirect} */
+ public List<AsyncRedirect> getRedirects() {
+ List<AsyncRedirect> allRedirects = new ArrayList<>(mListRedirects);
+ allRedirects.addAll(mLocationRedirects);
+
+ return allRedirects;
+ }
+
+ /** Get list of {@link AsyncRedirect} by redirect type */
+ public List<AsyncRedirect> getRedirectsByType(AsyncRegistration.RedirectType redirectType) {
+ if (redirectType == AsyncRegistration.RedirectType.LOCATION) {
+ return new ArrayList<>(mLocationRedirects);
+ } else {
+ return new ArrayList<>(mListRedirects);
+ }
+ }
+
+ /** Process redirects based on the given headers */
+ public void configure(
+ Map<String, List<String>> headers, Flags flags, AsyncRegistration parentRegistration) {
+ if (!parentRegistration.shouldProcessRedirects()) {
+ return;
+ }
+
+ Map<AsyncRegistration.RedirectType, List<Uri>> urisByType =
+ FetcherUtil.parseRedirects(headers);
+
+ for (Uri locationRedirectUri : urisByType.get(AsyncRegistration.RedirectType.LOCATION)) {
+ if (shouldRedirect302ToWellKnown(headers, flags, parentRegistration)) {
+ mLocationRedirects.add(
+ new AsyncRedirect(
+ getLocationRedirectToWellKnownUri(locationRedirectUri),
+ AsyncRedirect.RedirectBehavior.LOCATION_TO_WELL_KNOWN));
+ } else {
+ mLocationRedirects.add(
+ new AsyncRedirect(
+ locationRedirectUri, AsyncRedirect.RedirectBehavior.AS_IS));
+ }
+ }
+
+ for (Uri listRedirectUri : urisByType.get(AsyncRegistration.RedirectType.LIST)) {
+ mListRedirects.add(
+ new AsyncRedirect(listRedirectUri, AsyncRedirect.RedirectBehavior.AS_IS));
+ }
+ }
+
+ private static boolean shouldRedirect302ToWellKnown(
+ Map<String, List<String>> headers, Flags flags, AsyncRegistration parentRegistration) {
+ boolean isParentRegistrationRedirectsToWellKnown =
+ AsyncRedirect.RedirectBehavior.LOCATION_TO_WELL_KNOWN.equals(
+ parentRegistration.getRedirectBehavior());
+
+ return flags.getMeasurementEnableRedirectToWellKnownPath()
+ && (isParentRegistrationRedirectsToWellKnown
+ || isRedirect302ToWellKnownPath(headers));
+ }
+
+ /**
+ * Return true if the given headers indicate redirects should prepend well known prefix to the
+ * path.
+ */
+ private static boolean isRedirect302ToWellKnownPath(Map<String, List<String>> headers) {
+ if (!headers.containsKey(HEADER_ATTRIBUTION_REPORTING_REDIRECT_CONFIG)) {
+ return false;
+ }
+ List<String> config = headers.get(HEADER_ATTRIBUTION_REPORTING_REDIRECT_CONFIG);
+ if (config == null || config.size() != 1) {
+ return false;
+ }
+
+ return config.get(0).equalsIgnoreCase(REDIRECT_302_TO_WELL_KNOWN);
+ }
+
+ private Uri getLocationRedirectToWellKnownUri(Uri redirectUri) {
+ return redirectUri
+ .buildUpon()
+ .encodedPath(WELL_KNOWN_PATH_SEGMENT)
+ .clearQuery()
+ .appendQueryParameter(WELL_KNOWN_QUERY_PARAM, redirectUri.toString())
+ .build();
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistration.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistration.java
index 98c95bfe0e..ade38df694 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistration.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistration.java
@@ -51,9 +51,9 @@ public class AsyncRegistration {
private final boolean mDebugKeyAllowed;
private final boolean mAdIdPermission;
@Nullable private String mRegistrationId;
-
@Nullable private final String mPlatformAdId;
@Nullable private String mPostBody;
+ private final AsyncRedirect.RedirectBehavior mRedirectBehavior;
public enum RedirectType {
LOCATION,
@@ -77,6 +77,7 @@ public class AsyncRegistration {
mRegistrationId = builder.mRegistrationId;
mPlatformAdId = builder.mPlatformAdId;
mPostBody = builder.mPostBody;
+ mRedirectBehavior = builder.mRedirectBehavior;
}
@Override
@@ -99,7 +100,8 @@ public class AsyncRegistration {
&& mType == that.mType
&& Objects.equals(mRegistrationId, that.mRegistrationId)
&& mPlatformAdId.equals(that.mPlatformAdId)
- && Objects.equals(mPostBody, that.mPostBody);
+ && Objects.equals(mPostBody, that.mPostBody)
+ && Objects.equals(mRedirectBehavior, that.mRedirectBehavior);
}
@Override
@@ -120,7 +122,8 @@ public class AsyncRegistration {
mAdIdPermission,
mRegistrationId,
mPlatformAdId,
- mPostBody);
+ mPostBody,
+ mRedirectBehavior);
}
/** Unique identifier for the {@link AsyncRegistration}. */
@@ -214,6 +217,11 @@ public class AsyncRegistration {
return mPostBody;
}
+ /** Return the configuration for redirect behavior. */
+ public AsyncRedirect.RedirectBehavior getRedirectBehavior() {
+ return mRedirectBehavior;
+ }
+
/** Increments the retry count of the current record. */
public void incrementRetryCount() {
++mRetryCount;
@@ -260,9 +268,9 @@ public class AsyncRegistration {
private boolean mDebugKeyAllowed;
private boolean mAdIdPermission;
@Nullable private String mRegistrationId;
-
@Nullable private String mPlatformAdId;
@Nullable private String mPostBody;
+ private AsyncRedirect.RedirectBehavior mRedirectBehavior;
/** See {@link AsyncRegistration#getId()}. */
@NonNull
@@ -385,6 +393,12 @@ public class AsyncRegistration {
return this;
}
+ /** See {@link AsyncRegistration#getRedirectBehavior()}. */
+ public Builder setRedirectBehavior(AsyncRedirect.RedirectBehavior redirectBehavior) {
+ mRedirectBehavior = redirectBehavior;
+ return this;
+ }
+
/** Build the {@link AsyncRegistration}. */
@NonNull
public AsyncRegistration build() {
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistrationQueueRunner.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistrationQueueRunner.java
index 207f8b3f0f..41f66aea2e 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistrationQueueRunner.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncRegistrationQueueRunner.java
@@ -210,10 +210,11 @@ public class AsyncRegistrationQueueRunner {
AsyncRegistration asyncRegistration, Set<Uri> failedOrigins) {
AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
asyncFetchStatus.setRetryCount(Long.valueOf(asyncRegistration.getRetryCount()).intValue());
- AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncRedirects asyncRedirects = new AsyncRedirects();
long startTime = asyncRegistration.getRequestTime();
Optional<Source> resultSource =
- mAsyncSourceFetcher.fetchSource(asyncRegistration, asyncFetchStatus, asyncRedirect);
+ mAsyncSourceFetcher.fetchSource(
+ asyncRegistration, asyncFetchStatus, asyncRedirects);
long endTime = System.currentTimeMillis();
asyncFetchStatus.setRegistrationDelay(endTime - startTime);
@@ -225,7 +226,7 @@ public class AsyncRegistrationQueueRunner {
storeSource(resultSource.get(), asyncRegistration, dao);
}
handleSuccess(
- asyncRegistration, asyncFetchStatus, asyncRedirect, dao);
+ asyncRegistration, asyncFetchStatus, asyncRedirects, dao);
} else {
handleFailure(
asyncRegistration, asyncFetchStatus, failedOrigins, dao);
@@ -273,10 +274,11 @@ public class AsyncRegistrationQueueRunner {
AsyncRegistration asyncRegistration, Set<Uri> failedOrigins) {
AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
asyncFetchStatus.setRetryCount(Long.valueOf(asyncRegistration.getRetryCount()).intValue());
- AsyncRedirect asyncRedirect = new AsyncRedirect();
+ AsyncRedirects asyncRedirects = new AsyncRedirects();
long startTime = asyncRegistration.getRequestTime();
- Optional<Trigger> resultTrigger = mAsyncTriggerFetcher.fetchTrigger(
- asyncRegistration, asyncFetchStatus, asyncRedirect);
+ Optional<Trigger> resultTrigger =
+ mAsyncTriggerFetcher.fetchTrigger(
+ asyncRegistration, asyncFetchStatus, asyncRedirects);
long endTime = System.currentTimeMillis();
asyncFetchStatus.setRegistrationDelay(endTime - startTime);
@@ -288,7 +290,7 @@ public class AsyncRegistrationQueueRunner {
storeTrigger(resultTrigger.get(), dao);
}
handleSuccess(
- asyncRegistration, asyncFetchStatus, asyncRedirect, dao);
+ asyncRegistration, asyncFetchStatus, asyncRedirects, dao);
} else {
handleFailure(
asyncRegistration, asyncFetchStatus, failedOrigins, dao);
@@ -583,11 +585,11 @@ public class AsyncRegistrationQueueRunner {
< FlagsFactory.getFlags().getMeasurementMaxTriggersPerDestination();
}
- private static AsyncRegistration createAsyncRegistrationFromRedirect(
- AsyncRegistration asyncRegistration, Uri redirectUri) {
+ private AsyncRegistration createAsyncRegistrationFromRedirect(
+ AsyncRegistration asyncRegistration, AsyncRedirect asyncRedirect) {
return new AsyncRegistration.Builder()
.setId(UUID.randomUUID().toString())
- .setRegistrationUri(redirectUri)
+ .setRegistrationUri(asyncRedirect.getUri())
.setWebDestination(asyncRegistration.getWebDestination())
.setOsDestination(asyncRegistration.getOsDestination())
.setRegistrant(asyncRegistration.getRegistrant())
@@ -600,6 +602,7 @@ public class AsyncRegistrationQueueRunner {
.setDebugKeyAllowed(asyncRegistration.getDebugKeyAllowed())
.setAdIdPermission(asyncRegistration.hasAdIdPermission())
.setRegistrationId(asyncRegistration.getRegistrationId())
+ .setRedirectBehavior(asyncRedirect.getRedirectBehavior())
.build();
}
@@ -690,14 +693,14 @@ public class AsyncRegistrationQueueRunner {
private void handleSuccess(
AsyncRegistration asyncRegistration,
AsyncFetchStatus asyncFetchStatus,
- AsyncRedirect asyncRedirect,
+ AsyncRedirects asyncRedirects,
IMeasurementDao dao)
throws DatastoreException {
// deleteAsyncRegistration will throw an exception & rollback the transaction if the record
// is already deleted. This can happen if both fallback & regular job are running at the
// same time or if deletion job deletes the records.
dao.deleteAsyncRegistration(asyncRegistration.getId());
- if (asyncRedirect.getRedirects().isEmpty()) {
+ if (asyncRedirects.getRedirects().isEmpty()) {
return;
}
int maxRedirects = FlagsFactory.getFlags().getMeasurementMaxRegistrationRedirects();
@@ -706,16 +709,17 @@ public class AsyncRegistrationQueueRunner {
asyncRegistration.getRegistrationId(),
DataType.REGISTRATION_REDIRECT_COUNT);
int currentCount = keyValueData.getRegistrationRedirectCount();
- if (currentCount == maxRedirects) {
+ if (currentCount >= maxRedirects) {
asyncFetchStatus.setRedirectError(true);
return;
}
- for (Uri uri : asyncRedirect.getRedirects()) {
+
+ for (AsyncRedirect asyncRedirect : asyncRedirects.getRedirects()) {
if (currentCount >= maxRedirects) {
break;
}
dao.insertAsyncRegistration(
- createAsyncRegistrationFromRedirect(asyncRegistration, uri));
+ createAsyncRegistrationFromRedirect(asyncRegistration, asyncRedirect));
currentCount++;
}
keyValueData.setRegistrationRedirectCount(currentCount);
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncSourceFetcher.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncSourceFetcher.java
index c1f3a30c77..26157a01b1 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncSourceFetcher.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncSourceFetcher.java
@@ -492,8 +492,9 @@ public class AsyncSourceFetcher {
Source.SourceType sourceType,
int maxEventLevelReports,
Source.TriggerDataMatching triggerDataMatching) {
- List<Pair<Long, Long>> parsedEventReportWindows = Source.getOrDefaultEventReportWindows(
- eventReportWindows, sourceType, expiry, mFlags);
+ List<Pair<Long, Long>> parsedEventReportWindows =
+ Source.getOrDefaultEventReportWindowsForFlex(
+ eventReportWindows, sourceType, expiry, mFlags);
long defaultStart = parsedEventReportWindows.get(0).first;
List<Long> defaultEnds =
parsedEventReportWindows.stream().map((x) -> x.second).collect(Collectors.toList());
@@ -799,7 +800,7 @@ public class AsyncSourceFetcher {
public Optional<Source> fetchSource(
AsyncRegistration asyncRegistration,
AsyncFetchStatus asyncFetchStatus,
- AsyncRedirect asyncRedirect) {
+ AsyncRedirects asyncRedirects) {
HttpURLConnection urlConnection = null;
Map<String, List<String>> headers;
if (!asyncRegistration.getRegistrationUri().getScheme().equalsIgnoreCase("https")) {
@@ -852,9 +853,7 @@ public class AsyncSourceFetcher {
}
}
- if (asyncRegistration.shouldProcessRedirects()) {
- FetcherUtil.parseRedirects(headers).forEach(asyncRedirect::addToRedirects);
- }
+ asyncRedirects.configure(headers, mFlags, asyncRegistration);
if (!isSourceHeaderPresent(headers)) {
asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.HEADER_MISSING);
@@ -864,7 +863,9 @@ public class AsyncSourceFetcher {
Optional<String> enrollmentId =
mFlags.isDisableMeasurementEnrollmentCheck()
- ? Optional.of(Enrollment.FAKE_ENROLLMENT)
+ ? WebAddresses.topPrivateDomainAndScheme(
+ asyncRegistration.getRegistrationUri())
+ .map(Uri::toString)
: Enrollment.getValidEnrollmentId(
asyncRegistration.getRegistrationUri(),
asyncRegistration.getRegistrant().getAuthority(),
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncTriggerFetcher.java b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncTriggerFetcher.java
index 9fd56f6c6f..d7d5e9e95d 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncTriggerFetcher.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/registration/AsyncTriggerFetcher.java
@@ -308,7 +308,7 @@ public class AsyncTriggerFetcher {
public Optional<Trigger> fetchTrigger(
AsyncRegistration asyncRegistration,
AsyncFetchStatus asyncFetchStatus,
- AsyncRedirect asyncRedirect) {
+ AsyncRedirects asyncRedirects) {
HttpURLConnection urlConnection = null;
Map<String, List<String>> headers;
if (!asyncRegistration.getRegistrationUri().getScheme().equalsIgnoreCase("https")) {
@@ -347,9 +347,7 @@ public class AsyncTriggerFetcher {
}
}
- if (asyncRegistration.shouldProcessRedirects()) {
- FetcherUtil.parseRedirects(headers).forEach(asyncRedirect::addToRedirects);
- }
+ asyncRedirects.configure(headers, mFlags, asyncRegistration);
if (!isTriggerHeaderPresent(headers)) {
asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.HEADER_MISSING);
@@ -359,7 +357,9 @@ public class AsyncTriggerFetcher {
Optional<String> enrollmentId =
mFlags.isDisableMeasurementEnrollmentCheck()
- ? Optional.of(Enrollment.FAKE_ENROLLMENT)
+ ? WebAddresses.topPrivateDomainAndScheme(
+ asyncRegistration.getRegistrationUri())
+ .map(Uri::toString)
: Enrollment.getValidEnrollmentId(
asyncRegistration.getRegistrationUri(),
asyncRegistration.getRegistrant().getAuthority(),
@@ -627,8 +627,7 @@ public class AsyncTriggerFetcher {
JSONArray aggregateDeduplicationKeys) throws JSONException {
JSONArray validAggregateDeduplicationKeys = new JSONArray();
if (aggregateDeduplicationKeys.length()
- > FlagsFactory.getFlags()
- .getMeasurementMaxAggregateDeduplicationKeysPerRegistration()) {
+ > mFlags.getMeasurementMaxAggregateDeduplicationKeysPerRegistration()) {
LoggerFactory.getMeasurementLogger()
.d(
"Aggregate deduplication keys have more keys than permitted. %s",
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
index 0c8a801b0f..f6a7bdfb55 100644
--- 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
@@ -255,6 +255,7 @@ public class EnqueueAsyncRegistration {
.setPlatformAdId(platformAdIdValue)
.setPostBody(postBody)
.setRegistrationId(registrationId)
+ .setRedirectBehavior(AsyncRedirect.RedirectBehavior.AS_IS)
.build();
dao.insertAsyncRegistration(asyncRegistration);
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 d5d268e7c1..50c64886d2 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
@@ -48,15 +48,12 @@ import java.util.regex.Pattern;
* @hide
*/
class FetcherUtil {
- static final String REDIRECT_LIST_HEADER_KEY = "Attribution-Reporting-Redirect";
- static final String REDIRECT_LOCATION_HEADER_KEY = "Location";
static final Pattern HEX_PATTERN = Pattern.compile("\\p{XDigit}+");
/**
* Determine all redirects.
*
- * <p>Generates a list of: (url, allows_regular_redirects) tuples. Returns true if all steps
- * succeed. Returns false if there are any failures.
+ * <p>Generates a map of: (redirectType, List&lt;Uri&gt;)
*/
static Map<AsyncRegistration.RedirectType, List<Uri>> parseRedirects(
@NonNull Map<String, List<String>> headers) {
@@ -304,7 +301,7 @@ class FetcherUtil {
private static List<Uri> parseListRedirects(Map<String, List<String>> headers) {
List<Uri> redirects = new ArrayList<>();
- List<String> field = headers.get(REDIRECT_LIST_HEADER_KEY);
+ List<String> field = headers.get(AsyncRedirects.REDIRECT_LIST_HEADER_KEY);
int maxRedirects = FlagsFactory.getFlags().getMeasurementMaxRegistrationRedirects();
if (field != null) {
for (int i = 0; i < Math.min(field.size(), maxRedirects); i++) {
@@ -316,7 +313,7 @@ class FetcherUtil {
private static List<Uri> parseLocationRedirects(Map<String, List<String>> headers) {
List<Uri> redirects = new ArrayList<>();
- List<String> field = headers.get(REDIRECT_LOCATION_HEADER_KEY);
+ List<String> field = headers.get(AsyncRedirects.REDIRECT_LOCATION_HEADER_KEY);
if (field != null && !field.isEmpty()) {
redirects.add(Uri.parse(field.get(0)));
if (field.size() > 1) {
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 a4d1971070..0498d4a85c 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
@@ -50,7 +50,8 @@ public class AggregateReportBody {
private static final String API_NAME = "attribution-reporting";
- private interface PayloadBodyKeys {
+ @VisibleForTesting
+ interface PayloadBodyKeys {
String SHARED_INFO = "shared_info";
String AGGREGATION_SERVICE_PAYLOADS = "aggregation_service_payloads";
String SOURCE_DEBUG_KEY = "source_debug_key";
@@ -64,7 +65,8 @@ public class AggregateReportBody {
String DEBUG_CLEARTEXT_PAYLOAD = "debug_cleartext_payload";
}
- private interface SharedInfoKeys {
+ @VisibleForTesting
+ interface SharedInfoKeys {
String API_NAME = "api";
String ATTRIBUTION_DESTINATION = "attribution_destination";
String REPORT_ID = "report_id";
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 4367ee1b8b..849ec8164b 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
@@ -16,7 +16,10 @@
package com.android.adservices.service.measurement.reporting;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_ENCRYPTION_ERROR;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_NETWORK_ERROR;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_PARSING_ERROR;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_UNKNOWN_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MESUREMENT_REPORTS_UPLOADED;
@@ -289,7 +292,7 @@ public class AggregateReportingJobHandler {
// TODO(b/298330312): Change to defined error codes
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_NETWORK_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
return AdServicesStatusUtils.STATUS_IO_ERROR;
} catch (JSONException e) {
@@ -299,7 +302,7 @@ public class AggregateReportingJobHandler {
// TODO(b/298330312): Change to defined error codes
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_PARSING_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
if (mFlags.getMeasurementEnableReportDeletionOnUnrecoverableException()) {
// Unrecoverable state - delete the report.
@@ -324,7 +327,7 @@ public class AggregateReportingJobHandler {
// TODO(b/298330312): Change to defined error codes
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_ENCRYPTION_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
if (mFlags.getMeasurementEnableReportingJobsThrowCryptoException()
&& ThreadLocalRandom.current().nextFloat()
@@ -338,7 +341,7 @@ public class AggregateReportingJobHandler {
// TODO(b/298330312): Change to defined error codes
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_UNKNOWN_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
if (mFlags.getMeasurementEnableReportingJobsThrowUnaccountedException()
&& ThreadLocalRandom.current().nextFloat()
@@ -371,8 +374,7 @@ public class AggregateReportingJobHandler {
.setTriggerDebugKey(aggregateReport.getTriggerDebugKey())
.setAggregationCoordinatorOrigin(aggregateReport.getAggregationCoordinatorOrigin())
.setDebugMode(
- mIsDebugInstance
- && aggregateReport.getSourceDebugKey() != null
+ aggregateReport.getSourceDebugKey() != null
&& aggregateReport.getTriggerDebugKey() != null
? "enabled"
: null)
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportApi.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportApi.java
index c4a6d7201e..778570c249 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportApi.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportApi.java
@@ -80,6 +80,7 @@ public class DebugReportApi {
String TRIGGER_UNKNOWN_ERROR = "trigger-unknown-error";
String TRIGGER_AGGREGATE_STORAGE_LIMIT = "trigger-aggregate-storage-limit";
String TRIGGER_AGGREGATE_EXCESSIVE_REPORTS = "trigger-aggregate-excessive-reports";
+ String TRIGGER_EVENT_REPORT_WINDOW_NOT_STARTED = "trigger-event-report-window-not-started";
}
/** Defines different verbose debug report body parameters. */
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobHandler.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobHandler.java
index 0650f67e90..4391b37254 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobHandler.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/DebugReportingJobHandler.java
@@ -16,7 +16,9 @@
package com.android.adservices.service.measurement.reporting;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_NETWORK_ERROR;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_PARSING_ERROR;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_UNKNOWN_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MESUREMENT_REPORTS_UPLOADED;
@@ -184,7 +186,7 @@ public class DebugReportingJobHandler {
.d(e, "Network error occurred when attempting to deliver debug report.");
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_NETWORK_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
reportingStatus.setFailureStatus(ReportingStatus.FailureStatus.NETWORK);
// TODO(b/298330312): Change to defined error codes
@@ -195,7 +197,7 @@ public class DebugReportingJobHandler {
// TODO(b/298330312): Change to defined error codes
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_PARSING_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
reportingStatus.setFailureStatus(ReportingStatus.FailureStatus.SERIALIZATION_ERROR);
if (mFlags.getMeasurementEnableReportDeletionOnUnrecoverableException()) {
@@ -216,7 +218,7 @@ public class DebugReportingJobHandler {
// TODO(b/298330312): Change to defined error codes
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_UNKNOWN_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
reportingStatus.setFailureStatus(ReportingStatus.FailureStatus.UNKNOWN);
if (mFlags.getMeasurementEnableReportingJobsThrowUnaccountedException()
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java
index 9d7564c43c..93f7913854 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java
@@ -54,36 +54,42 @@ public class EventReportWindowCalcDelegate {
}
/**
- * Max reports count based on conversion destination type and installation state.
+ * Max reports count given the Source object.
*
- * @param isInstallCase is app installed
+ * @param source the Source object
* @return maximum number of reports allowed
*/
- public int getMaxReportCount(@NonNull Source source, boolean isInstallCase) {
+ public int getMaxReportCount(Source source) {
+ return getMaxReportCount(source, source.isInstallDetectionEnabled());
+ }
+
+ /**
+ * Max reports count based on conversion destination type.
+ *
+ * @param source the Source object
+ * @param destinationType destination type
+ * @return maximum number of reports allowed
+ */
+ public int getMaxReportCount(@NonNull Source source, @EventSurfaceType int destinationType) {
+ return getMaxReportCount(source, isInstallCase(source, destinationType));
+ }
+
+ private int getMaxReportCount(@NonNull Source source, boolean isInstallCase) {
// TODO(b/290101531): Cleanup flags
if (mFlags.getMeasurementFlexLiteApiEnabled() && source.getMaxEventLevelReports() != null) {
return source.getMaxEventLevelReports();
}
- if (source.getSourceType() == Source.SourceType.EVENT
- && mFlags.getMeasurementEnableVtcConfigurableMaxEventReports()) {
- // Max VTC event reports are configurable
- int configuredMaxReports = mFlags.getMeasurementVtcConfigurableMaxEventReportsCount();
+
+ if (source.getSourceType() == Source.SourceType.EVENT) {
// Additional report essentially for first open + 1 post install conversion. If there
// is already more than 1 report allowed, no need to have that additional report.
- if (isInstallCase && configuredMaxReports == PrivacyParams.EVENT_SOURCE_MAX_REPORTS) {
+ if (isInstallCase && !source.hasWebDestinations() && isDefaultConfiguredVtc()) {
return PrivacyParams.INSTALL_ATTR_EVENT_SOURCE_MAX_REPORTS;
}
- return configuredMaxReports;
+ return mFlags.getMeasurementVtcConfigurableMaxEventReportsCount();
}
- if (isInstallCase) {
- return source.getSourceType() == Source.SourceType.EVENT
- ? PrivacyParams.INSTALL_ATTR_EVENT_SOURCE_MAX_REPORTS
- : PrivacyParams.INSTALL_ATTR_NAVIGATION_SOURCE_MAX_REPORTS;
- }
- return source.getSourceType() == Source.SourceType.EVENT
- ? PrivacyParams.EVENT_SOURCE_MAX_REPORTS
- : PrivacyParams.NAVIGATION_SOURCE_MAX_REPORTS;
+ return PrivacyParams.NAVIGATION_SOURCE_MAX_REPORTS;
}
/**
@@ -100,18 +106,14 @@ public class EventReportWindowCalcDelegate {
// Cases where source could have both web and app destinations, there if the trigger
// destination is an app, and it was installed, then installState should be considered true.
- boolean isAppInstalled = isAppInstalled(source, destinationType);
- List<Pair<Long, Long>> earlyReportingWindows =
- getEarlyReportingWindows(source, isAppInstalled);
- for (Pair<Long, Long> window : earlyReportingWindows) {
+ List<Pair<Long, Long>> reportingWindows =
+ getEffectiveReportingWindows(source, isInstallCase(source, destinationType));
+ for (Pair<Long, Long> window : reportingWindows) {
if (isWithinWindow(triggerTime, window)) {
return window.second + mFlags.getMeasurementMinEventReportDelayMillis();
}
}
- Pair<Long, Long> finalWindow = getFinalReportingWindow(source, earlyReportingWindows);
- if (isWithinWindow(triggerTime, finalWindow)) {
- return finalWindow.second + mFlags.getMeasurementMinEventReportDelayMillis();
- }
+
return -1;
}
@@ -120,45 +122,60 @@ public class EventReportWindowCalcDelegate {
}
/**
+ * Enum shows trigger time and source window time relationship. It is used to generate different
+ * verbose debug reports.
+ */
+ public enum MomentPlacement {
+ BEFORE,
+ AFTER,
+ WITHIN;
+ }
+
+ /**
+ * @param source source for which the window is calculated
+ * @param triggerTime time for the trigger
+ * @param destinationType trigger destination type
+ * @return how trigger time falls in source windows
+ */
+ public MomentPlacement fallsWithinWindow(
+ @NonNull Source source, long triggerTime, @EventSurfaceType int destinationType) {
+ List<Pair<Long, Long>> reportingWindows =
+ getEffectiveReportingWindows(source, isInstallCase(source, destinationType));
+ if (triggerTime < reportingWindows.get(0).first) {
+ return MomentPlacement.BEFORE;
+ }
+ if (triggerTime >= reportingWindows.get(reportingWindows.size() - 1).second) {
+ return MomentPlacement.AFTER;
+ }
+ return MomentPlacement.WITHIN;
+ }
+
+ /**
* Return reporting time by index for noising based on the index
*
* @param windowIndex index of the reporting window for which
* @return reporting time in milliseconds
*/
public long getReportingTimeForNoising(
- @NonNull Source source, int windowIndex, boolean isInstallCase) {
- List<Pair<Long, Long>> earlyWindows = getEarlyReportingWindows(source, isInstallCase);
- Pair<Long, Long> finalWindow = getFinalReportingWindow(source, earlyWindows);
- return windowIndex < earlyWindows.size()
- ? earlyWindows.get(windowIndex).second
+ @NonNull Source source, int windowIndex) {
+ List<Pair<Long, Long>> reportingWindows = getEffectiveReportingWindows(
+ source, source.isInstallDetectionEnabled());
+ Pair<Long, Long> finalWindow = reportingWindows.get(reportingWindows.size() - 1);
+ // TODO: (b/288646239) remove this check, confirming noising indexing accuracy.
+ return windowIndex < reportingWindows.size()
+ ? reportingWindows.get(windowIndex).second
+ mFlags.getMeasurementMinEventReportDelayMillis()
: finalWindow.second + mFlags.getMeasurementMinEventReportDelayMillis();
}
- private Pair<Long, Long> getFinalReportingWindow(
- Source source, List<Pair<Long, Long>> earlyWindows) {
- if (mFlags.getMeasurementFlexLiteApiEnabled() && source.hasManualEventReportWindows()) {
- List<Pair<Long, Long>> windowList = source.parsedProcessedEventReportWindows();
- return windowList.get(windowList.size() - 1);
- }
- long secondToLastWindowEnd =
- !earlyWindows.isEmpty() ? earlyWindows.get(earlyWindows.size() - 1).second : 0;
- if (source.getProcessedEventReportWindow() != null) {
- return new Pair<>(secondToLastWindowEnd, source.getProcessedEventReportWindow());
- }
- return new Pair<>(secondToLastWindowEnd, source.getExpiryTime());
- }
-
/**
- * Returns effective, i.e. the ones that occur before {@link
- * Source#getProcessedEventReportWindow()}, event reporting windows count for noising cases.
+ * Returns effective, that is, the ones that occur before {@link
+ * Source#getEffectiveEventReportWindow()}, event reporting windows count for noising cases.
*
* @param source source for which the count is requested
- * @param isInstallCase true of cool down window was specified
*/
- public int getReportingWindowCountForNoising(@NonNull Source source, boolean isInstallCase) {
- // Early Count + lastWindow
- return getEarlyReportingWindows(source, isInstallCase).size() + 1;
+ public int getReportingWindowCountForNoising(@NonNull Source source) {
+ return getEffectiveReportingWindows(source, source.isInstallDetectionEnabled()).size();
}
/**
@@ -213,47 +230,53 @@ public class EventReportWindowCalcDelegate {
return -1L;
}
- private boolean isAppInstalled(Source source, int destinationType) {
+ private static boolean isInstallCase(Source source, @EventSurfaceType int destinationType) {
return destinationType == EventSurfaceType.APP && source.isInstallAttributed();
}
/**
* If the flag is enabled and the specified report windows are valid, picks from flag controlled
- * configurable early reporting windows. Otherwise, falls back to the statical {@link
- * com.android.adservices.service.measurement.PrivacyParams} values. It curtails the windows
- * that occur after {@link Source#getProcessedEventReportWindow()} because they would
- * effectively be unusable.
+ * configurable early reporting windows. Otherwise, falls back to the values provided in
+ * {@code getDefaultEarlyReportingWindowEnds}, which can have install-related custom behaviour.
+ * It curtails the windows that occur after {@link Source#getEffectiveEventReportWindow()}
+ * because they would effectively be unusable.
*/
- private List<Pair<Long, Long>> getEarlyReportingWindows(Source source, boolean installState) {
+ private List<Pair<Long, Long>> getEffectiveReportingWindows(Source source,
+ boolean installState) {
// TODO(b/290221611) Remove early reporting windows from code, only use them for flags.
if (mFlags.getMeasurementFlexLiteApiEnabled() && source.hasManualEventReportWindows()) {
- List<Pair<Long, Long>> windows = source.parsedProcessedEventReportWindows();
- // Select early windows only i.e. skip the last element
- return windows.subList(0, windows.size() - 1);
+ return source.parsedProcessedEventReportWindows();
}
- List<Long> earlyWindows;
- List<Long> defaultEarlyWindows =
- getDefaultEarlyReportingWindows(source.getSourceType(), installState);
- earlyWindows =
- getConfiguredOrDefaultEarlyReportingWindows(
- source.getSourceType(), defaultEarlyWindows, true);
+ List<Long> defaultEarlyWindowEnds =
+ getDefaultEarlyReportingWindowEnds(
+ source.getSourceType(),
+ installState && !source.hasWebDestinations());
+ List<Long> earlyWindowEnds =
+ getConfiguredOrDefaultEarlyReportingWindowEnds(
+ source.getSourceType(), defaultEarlyWindowEnds);
// Add source event time to windows
- earlyWindows =
- earlyWindows.stream()
+ earlyWindowEnds =
+ earlyWindowEnds.stream()
.map((x) -> source.getEventTime() + x)
.collect(Collectors.toList());
List<Pair<Long, Long>> windowList = new ArrayList<>();
- long windowStart = 0;
+ long windowStart = 0L;
Pair<Long, Long> finalWindow =
- getFinalReportingWindow(source, createStartEndWindow(earlyWindows));
- for (long windowEnd : earlyWindows) {
- if (finalWindow.second <= windowEnd) {
- continue;
+ getFinalReportingWindow(source, earlyWindowEnds);
+
+ for (long windowEnd : earlyWindowEnds) {
+ // Start time of `finalWindow` is either 0 or one of `earlyWindowEnds` times; stop
+ // iterating if we see it, and add `finalWindow`.
+ if (windowStart == finalWindow.first) {
+ break;
}
- windowList.add(new Pair<>(windowStart, windowEnd));
+ windowList.add(Pair.create(windowStart, windowEnd));
windowStart = windowEnd;
}
+
+ windowList.add(finalWindow);
+
return ImmutableList.copyOf(windowList);
}
@@ -261,13 +284,13 @@ public class EventReportWindowCalcDelegate {
* Returns the default early reporting windows
*
* @param sourceType Source's Type
- * @param installState Install State of the source
+ * @param installAttributionEnabled whether windows for install attribution should be provided
* @return a list of windows
*/
- public static List<Long> getDefaultEarlyReportingWindows(
- Source.SourceType sourceType, boolean installState) {
+ public static List<Long> getDefaultEarlyReportingWindowEnds(
+ Source.SourceType sourceType, boolean installAttributionEnabled) {
long[] earlyWindows;
- if (installState) {
+ if (installAttributionEnabled) {
earlyWindows =
sourceType == Source.SourceType.EVENT
? INSTALL_ATTR_EVENT_EARLY_REPORTING_WINDOW_MILLISECONDS
@@ -281,53 +304,34 @@ public class EventReportWindowCalcDelegate {
return asList(earlyWindows);
}
- private List<Pair<Long, Long>> createStartEndWindow(List<Long> windowEnds) {
- List<Pair<Long, Long>> windows = new ArrayList<>();
- long start = 0;
- for (Long end : windowEnds) {
- windows.add(new Pair<>(start, end));
- start = end;
- }
- return windows;
- }
-
/**
* Returns default or configured (via flag) early reporting windows for the SourceType
*
* @param sourceType Source's Type
* @param defaultEarlyWindows default value for early windows
- * @param checkEnableFlag set true if configurable window flag should be checked
* @return list of windows
*/
- public List<Long> getConfiguredOrDefaultEarlyReportingWindows(
- Source.SourceType sourceType, List<Long> defaultEarlyWindows, boolean checkEnableFlag) {
- // TODO(b/290101531): Cleanup flags
- if (checkEnableFlag && !mFlags.getMeasurementEnableConfigurableEventReportingWindows()) {
- return defaultEarlyWindows;
+ public List<Long> getConfiguredOrDefaultEarlyReportingWindowEnds(
+ Source.SourceType sourceType, List<Long> defaultEarlyWindowEnds) {
+ // `defaultEarlyWindowEnds` may contain custom install-related logic, which we only apply if
+ // the configurable report windows (and max reports) are in their default state. Without
+ // this check, we may construct default-value report windows without the custom
+ // install-related logic applied.
+ if ((sourceType == Source.SourceType.EVENT && isDefaultConfiguredVtc())
+ || (sourceType == Source.SourceType.NAVIGATION && isDefaultConfiguredCtc())) {
+ return defaultEarlyWindowEnds;
}
String earlyReportingWindowsString = pickEarlyReportingWindowsConfig(mFlags, sourceType);
- if (earlyReportingWindowsString == null) {
- LoggerFactory.getMeasurementLogger()
- .d("Invalid configurable early reporting windows; null");
- return defaultEarlyWindows;
- }
-
if (earlyReportingWindowsString.isEmpty()) {
// No early reporting windows specified. It needs to be handled separately because
- // splitting an empty string results into an array containing a single element,
- // i.e. "". We want to handle it as an array having no element.
-
- if (Source.SourceType.EVENT.equals(sourceType)) {
- // We need to add a reporting window at 2d for post-install case. Non-install case
- // has no early reporting window by default.
- return defaultEarlyWindows;
- }
+ // splitting an empty string results in an array containing a single empty string. We
+ // want to handle it as an empty array.
return Collections.emptyList();
}
- ImmutableList.Builder<Long> earlyWindows = new ImmutableList.Builder<>();
+ ImmutableList.Builder<Long> earlyWindowEnds = new ImmutableList.Builder<>();
String[] split =
earlyReportingWindowsString.split(EARLY_REPORTING_WINDOWS_CONFIG_DELIMITER);
if (split.length > MAX_CONFIGURABLE_EVENT_REPORT_EARLY_REPORTING_WINDOWS) {
@@ -335,22 +339,54 @@ public class EventReportWindowCalcDelegate {
.d(
"Invalid configurable early reporting window; more than allowed size: "
+ MAX_CONFIGURABLE_EVENT_REPORT_EARLY_REPORTING_WINDOWS);
- return defaultEarlyWindows;
+ return defaultEarlyWindowEnds;
}
- for (String window : split) {
+ for (String windowEnd : split) {
try {
- earlyWindows.add(TimeUnit.SECONDS.toMillis(Long.parseLong(window)));
+ earlyWindowEnds.add(TimeUnit.SECONDS.toMillis(Long.parseLong(windowEnd)));
} catch (NumberFormatException e) {
LoggerFactory.getMeasurementLogger()
.d(e, "Configurable early reporting window parsing failed.");
- return defaultEarlyWindows;
+ return defaultEarlyWindowEnds;
+ }
+ }
+ return earlyWindowEnds.build();
+ }
+
+ private Pair<Long, Long> getFinalReportingWindow(
+ Source source, List<Long> earlyWindowEnds) {
+ // The latest end-time we can associate with a report for this source
+ long effectiveExpiry = Math.min(
+ source.getEffectiveEventReportWindow(), source.getExpiryTime());
+ // Find the latest end-time that can start a window ending at effectiveExpiry
+ for (int i = earlyWindowEnds.size() - 1; i >= 0; i--) {
+ long windowEnd = earlyWindowEnds.get(i);
+ if (windowEnd < effectiveExpiry) {
+ return Pair.create(windowEnd, effectiveExpiry);
}
}
- return earlyWindows.build();
+ return Pair.create(0L, effectiveExpiry);
+ }
+
+ /** Indicates whether VTC report windows and max reports are default configured, which can
+ * affect custom install-related attribution.
+ */
+ public boolean isDefaultConfiguredVtc() {
+ return mFlags.getMeasurementEventReportsVtcEarlyReportingWindows().isEmpty()
+ && mFlags.getMeasurementVtcConfigurableMaxEventReportsCount() == 1;
+ }
+
+ /** Indicates whether CTC report windows are default configured, which can affect custom
+ * install-related attribution.
+ */
+ private boolean isDefaultConfiguredCtc() {
+ return mFlags.getMeasurementEventReportsCtcEarlyReportingWindows().equals(
+ Flags.MEASUREMENT_EVENT_REPORTS_CTC_EARLY_REPORTING_WINDOWS);
}
- private String pickEarlyReportingWindowsConfig(Flags flags, Source.SourceType sourceType) {
+ private static String pickEarlyReportingWindowsConfig(Flags flags,
+ Source.SourceType sourceType) {
return sourceType == Source.SourceType.EVENT
? flags.getMeasurementEventReportsVtcEarlyReportingWindows()
: flags.getMeasurementEventReportsCtcEarlyReportingWindows();
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 f5042f3afd..c67f0acd67 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
@@ -16,7 +16,9 @@
package com.android.adservices.service.measurement.reporting;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_NETWORK_ERROR;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_PARSING_ERROR;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_UNKNOWN_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MESUREMENT_REPORTS_UPLOADED;
@@ -270,7 +272,7 @@ public class EventReportingJobHandler {
// TODO(b/298330312): Change to defined error codes
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_NETWORK_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
return AdServicesStatusUtils.STATUS_IO_ERROR;
} catch (JSONException e) {
@@ -281,7 +283,7 @@ public class EventReportingJobHandler {
// TODO(b/298330312): Change to defined error codes
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_PARSING_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
if (mFlags.getMeasurementEnableReportDeletionOnUnrecoverableException()) {
// Unrecoverable state - delete the report.
@@ -305,7 +307,7 @@ public class EventReportingJobHandler {
reportingStatus.setFailureStatus(ReportingStatus.FailureStatus.UNKNOWN);
ErrorLogUtil.e(
e,
- AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_CODE_UNSPECIFIED,
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REPORTING_UNKNOWN_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
if (mFlags.getMeasurementEnableReportingJobsThrowUnaccountedException()
&& ThreadLocalRandom.current().nextFloat()
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportingStatus.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportingStatus.java
index 8e7b1780fe..4e83edb8c9 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportingStatus.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportingStatus.java
@@ -52,7 +52,9 @@ public class ReportingStatus {
VERBOSE_DEBUG_TRIGGER_UNKNOWN_ERROR(26),
VERBOSE_DEBUG_TRIGGER_AGGREGATE_STORAGE_LIMIT(27),
VERBOSE_DEBUG_TRIGGER_AGGREGATE_EXCESSIVE_REPORTS(28),
- VERBOSE_DEBUG_UNKNOWN(29);
+ VERBOSE_DEBUG_TRIGGER_EVENT_REPORT_WINDOW_NOT_STARTED(29),
+ VERBOSE_DEBUG_UNKNOWN(9999);
+
private final int mValue;
ReportType(int value) {
@@ -202,6 +204,10 @@ public class ReportingStatus {
mReportType = ReportType.VERBOSE_DEBUG_TRIGGER_AGGREGATE_STORAGE_LIMIT;
} else if (reportType.equals(DebugReportApi.Type.TRIGGER_AGGREGATE_EXCESSIVE_REPORTS)) {
mReportType = ReportType.VERBOSE_DEBUG_TRIGGER_AGGREGATE_EXCESSIVE_REPORTS;
+ } else if (reportType.equals(DebugReportApi.Type.TRIGGER_EVENT_REPORT_WINDOW_NOT_STARTED)) {
+ mReportType = ReportType.VERBOSE_DEBUG_TRIGGER_EVENT_REPORT_WINDOW_NOT_STARTED;
+ } else {
+ mReportType = ReportType.VERBOSE_DEBUG_UNKNOWN;
}
}
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 7f71d2eb07..f98f138fb5 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
@@ -30,7 +30,6 @@ import java.util.Optional;
/** Enrollment utilities for measurement. */
public final class Enrollment {
- public static final String FAKE_ENROLLMENT = "fake_enrollment";
public static final String LOCALHOST_ENROLLMENT_ID = "localhost_enrollment_id";
public static final String LOCALHOST_IP_ENROLLMENT_ID = "localhost_ip_enrollment_id";
diff --git a/adservices/service-core/java/com/android/adservices/service/shell/AdServicesShellCommandHandler.java b/adservices/service-core/java/com/android/adservices/service/shell/AdServicesShellCommandHandler.java
index ab2227b46a..d93eba2db7 100644
--- a/adservices/service-core/java/com/android/adservices/service/shell/AdServicesShellCommandHandler.java
+++ b/adservices/service-core/java/com/android/adservices/service/shell/AdServicesShellCommandHandler.java
@@ -21,6 +21,7 @@ import android.util.Log;
import com.android.adservices.service.common.AppManifestConfigHelper;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -45,24 +46,30 @@ public final class AdServicesShellCommandHandler {
@VisibleForTesting
static final String CMD_IS_ALLOWED_ATTRIBUTION_ACCESS = "is-allowed-attribution-access";
+
@VisibleForTesting
static final String CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS =
"is-allowed-custom-audiences-access";
+
@VisibleForTesting
static final String CMD_IS_ALLOWED_TOPICS_ACCESS = "is-allowed-topics-access";
+
@VisibleForTesting
static final String HELP_ECHO =
CMD_ECHO + " <message> - prints the given message (useful to check cmd is working).";
+
@VisibleForTesting
static final String HELP_IS_ALLOWED_ATTRIBUTION_ACCESS =
CMD_IS_ALLOWED_ATTRIBUTION_ACCESS
+ " <package_name> <enrollment_id> - checks if the given enrollment id is"
+ " allowed to use the Attribution APIs in the given app.";
+
@VisibleForTesting
static final String HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS =
CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS
+ " <package_name> <enrollment_id> - checks if the given enrollment id is"
+ " allowed to use the Custom Audience APIs in the given app.";
+
@VisibleForTesting
static final String HELP_IS_ALLOWED_TOPICS_ACCESS =
CMD_IS_ALLOWED_TOPICS_ACCESS
@@ -74,6 +81,7 @@ public final class AdServicesShellCommandHandler {
@VisibleForTesting
static final String ERROR_TEMPLATE_INVALID_ARGS = "Invalid cmd (%s). Syntax: %s";
+
// TODO(b/280460130): use adservice helpers for tag name / logging methods
private static final String TAG = "AdServicesShellCmd";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -81,23 +89,27 @@ public final class AdServicesShellCommandHandler {
private static final int RESULT_GENERIC_ERROR = -1;
private final PrintWriter mOut;
+ private final PrintWriter mErr;
private String[] mArgs;
private int mArgPos;
private String mCurArgData;
+ /** If PrintWriter {@code err} is not provided, we use {@code out} for the {@code err}. */
public AdServicesShellCommandHandler(PrintWriter out) {
+ this(out, /* err= */ out);
+ }
+
+ public AdServicesShellCommandHandler(PrintWriter out, PrintWriter err) {
mOut = Objects.requireNonNull(out, "out cannot be null");
+ mErr = Objects.requireNonNull(err, "err cannot be null");
}
/** Runs the given command ({@code args[0]}) and optional arguments */
public int run(String... args) {
Objects.requireNonNull(args, "args cannot be null");
- // TODO(b/303886367): use Preconditions
- if (args.length < 1) {
- throw new IllegalArgumentException(
- "must have at least one argument (the command itself)");
- }
+ Preconditions.checkArgument(
+ args.length >= 1, "must have at least one argument (the command itself)");
if (DEBUG) {
Log.d(TAG, "run(): " + Arrays.toString(args));
}
@@ -112,13 +124,14 @@ public final class AdServicesShellCommandHandler {
}
} catch (Throwable e) {
// TODO(b/308009734): need to test this
- mOut.printf("Exception occurred while executing %s\n", Arrays.toString(mArgs));
+ mErr.printf("Exception occurred while executing %s\n", Arrays.toString(mArgs));
e.printStackTrace(mOut);
} finally {
if (DEBUG) {
Log.d(TAG, "Flushing output");
}
mOut.flush();
+ mErr.flush();
}
if (DEBUG) {
Log.d(TAG, "Sending command result: " + res);
@@ -168,7 +181,7 @@ public final class AdServicesShellCommandHandler {
}
private int invalidArgsError(String syntax) {
- mOut.println(String.format(ERROR_TEMPLATE_INVALID_ARGS, Arrays.toString(mArgs), syntax));
+ mErr.println(String.format(ERROR_TEMPLATE_INVALID_ARGS, Arrays.toString(mArgs), syntax));
return RESULT_GENERIC_ERROR;
}
@@ -188,9 +201,9 @@ public final class AdServicesShellCommandHandler {
case CMD_SHORT_HELP:
case CMD_HELP:
onHelp();
- return RESULT_GENERIC_ERROR;
+ return RESULT_OK;
case "":
- mOut.println(ERROR_EMPTY_COMMAND);
+ mErr.println(ERROR_EMPTY_COMMAND);
return RESULT_GENERIC_ERROR;
case CMD_ECHO:
return runEcho();
@@ -199,7 +212,7 @@ public final class AdServicesShellCommandHandler {
case CMD_IS_ALLOWED_TOPICS_ACCESS:
return runIsAllowedApiAccess(cmd);
default:
- mOut.printf("Unknown command: %s\n", cmd);
+ mErr.printf("Unknown command: %s\n", cmd);
return RESULT_GENERIC_ERROR;
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/shell/ShellCommandServiceImpl.java b/adservices/service-core/java/com/android/adservices/service/shell/ShellCommandServiceImpl.java
new file mode 100644
index 0000000000..285396c155
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/shell/ShellCommandServiceImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 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.shell;
+
+import android.adservices.shell.IShellCommand;
+import android.adservices.shell.IShellCommandCallback;
+import android.adservices.shell.ShellCommandParam;
+import android.adservices.shell.ShellCommandResult;
+import android.os.RemoteException;
+
+import com.android.adservices.LogUtil;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Implements a service which runs shell command in the AdServices process.
+ *
+ * <p>This internally calls {@link AdServicesShellCommandHandler} which has main logic to execute
+ * the shell command.
+ *
+ * @hide
+ */
+public final class ShellCommandServiceImpl extends IShellCommand.Stub {
+ @Override
+ public void runShellCommand(ShellCommandParam param, IShellCommandCallback callback) {
+ StringWriter outStringWriter = new StringWriter();
+ StringWriter ErrStringWriter = new StringWriter();
+
+ try (PrintWriter outPw = new PrintWriter(outStringWriter);
+ PrintWriter errPw = new PrintWriter(ErrStringWriter); ) {
+ AdServicesShellCommandHandler handler = new AdServicesShellCommandHandler(outPw, errPw);
+ int resultCode = handler.run(param.getCommandArgs());
+ ShellCommandResult response =
+ new ShellCommandResult.Builder()
+ .setResultCode(resultCode)
+ .setOut(outStringWriter.toString())
+ .setErr(ErrStringWriter.toString())
+ .build();
+ callback.onResult(response);
+ } catch (RemoteException e) {
+ LogUtil.e(e, "Unable to send result to the callback for request: %s", param);
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/signals/PeriodicEncodingJobWorker.java b/adservices/service-core/java/com/android/adservices/service/signals/PeriodicEncodingJobWorker.java
index 08a6c65254..b792dc7733 100644
--- a/adservices/service-core/java/com/android/adservices/service/signals/PeriodicEncodingJobWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/signals/PeriodicEncodingJobWorker.java
@@ -24,10 +24,11 @@ import com.android.adservices.LoggerFactory;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.data.signals.DBEncodedPayload;
import com.android.adservices.data.signals.DBEncoderLogicMetadata;
+import com.android.adservices.data.signals.DBSignalsUpdateMetadata;
import com.android.adservices.data.signals.EncodedPayloadDao;
import com.android.adservices.data.signals.EncoderLogicHandler;
import com.android.adservices.data.signals.EncoderLogicMetadataDao;
-import com.android.adservices.data.signals.EncoderPersistenceDao;
+import com.android.adservices.data.signals.ProtectedSignalsDao;
import com.android.adservices.data.signals.ProtectedSignalsDatabase;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
@@ -75,9 +76,9 @@ public class PeriodicEncodingJobWorker {
private final EncoderLogicHandler mEncoderLogicHandler;
private final EncoderLogicMetadataDao mEncoderLogicMetadataDao;
- private final EncoderPersistenceDao mEncoderPersistenceDao;
private final EncodedPayloadDao mEncodedPayloadDao;
private final SignalsProvider mSignalsProvider;
+ private final ProtectedSignalsDao mProtectedSignalsDao;
private final AdSelectionScriptEngine mScriptEngine;
private final ListeningExecutorService mBackgroundExecutor;
private final ListeningExecutorService mLightWeightExecutor;
@@ -92,9 +93,9 @@ public class PeriodicEncodingJobWorker {
protected PeriodicEncodingJobWorker(
@NonNull EncoderLogicHandler encoderLogicHandler,
@NonNull EncoderLogicMetadataDao encoderLogicMetadataDao,
- @NonNull EncoderPersistenceDao encoderPersistenceDao,
@NonNull EncodedPayloadDao encodedPayloadDao,
@NonNull SignalsProviderImpl signalStorageManager,
+ @NonNull ProtectedSignalsDao protectedSignalsDao,
@NonNull AdSelectionScriptEngine scriptEngine,
@NonNull ListeningExecutorService backgroundExecutor,
@NonNull ListeningExecutorService lightWeightExecutor,
@@ -102,9 +103,9 @@ public class PeriodicEncodingJobWorker {
@NonNull Flags flags) {
mEncoderLogicHandler = encoderLogicHandler;
mEncoderLogicMetadataDao = encoderLogicMetadataDao;
- mEncoderPersistenceDao = encoderPersistenceDao;
mEncodedPayloadDao = encodedPayloadDao;
mSignalsProvider = signalStorageManager;
+ mProtectedSignalsDao = protectedSignalsDao;
mScriptEngine = scriptEngine;
mBackgroundExecutor = backgroundExecutor;
mLightWeightExecutor = lightWeightExecutor;
@@ -135,9 +136,9 @@ public class PeriodicEncodingJobWorker {
new PeriodicEncodingJobWorker(
new EncoderLogicHandler(context),
signalsDatabase.getEncoderLogicMetadataDao(),
- EncoderPersistenceDao.getInstance(context),
signalsDatabase.getEncodedPayloadDao(),
new SignalsProviderImpl(signalsDatabase.protectedSignalsDao()),
+ signalsDatabase.protectedSignalsDao(),
new AdSelectionScriptEngine(
context,
() ->
@@ -242,12 +243,30 @@ public class PeriodicEncodingJobWorker {
FluentFuture<Void> runEncodingPerBuyer(
DBEncoderLogicMetadata encoderLogicMetadata, int timeout) {
AdTechIdentifier buyer = encoderLogicMetadata.getBuyer();
+
Map<String, List<ProtectedSignal>> signals = mSignalsProvider.getSignals(buyer);
if (signals.isEmpty()) {
mEncoderLogicHandler.deleteEncoderForBuyer(buyer);
return FluentFuture.from(Futures.immediateFuture(null));
}
+ DBSignalsUpdateMetadata signalsUpdateMetadata =
+ mProtectedSignalsDao.getSignalsUpdateMetadata(buyer);
+ DBEncodedPayload existingPayload = mEncodedPayloadDao.getEncodedPayload(buyer);
+ if (signalsUpdateMetadata != null && existingPayload != null) {
+ boolean isNoNewSignalUpdateAfterLastEncoding =
+ signalsUpdateMetadata
+ .getLastSignalsUpdatedTime()
+ .isBefore(existingPayload.getCreationTime());
+ boolean isEncoderLogicNotUpdatedAfterLastEncoding =
+ encoderLogicMetadata
+ .getCreationTime()
+ .isBefore(existingPayload.getCreationTime());
+ if (isNoNewSignalUpdateAfterLastEncoding && isEncoderLogicNotUpdatedAfterLastEncoding) {
+ return FluentFuture.from(Futures.immediateFuture(null));
+ }
+ }
+
int failedCount = encoderLogicMetadata.getFailedEncodingCount();
if (failedCount >= mEncoderLogicMaximumFailure) {
return FluentFuture.from(Futures.immediateFuture(null));
diff --git a/adservices/service-core/java/com/android/adservices/service/signals/ProtectedSignalsServiceImpl.java b/adservices/service-core/java/com/android/adservices/service/signals/ProtectedSignalsServiceImpl.java
index 902ac74117..9cac9eef52 100644
--- a/adservices/service-core/java/com/android/adservices/service/signals/ProtectedSignalsServiceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/signals/ProtectedSignalsServiceImpl.java
@@ -58,6 +58,7 @@ import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.internal.annotations.VisibleForTesting;
+import java.time.Clock;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -104,7 +105,8 @@ public class ProtectedSignalsServiceImpl extends IProtectedSignalsService.Stub {
new UpdateProcessorSelector(),
new UpdateEncoderEventHandler(context),
new SignalEvictionController()),
- new AdTechUriValidator(ADTECH_CALLER_NAME, "", CLASS_NAME, FIELD_NAME)),
+ new AdTechUriValidator(ADTECH_CALLER_NAME, "", CLASS_NAME, FIELD_NAME),
+ Clock.systemUTC()),
FledgeAuthorizationFilter.create(context, AdServicesLoggerImpl.getInstance()),
ConsentManager.getInstance(context),
DevContextFilter.create(context),
@@ -187,7 +189,7 @@ public class ProtectedSignalsServiceImpl extends IProtectedSignalsService.Stub {
}
// Caller permissions must be checked in the binder thread, before anything else
- mFledgeAuthorizationFilter.assertAppDeclaredProtectedSignalsPermission(
+ mFledgeAuthorizationFilter.assertAppDeclaredPermission(
mContext, updateSignalsInput.getCallerPackageName(), apiName);
final int callerUid = getCallingUid(apiName);
diff --git a/adservices/service-core/java/com/android/adservices/service/signals/SignalsMaintenanceTasksWorker.java b/adservices/service-core/java/com/android/adservices/service/signals/SignalsMaintenanceTasksWorker.java
index 2df832c1ab..0205389b1d 100644
--- a/adservices/service-core/java/com/android/adservices/service/signals/SignalsMaintenanceTasksWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/signals/SignalsMaintenanceTasksWorker.java
@@ -109,18 +109,20 @@ public class SignalsMaintenanceTasksWorker {
* </ul>
*/
public void clearInvalidProtectedSignalsData() {
- Instant expirationInstant =
- mClock.instant().minusSeconds(ProtectedSignal.EXPIRATION_SECONDS);
- clearInvalidSignals(expirationInstant);
+ Instant now = mClock.instant();
+ Instant expirationInstant = now.minusSeconds(ProtectedSignal.EXPIRATION_SECONDS);
+ clearInvalidSignals(expirationInstant, now);
clearInvalidEncoders(expirationInstant);
clearInvalidEncodedPayloads(expirationInstant);
}
@VisibleForTesting
- void clearInvalidSignals(Instant expirationInstant) {
+ void clearInvalidSignals(Instant expirationInstant, Instant now) {
sLogger.v("Clearing expired signals older than %s", expirationInstant);
- int numExpiredSignals = mProtectedSignalsDao.deleteSignalsBeforeTime(expirationInstant);
+ int numExpiredSignals =
+ mProtectedSignalsDao.deleteExpiredSignalsAndUpdateSignalsUpdateMetadata(
+ expirationInstant, now);
sLogger.v("Cleared %d expired signals", numExpiredSignals);
// Read from flags directly, since this maintenance task worker is attached to a background
@@ -138,7 +140,8 @@ public class SignalsMaintenanceTasksWorker {
sLogger.v("Clearing signals for disallowed source apps");
int numDisallowedSourceAppSignals =
- mProtectedSignalsDao.deleteAllDisallowedPackageSignals(mPackageManager, mFlags);
+ mProtectedSignalsDao.deleteAllDisallowedPackageSignalsAndUpdateSignalUpdateMetadata(
+ mPackageManager, mFlags, now);
sLogger.v("Cleared %d signals for disallowed source apps", numDisallowedSourceAppSignals);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/signals/UpdateProcessingOrchestrator.java b/adservices/service-core/java/com/android/adservices/service/signals/UpdateProcessingOrchestrator.java
index 9a3c004da4..476445b87d 100644
--- a/adservices/service-core/java/com/android/adservices/service/signals/UpdateProcessingOrchestrator.java
+++ b/adservices/service-core/java/com/android/adservices/service/signals/UpdateProcessingOrchestrator.java
@@ -115,7 +115,7 @@ public class UpdateProcessingOrchestrator {
mSignalEvictionController.evict(adtech, updatedSignals, combinedUpdates);
- writeChanges(adtech, combinedUpdates, devContext);
+ writeChanges(adtech, creationTime, combinedUpdates, devContext);
} catch (JSONException e) {
throw new IllegalArgumentException("Couldn't unpack signal updates JSON", e);
}
@@ -195,11 +195,16 @@ public class UpdateProcessingOrchestrator {
}
private void writeChanges(
- AdTechIdentifier adTech, UpdateOutput combinedUpdates, DevContext devContext) {
+ AdTechIdentifier adTech,
+ Instant creationTime,
+ UpdateOutput combinedUpdates,
+ DevContext devContext) {
/* Modify the DB based on the output of the update processors. Might be worth skipping
* this is both signalsToAdd and signalsToDelete are empty.
*/
mProtectedSignalsDao.insertAndDelete(
+ adTech,
+ creationTime,
combinedUpdates.getToAdd().stream()
.map(DBProtectedSignal.Builder::build)
.collect(Collectors.toList()),
diff --git a/adservices/service-core/java/com/android/adservices/service/signals/UpdateSignalsOrchestrator.java b/adservices/service-core/java/com/android/adservices/service/signals/UpdateSignalsOrchestrator.java
index 24ff9be759..73ad03455b 100644
--- a/adservices/service-core/java/com/android/adservices/service/signals/UpdateSignalsOrchestrator.java
+++ b/adservices/service-core/java/com/android/adservices/service/signals/UpdateSignalsOrchestrator.java
@@ -27,7 +27,7 @@ import com.google.common.util.concurrent.FluentFuture;
import org.json.JSONObject;
-import java.time.Instant;
+import java.time.Clock;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -38,20 +38,24 @@ public class UpdateSignalsOrchestrator {
@NonNull private final UpdatesDownloader mUpdatesDownloader;
@NonNull private final UpdateProcessingOrchestrator mUpdateProcessingOrchestrator;
@NonNull private final AdTechUriValidator mAdTechUriValidator;
+ @NonNull private final Clock mClock;
public UpdateSignalsOrchestrator(
@NonNull Executor backgroundExecutor,
@NonNull UpdatesDownloader updatesDownloader,
@NonNull UpdateProcessingOrchestrator updateProcessingOrchestrator,
- @NonNull AdTechUriValidator adTechUriValidator) {
+ @NonNull AdTechUriValidator adTechUriValidator,
+ @NonNull Clock clock) {
Objects.requireNonNull(backgroundExecutor);
Objects.requireNonNull(updatesDownloader);
Objects.requireNonNull(updateProcessingOrchestrator);
Objects.requireNonNull(adTechUriValidator);
+ Objects.requireNonNull(clock);
mBackgroundExecutor = backgroundExecutor;
mUpdateProcessingOrchestrator = updateProcessingOrchestrator;
mUpdatesDownloader = updatesDownloader;
mAdTechUriValidator = adTechUriValidator;
+ mClock = clock;
}
/**
@@ -70,7 +74,7 @@ public class UpdateSignalsOrchestrator {
return jsonFuture.transform(
x -> {
mUpdateProcessingOrchestrator.processUpdates(
- adtech, packageName, Instant.now(), x, devContext);
+ adtech, packageName, mClock.instant(), x, devContext);
return null;
},
mBackgroundExecutor);
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/ConsentMigrationStats.java b/adservices/service-core/java/com/android/adservices/service/stats/ConsentMigrationStats.java
index b5437a8da2..d877f45dd6 100644
--- a/adservices/service-core/java/com/android/adservices/service/stats/ConsentMigrationStats.java
+++ b/adservices/service-core/java/com/android/adservices/service/stats/ConsentMigrationStats.java
@@ -19,6 +19,8 @@ import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICE
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_CONSENT_MIGRATED__MIGRATION_STATUS__SUCCESS_WITH_SHARED_PREF_NOT_UPDATED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_CONSENT_MIGRATED__MIGRATION_STATUS__SUCCESS_WITH_SHARED_PREF_UPDATED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_CONSENT_MIGRATED__MIGRATION_STATUS__UNSPECIFIED_MIGRATION_STATUS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__ADEXT_SERVICE_TO_APPSEARCH;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__ADEXT_SERVICE_TO_SYSTEM_SERVICE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__APPSEARCH_TO_SYSTEM_SERVICE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__PPAPI_TO_SYSTEM_SERVICE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__UNSPECIFIED_MIGRATION_TYPE;
@@ -75,7 +77,14 @@ public abstract class ConsentMigrationStats {
AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__PPAPI_TO_SYSTEM_SERVICE),
// Migrating consent from App Search to system service
APPSEARCH_TO_SYSTEM_SERVICE(
- AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__APPSEARCH_TO_SYSTEM_SERVICE);
+ AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__APPSEARCH_TO_SYSTEM_SERVICE),
+ // Migrating consent from AdServicesExtDataStorageService to System Server
+ ADEXT_SERVICE_TO_SYSTEM_SERVICE(
+ AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__ADEXT_SERVICE_TO_SYSTEM_SERVICE),
+ // Migrating consent from AdServicesExtDataStorageService to App Search
+ ADEXT_SERVICE_TO_APPSEARCH(
+ AD_SERVICES_CONSENT_MIGRATED__MIGRATION_TYPE__ADEXT_SERVICE_TO_APPSEARCH);
+
private final int mMigrationType;
MigrationType(int migrationType) {
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/EncryptionManager.java b/adservices/service-core/java/com/android/adservices/service/topics/EncryptionManager.java
index a9a56b8aaa..4f147fe67f 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/EncryptionManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/EncryptionManager.java
@@ -116,8 +116,9 @@ public class EncryptionManager {
* public key is missing.
*/
private Optional<String> fetchPublicKeyFor(String sdkName) {
- if (mFlags.isDisableTopicsEnrollmentCheck()) {
- return Optional.of(TEST_PUBLIC_KEY_BASE64);
+ if (!mFlags.getTopicsTestEncryptionPublicKey().isEmpty()) {
+ // Use testing key for encryption if the test key is non-empty.
+ return Optional.of(mFlags.getTopicsTestEncryptionPublicKey());
}
sLogger.v("Fetching EnrollmentData for %s", sdkName);
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 5091491c27..683a63d9ff 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
@@ -31,6 +31,7 @@ import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICE
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_CALLBACK_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PACKAGE_NAME_NOT_FOUND_EXCEPTION;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__RATE_LIMIT_CALLBACK_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__TOPICS_REQUEST_EMPTY_SDK_NAME;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__TOPICS;
import android.adservices.common.AdServicesStatusUtils;
@@ -147,7 +148,7 @@ public class TopicsServiceImpl extends ITopicsService.Stub {
// Check if the request is valid.
if (!validateRequest(topicsParam, callback)) {
// Return early if the request is invalid.
- sLogger.d("Invalid request %s", topicsParam);
+ sLogger.e("Invalid request %s", topicsParam);
return;
}
}
@@ -202,6 +203,9 @@ public class TopicsServiceImpl extends ITopicsService.Stub {
GetTopicsParam topicsParam, IGetTopicsCallback callback) {
// Return false if sdkName is empty or null.
if (TextUtils.isEmpty(topicsParam.getSdkName())) {
+ ErrorLogUtil.e(
+ AD_SERVICES_ERROR_REPORTED__ERROR_CODE__TOPICS_REQUEST_EMPTY_SDK_NAME,
+ AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__TOPICS);
invokeCallbackWithStatus(
callback,
STATUS_INVALID_ARGUMENT,
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierInputManager.java b/adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierInputManager.java
index 8e257aff4b..139d2d9ba6 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierInputManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/classifier/ClassifierInputManager.java
@@ -138,7 +138,7 @@ public class ClassifierInputManager {
applicationInfo.descriptionRes)));
break;
default:
- LogUtil.e("Invalid input field in config: {}", inputField);
+ LogUtil.e("Invalid input field in config: %s", inputField);
return null;
}
}
@@ -171,7 +171,7 @@ public class ClassifierInputManager {
PackageManager packageManager, ApplicationInfo applicationInfo, int resourceId) {
if (!SdkLevel.isAtLeastS()) {
LogUtil.d(
- "English app resource not available for SDK version {} - "
+ "English app resource not available for SDK version %d - "
+ "returning localized app resource",
Build.VERSION.SDK_INT);
return getLocalAppResource(packageManager, applicationInfo, resourceId);
@@ -192,7 +192,7 @@ public class ClassifierInputManager {
} catch (PackageManager.NameNotFoundException e) {
LogUtil.e("No resources returned from packageManager.");
} catch (Resources.NotFoundException e) {
- LogUtil.e("Resource not found by packageManager - resourceId: {}", resourceId);
+ LogUtil.e("Resource not found by packageManager - resourceId: %d", resourceId);
}
return EMPTY_STRING;
}
@@ -204,7 +204,7 @@ public class ClassifierInputManager {
} catch (PackageManager.NameNotFoundException e) {
LogUtil.e("No resources returned from packageManager.");
} catch (Resources.NotFoundException e) {
- LogUtil.e("Resource not found by packageManager - resourceId: {}", resourceId);
+ LogUtil.e("Resource not found by packageManager - resourceId: %d", resourceId);
}
return EMPTY_STRING;
}
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 3a1f1934a8..4a2fda2f4d 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
@@ -665,7 +665,7 @@ public class ModelManager {
try {
inputFields.add(ClassifierInputField.valueOf(line));
} catch (IllegalArgumentException e) {
- LogUtil.e("Invalid input field in classifier input config: {}", line);
+ LogUtil.e("Invalid input field in classifier input config: %s", line);
return ClassifierInputConfig.getEmptyConfig();
}
}
@@ -696,7 +696,7 @@ public class ModelManager {
try {
String formattedInput =
String.format(classifierInputConfig.getInputFormat(), (Object[]) inputFields);
- LogUtil.d("Validated classifier input format: {}", formattedInput);
+ LogUtil.d("Validated classifier input format: %s", formattedInput);
} catch (IllegalFormatException e) {
LogUtil.e("Classifier input config is incorrectly formatted");
return false;
diff --git a/adservices/service-core/java/com/android/adservices/service/ui/data/UxStatesManager.java b/adservices/service-core/java/com/android/adservices/service/ui/data/UxStatesManager.java
index ea6c8203f9..9079562e3a 100644
--- a/adservices/service-core/java/com/android/adservices/service/ui/data/UxStatesManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/ui/data/UxStatesManager.java
@@ -102,7 +102,7 @@ public class UxStatesManager {
/** Returns process statble UX flags. */
public boolean getFlag(String uxFlagKey) {
if (!mUxFlags.containsKey(uxFlagKey)) {
- LogUtil.e("Key not found in cached UX flags: ", uxFlagKey);
+ LogUtil.e("Key not found in cached UX flags: %s", uxFlagKey);
}
Boolean value = mUxFlags.get(uxFlagKey);
return value != null ? value : false;
diff --git a/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/AlreadyEnrolledChannel.java b/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/AlreadyEnrolledChannel.java
index caea3ca6c4..bff7b772c5 100644
--- a/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/AlreadyEnrolledChannel.java
+++ b/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/AlreadyEnrolledChannel.java
@@ -56,12 +56,9 @@ public class AlreadyEnrolledChannel implements PrivacySandboxEnrollmentChannel {
private static boolean isPreNotificationManualUser(
ConsentManager consentManager, UxStatesManager uxStatesManager) {
- if (uxStatesManager.getFlag(KEY_CONSENT_ALREADY_INTERACTED_FIX_ENABLE)
+ return uxStatesManager.getFlag(KEY_CONSENT_ALREADY_INTERACTED_FIX_ENABLE)
&& consentManager.getUserManualInteractionWithConsent()
- == MANUAL_INTERACTIONS_RECORDED) {
- return true;
- }
- return false;
+ == MANUAL_INTERACTIONS_RECORDED;
}
/** No-Op if the user has already enrolled. */
diff --git a/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/ConsentNotificationResetChannel.java b/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/ConsentNotificationResetChannel.java
index 126e35346a..aa26433a8e 100644
--- a/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/ConsentNotificationResetChannel.java
+++ b/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/ConsentNotificationResetChannel.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.ui.enrollment.impl;
+import static com.android.adservices.service.consent.ConsentManager.NO_MANUAL_INTERACTIONS_RECORDED;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
@@ -72,6 +74,7 @@ public class ConsentNotificationResetChannel implements PrivacySandboxEnrollment
/** Perform enrollment logic for the reset channel. */
public void enroll(Context context, ConsentManager consentManager) {
+ consentManager.recordUserManualInteractionWithConsent(NO_MANUAL_INTERACTIONS_RECORDED);
consentManager.recordNotificationDisplayed(false);
consentManager.recordGaUxNotificationDisplayed(false);
consentManager.setU18NotificationDisplayed(false);
diff --git a/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/RvcPostOTAChannel.java b/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/RvcPostOTAChannel.java
index 6d6bc18d67..6d89bd4d3e 100644
--- a/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/RvcPostOTAChannel.java
+++ b/adservices/service-core/java/com/android/adservices/service/ui/enrollment/impl/RvcPostOTAChannel.java
@@ -16,7 +16,7 @@
package com.android.adservices.service.ui.enrollment.impl;
-import static com.android.adservices.service.FlagsConstants.KEY_RVC_NOTIFICATION_ENABLED;
+import static com.android.adservices.service.FlagsConstants.KEY_RVC_POST_OTA_NOTIFICATION_ENABLED;
import android.content.Context;
import android.os.Build;
@@ -40,8 +40,7 @@ public class RvcPostOTAChannel implements PrivacySandboxEnrollmentChannel {
ConsentManager consentManager,
UxStatesManager uxStatesManager) {
// Rvc user should be matched to RvcPostOTAChannel on S+
- // TODO: rename flag to KEY_RVC_POST_OTA_NOTIFICATION_ENABLED
- return uxStatesManager.getFlag(KEY_RVC_NOTIFICATION_ENABLED)
+ return uxStatesManager.getFlag(KEY_RVC_POST_OTA_NOTIFICATION_ENABLED)
&& consentManager.isOtaAdultUserFromRvc();
}