| /* |
| * 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__DATASTORE_EXCEPTION_WHILE_RECORDING_DEFAULT_CONSENT; |
| import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_MANUAL_CONSENT_INTERACTION; |
| import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_NOTIFICATION; |
| 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__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 android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.app.adservices.AdServicesManager; |
| import android.app.adservices.consent.ConsentParcel; |
| 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.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.AppSearchConsentManager; |
| import com.android.adservices.service.common.BackgroundJobsManager; |
| import com.android.adservices.service.common.feature.PrivacySandboxFeatureType; |
| 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.MeasurementWipeoutStats; |
| import com.android.adservices.service.stats.UiStatsLogger; |
| import com.android.adservices.service.topics.TopicsWorker; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.Preconditions; |
| 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.List; |
| import java.util.Objects; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Manager to handle user's consent. |
| * |
| * <p>For Beta the consent is given for all {@link AdServicesApiType} or for none. |
| * |
| * <p>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. |
| * </ul> |
| */ |
| // TODO(b/259791134): Add a CTS/UI test to test the Consent Migration |
| // TODO(b/269798827): Enable for R. |
| // TODO(b/279042385): move UI logs to UI. |
| @RequiresApi(Build.VERSION_CODES.S) |
| public class ConsentManager { |
| private static volatile ConsentManager 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 Context mContext; |
| private final Flags mFlags; |
| private final TopicsWorker mTopicsWorker; |
| private final BooleanFileDatastore mDatastore; |
| private final AppConsentDao mAppConsentDao; |
| private final EnrollmentDao mEnrollmentDao; |
| private final MeasurementImpl mMeasurementImpl; |
| private final CustomAudienceDao mCustomAudienceDao; |
| private final AppInstallDao mAppInstallDao; |
| private final AdServicesManager mAdServicesManager; |
| private final int mConsentSourceOfTruth; |
| private final AppSearchConsentManager mAppSearchConsentManager; |
| |
| private static final Object LOCK = new Object(); |
| |
| ConsentManager( |
| @NonNull Context context, |
| @NonNull TopicsWorker topicsWorker, |
| @NonNull AppConsentDao appConsentDao, |
| @NonNull EnrollmentDao enrollmentDao, |
| @NonNull MeasurementImpl measurementImpl, |
| @NonNull CustomAudienceDao customAudienceDao, |
| @NonNull AppInstallDao appInstallDao, |
| @NonNull AdServicesManager adServicesManager, |
| @NonNull BooleanFileDatastore booleanFileDatastore, |
| @NonNull AppSearchConsentManager appSearchConsentManager, |
| @NonNull Flags flags, |
| @Flags.ConsentSourceOfTruth int consentSourceOfTruth) { |
| Objects.requireNonNull(context); |
| Objects.requireNonNull(topicsWorker); |
| Objects.requireNonNull(appConsentDao); |
| Objects.requireNonNull(measurementImpl); |
| Objects.requireNonNull(customAudienceDao); |
| Objects.requireNonNull(appInstallDao); |
| Objects.requireNonNull(booleanFileDatastore); |
| |
| if (consentSourceOfTruth != Flags.PPAPI_ONLY |
| && consentSourceOfTruth != Flags.APPSEARCH_ONLY) { |
| Objects.requireNonNull(adServicesManager); |
| } |
| |
| if (flags.getEnableAppsearchConsentData()) { |
| Objects.requireNonNull(appSearchConsentManager); |
| } |
| |
| mContext = context; |
| mAdServicesManager = adServicesManager; |
| mTopicsWorker = topicsWorker; |
| mDatastore = booleanFileDatastore; |
| mAppConsentDao = appConsentDao; |
| mEnrollmentDao = enrollmentDao; |
| mMeasurementImpl = measurementImpl; |
| mCustomAudienceDao = customAudienceDao; |
| mAppInstallDao = appInstallDao; |
| |
| mAppSearchConsentManager = appSearchConsentManager; |
| mFlags = flags; |
| mConsentSourceOfTruth = consentSourceOfTruth; |
| } |
| |
| /** |
| * Gets an instance of {@link ConsentManager} 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 ConsentManager getInstance(@NonNull Context context) { |
| Objects.requireNonNull(context); |
| |
| if (sConsentManager == null) { |
| synchronized (LOCK) { |
| // Execute one-time consent migration if needed. |
| int consentSourceOfTruth = FlagsFactory.getFlags().getConsentSourceOfTruth(); |
| BooleanFileDatastore datastore = createAndInitializeDataStore(context); |
| AdServicesManager adServicesManager = 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; |
| } |
| AppSearchConsentManager appSearchConsentManager = null; |
| // Flag enable_appsearch_consent_data is true on S- and T+ only when we want to use |
| // AppSearch to write to or read from. |
| if (FlagsFactory.getFlags().getEnableAppsearchConsentData()) { |
| appSearchConsentManager = AppSearchConsentManager.getInstance(context); |
| handleConsentMigrationFromAppSearchIfNeeded( |
| context, |
| datastore, |
| appConsentDao, |
| appSearchConsentManager, |
| adServicesManager); |
| } |
| |
| // Attempt to migrate consent data from PPAPI to System server if needed. |
| handleConsentMigrationIfNeeded( |
| context, datastore, adServicesManager, consentSourceOfTruth); |
| |
| if (sConsentManager == null) { |
| sConsentManager = |
| new ConsentManager( |
| context, |
| TopicsWorker.getInstance(context), |
| appConsentDao, |
| EnrollmentDao.getInstance(context), |
| MeasurementImpl.getInstance(context), |
| CustomAudienceDatabase.getInstance(context).customAudienceDao(), |
| SharedStorageDatabase.getInstance(context).appInstallDao(), |
| adServicesManager, |
| datastore, |
| appSearchConsentManager, |
| // TODO(b/260601944): Remove Flag Instance. |
| FlagsFactory.getFlags(), |
| consentSourceOfTruth); |
| } |
| } |
| } |
| 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); |
| |
| UiStatsLogger.logOptInSelected(context); |
| |
| BackgroundJobsManager.scheduleAllBackgroundJobs(context); |
| |
| try { |
| // reset all state data which should be removed |
| resetTopicsAndBlockedTopics(); |
| resetAppsAndBlockedApps(); |
| resetMeasurement(); |
| } 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(context); |
| // Disable all the APIs |
| try { |
| // reset all data |
| resetTopicsAndBlockedTopics(); |
| resetAppsAndBlockedApps(); |
| resetMeasurement(); |
| resetEnrollment(); |
| |
| 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); |
| |
| UiStatsLogger.logOptInSelected(context, apiType); |
| |
| BackgroundJobsManager.scheduleJobsPerApi(context, apiType); |
| |
| try { |
| // reset all state data which should be removed |
| resetByApi(apiType); |
| } 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(context, 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; |
| } |
| |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return AdServicesApiConsent.getConsent( |
| mDatastore.get(ConsentConstants.CONSENT_KEY)); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| ConsentParcel consentParcel = |
| mAdServicesManager.getConsent(ConsentParcel.ALL_API); |
| return AdServicesApiConsent.getConsent(consentParcel.isIsGiven()); |
| // This is the default for back compat. All consent data is written to and |
| // read from AppSearch on S- devices. |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return AdServicesApiConsent.getConsent( |
| mAppSearchConsentManager.getConsent( |
| ConsentConstants.CONSENT_KEY_FOR_ALL)); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return AdServicesApiConsent.REVOKED; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, ConsentConstants.ERROR_MESSAGE_WHILE_GET_CONTENT); |
| ErrorLogUtil.e( |
| e, |
| AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT, |
| AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX); |
| } |
| |
| return AdServicesApiConsent.REVOKED; |
| } |
| } |
| |
| /** |
| * 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.getGaUxFeatureEnabled()) { |
| throw new IllegalStateException("GA UX feature is disabled."); |
| } |
| |
| if (mFlags.getConsentManagerDebugMode()) { |
| return AdServicesApiConsent.GIVEN; |
| } |
| |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return AdServicesApiConsent.getConsent( |
| mDatastore.get(apiType.toPpApiDatastoreKey())); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| ConsentParcel consentParcel = |
| mAdServicesManager.getConsent(apiType.toConsentApiType()); |
| return AdServicesApiConsent.getConsent(consentParcel.isIsGiven()); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return AdServicesApiConsent.getConsent( |
| mAppSearchConsentManager.getConsent( |
| apiType.toPpApiDatastoreKey())); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return AdServicesApiConsent.REVOKED; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, ConsentConstants.ERROR_MESSAGE_WHILE_GET_CONTENT); |
| ErrorLogUtil.e( |
| e, |
| AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ERROR_WHILE_GET_CONSENT, |
| AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX); |
| } |
| |
| return AdServicesApiConsent.REVOKED; |
| } |
| } |
| |
| /** |
| * 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() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| try { |
| return ImmutableList.copyOf( |
| mAppConsentDao.getKnownAppsWithConsent().stream() |
| .map(App::create) |
| .collect(Collectors.toList())); |
| } catch (IOException e) { |
| LogUtil.e(e, "getKnownAppsWithConsent failed due to IOException."); |
| } |
| return ImmutableList.of(); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return ImmutableList.copyOf( |
| mAdServicesManager |
| .getKnownAppsWithConsent( |
| new ArrayList<>( |
| mAppConsentDao.getInstalledPackages())) |
| .stream() |
| .map(App::create) |
| .collect(Collectors.toList())); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.getKnownAppsWithConsent(); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return ImmutableList.of(); |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Error get known apps with consent."); |
| } |
| return ImmutableList.of(); |
| } |
| } |
| |
| /** |
| * @return an {@link ImmutableList} of all known apps in the database that have had user consent |
| * revoked |
| */ |
| public ImmutableList<App> getAppsWithRevokedConsent() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| try { |
| return ImmutableList.copyOf( |
| mAppConsentDao.getAppsWithRevokedConsent().stream() |
| .map(App::create) |
| .collect(Collectors.toList())); |
| } catch (IOException e) { |
| LogUtil.e(e, "getAppsWithRevokedConsent() failed due to IOException."); |
| } |
| return ImmutableList.of(); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return ImmutableList.copyOf( |
| mAdServicesManager |
| .getAppsWithRevokedConsent( |
| new ArrayList<>( |
| mAppConsentDao.getInstalledPackages())) |
| .stream() |
| .map(App::create) |
| .collect(Collectors.toList())); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.getAppsWithRevokedConsent(); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return ImmutableList.of(); |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Error get apps with revoked consent."); |
| } |
| return ImmutableList.of(); |
| } |
| } |
| |
| /** |
| * 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 { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mAppConsentDao.setConsentForApp(app.getPackageName(), true); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.setConsentForApp( |
| app.getPackageName(), |
| mAppConsentDao.getUidForInstalledPackageName(app.getPackageName()), |
| true); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mAppConsentDao.setConsentForApp(app.getPackageName(), true); |
| mAdServicesManager.setConsentForApp( |
| app.getPackageName(), |
| mAppConsentDao.getUidForInstalledPackageName(app.getPackageName()), |
| true); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.revokeConsentForApp(app); |
| break; |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Error revoke consent for app %s", app.getPackageName()); |
| } |
| } |
| asyncExecute( |
| () -> mCustomAudienceDao.deleteCustomAudienceDataByOwner(app.getPackageName())); |
| if (mFlags.getFledgeAdSelectionFilteringEnabled()) { |
| asyncExecute(() -> mAppInstallDao.deleteByPackageName(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 { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mAppConsentDao.setConsentForApp(app.getPackageName(), false); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.setConsentForApp( |
| app.getPackageName(), |
| mAppConsentDao.getUidForInstalledPackageName(app.getPackageName()), |
| false); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mAppConsentDao.setConsentForApp(app.getPackageName(), false); |
| mAdServicesManager.setConsentForApp( |
| app.getPackageName(), |
| mAppConsentDao.getUidForInstalledPackageName(app.getPackageName()), |
| false); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.restoreConsentForApp(app); |
| break; |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Error restore consent for app %s", app.getPackageName()); |
| } |
| } |
| } |
| |
| /** |
| * 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 resetAppsAndBlockedApps() throws IOException { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mAppConsentDao.clearAllConsentData(); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.clearAllAppConsentData(); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mAppConsentDao.clearAllConsentData(); |
| mAdServicesManager.clearAllAppConsentData(); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.clearAllAppConsentData(); |
| break; |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Error reset apps and blocked apps."); |
| } |
| } |
| asyncExecute(mCustomAudienceDao::deleteAllCustomAudienceData); |
| if (mFlags.getFledgeAdSelectionFilteringEnabled()) { |
| asyncExecute(mAppInstallDao::deleteAllAppInstallData); |
| } |
| } |
| |
| /** |
| * 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 resetApps() throws IOException { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mAppConsentDao.clearKnownAppsWithConsent(); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.clearKnownAppsWithConsent(); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mAppConsentDao.clearKnownAppsWithConsent(); |
| mAdServicesManager.clearKnownAppsWithConsent(); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.clearKnownAppsWithConsent(); |
| break; |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Error reset apps."); |
| } |
| } |
| asyncExecute(mCustomAudienceDao::deleteAllCustomAudienceData); |
| if (mFlags.getFledgeAdSelectionFilteringEnabled()) { |
| asyncExecute(mAppInstallDao::deleteAllAppInstallData); |
| } |
| } |
| |
| /** |
| * 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 { |
| // TODO(b/238464639): Implement API-specific consent for FLEDGE |
| AdServicesApiConsent consent; |
| if (!mFlags.getGaUxFeatureEnabled()) { |
| consent = getConsent(); |
| } else { |
| consent = getConsent(AdServicesApiType.FLEDGE); |
| } |
| |
| if (!consent.isGiven()) { |
| return true; |
| } |
| |
| synchronized (LOCK) { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| try { |
| return mAppConsentDao.isConsentRevokedForApp(packageName); |
| } catch (IOException exception) { |
| LogUtil.e(exception, "FLEDGE consent check failed due to IOException"); |
| } |
| return true; |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.isConsentRevokedForApp( |
| packageName, mAppConsentDao.getUidForInstalledPackageName(packageName)); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.isFledgeConsentRevokedForApp(packageName); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return true; |
| } |
| } |
| } |
| |
| /** |
| * 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 { |
| // TODO(b/238464639): Implement API-specific consent for FLEDGE |
| AdServicesApiConsent consent; |
| if (!mFlags.getGaUxFeatureEnabled()) { |
| consent = getConsent(); |
| } else { |
| consent = getConsent(AdServicesApiType.FLEDGE); |
| } |
| |
| if (!consent.isGiven()) { |
| return true; |
| } |
| |
| synchronized (LOCK) { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| try { |
| return mAppConsentDao.setConsentForAppIfNew(packageName, false); |
| } catch (IOException exception) { |
| LogUtil.e(exception, "FLEDGE consent check failed due to IOException"); |
| return true; |
| } |
| case Flags.SYSTEM_SERVER_ONLY: |
| return mAdServicesManager.setConsentForAppIfNew( |
| packageName, |
| mAppConsentDao.getUidForInstalledPackageName(packageName), |
| false); |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| try { |
| mAppConsentDao.setConsentForAppIfNew(packageName, false); |
| } catch (IOException exception) { |
| LogUtil.e(exception, "FLEDGE consent check failed due to IOException"); |
| return true; |
| } |
| return mAdServicesManager.setConsentForAppIfNew( |
| packageName, |
| mAppConsentDao.getUidForInstalledPackageName(packageName), |
| false); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager |
| .isFledgeConsentRevokedForAppAfterSettingFledgeUse(packageName); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return true; |
| } |
| } |
| } |
| |
| /** |
| * Clear consent data after an app was uninstalled. |
| * |
| * @param packageName the package name that had been uninstalled. |
| * @param packageUid the package uid that had been uninstalled. |
| */ |
| public void clearConsentForUninstalledApp(String packageName, int packageUid) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| try { |
| mAppConsentDao.clearConsentForUninstalledApp(packageName, packageUid); |
| } catch (IOException exception) { |
| LogUtil.e( |
| exception, |
| "Clear consent for uninstalled app %s and uid %d failed due to" |
| + " IOException", |
| packageName, |
| packageUid); |
| } |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.clearConsentForUninstalledApp(packageName, packageUid); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| try { |
| mAppConsentDao.clearConsentForUninstalledApp(packageName, packageUid); |
| } catch (IOException exception) { |
| LogUtil.e( |
| exception, |
| "Clear consent for uninstalled app %s and uid %d failed due to" |
| + " IOException", |
| packageName, |
| packageUid); |
| } |
| mAdServicesManager.clearConsentForUninstalledApp(packageName, packageUid); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| // AppSearch is written only for S- where we don't have permission to |
| // receive UID info when package is uninstalled, so clear for all. |
| mAppSearchConsentManager.clearConsentForUninstalledApp(packageName); |
| break; |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e( |
| e, |
| "Error clear consent for uninstalled app %s and uid %d.", |
| 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) { |
| Objects.requireNonNull(packageName); |
| Preconditions.checkStringNotEmpty(packageName, "Package name should not be empty"); |
| |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| try { |
| mAppConsentDao.clearConsentForUninstalledApp(packageName); |
| } catch (IOException exception) { |
| LogUtil.e( |
| exception, |
| "Clear consent for uninstalled app %s failed due to" |
| + " IOException", |
| packageName); |
| } |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.clearConsentForUninstalledApp(packageName); |
| break; |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Error clear consent for uninstalled app %s.", 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. |
| * |
| * <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 void recordNotificationDisplayed() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE, true); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.recordNotificationDisplayed(); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE, true); |
| mAdServicesManager.recordNotificationDisplayed(); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.recordNotificationDisplayed(); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException("Record Notification Displayed failed", e); |
| } |
| } |
| } |
| |
| /** |
| * Retrieves if notification has been displayed. |
| * |
| * <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 true if Consent Notification was displayed, otherwise false. |
| */ |
| public Boolean wasNotificationDisplayed() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.wasNotificationDisplayed(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.wasNotificationDisplayed(); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get notification failed."); |
| ErrorLogUtil.e( |
| e, |
| AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_NOTIFICATION, |
| AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX); |
| } |
| |
| return false; |
| } |
| } |
| |
| /** |
| * Saves information to the storage that GA UX notification was displayed for the first time to |
| * the user. |
| * |
| * <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 void recordGaUxNotificationDisplayed() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE, true); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.recordGaUxNotificationDisplayed(); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE, true); |
| mAdServicesManager.recordGaUxNotificationDisplayed(); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.recordGaUxNotificationDisplayed(); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| ErrorLogUtil.e( |
| e, |
| AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_NOTIFICATION, |
| AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX); |
| throw new RuntimeException("Record GA UX Notification Displayed failed", e); |
| } |
| } |
| } |
| |
| /** |
| * Retrieves if GA UX notification has been displayed. |
| * |
| * <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 true if GA UX Consent Notification was displayed, otherwise false. |
| */ |
| public Boolean wasGaUxNotificationDisplayed() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.wasGaUxNotificationDisplayed(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.wasGaUxNotificationDisplayed(); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get GA UX notification failed."); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Retrieves the PP API default consent. |
| * |
| * <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 true if the topics default consent is true, false otherwise. |
| */ |
| public Boolean getDefaultConsent() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.DEFAULT_CONSENT); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.getDefaultConsent(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.getConsent( |
| ConsentConstants.DEFAULT_CONSENT); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get PP API default consent failed."); |
| ErrorLogUtil.e( |
| e, |
| AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_DEFAULT_CONSENT, |
| AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Retrieves the topics default consent. |
| * |
| * <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 true if the topics default consent is true, false otherwise. |
| */ |
| public Boolean getTopicsDefaultConsent() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.TOPICS_DEFAULT_CONSENT); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.getTopicsDefaultConsent(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.getConsent( |
| ConsentConstants.TOPICS_DEFAULT_CONSENT); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get topics default consent failed."); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Retrieves the FLEDGE default consent. |
| * |
| * <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 true if the FLEDGE default consent is true, false otherwise. |
| */ |
| public Boolean getFledgeDefaultConsent() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.FLEDGE_DEFAULT_CONSENT); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.getFledgeDefaultConsent(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.getConsent( |
| ConsentConstants.FLEDGE_DEFAULT_CONSENT); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get FLEDGE default consent failed."); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Retrieves the measurement default consent. |
| * |
| * <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 true if the measurement default consent is true, false otherwise. |
| */ |
| public Boolean getMeasurementDefaultConsent() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.MEASUREMENT_DEFAULT_CONSENT); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.getMeasurementDefaultConsent(); |
| case Flags.APPSEARCH_ONLY: |
| return mAppSearchConsentManager.getConsent( |
| ConsentConstants.MEASUREMENT_DEFAULT_CONSENT); |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get measurement default consent failed."); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Retrieves the default AdId state. |
| * |
| * <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 true if the AdId is enabled by default, false otherwise. |
| */ |
| public Boolean getDefaultAdIdState() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.DEFAULT_AD_ID_STATE); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.getDefaultAdIdState(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.getConsent( |
| ConsentConstants.DEFAULT_AD_ID_STATE); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get default AdId state failed."); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Saves the PP API default consent bit to storage. |
| * |
| * <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 void recordDefaultConsent(boolean defaultConsent) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put(ConsentConstants.DEFAULT_CONSENT, defaultConsent); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.recordDefaultConsent(defaultConsent); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put(ConsentConstants.DEFAULT_CONSENT, defaultConsent); |
| mAdServicesManager.recordDefaultConsent(defaultConsent); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setConsent( |
| ConsentConstants.DEFAULT_CONSENT, defaultConsent); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| ErrorLogUtil.e( |
| e, |
| AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_DEFAULT_CONSENT, |
| AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX); |
| throw new RuntimeException("Record default consent failed", e); |
| } |
| } |
| } |
| |
| /** |
| * Saves the topics default consent bit to storage. |
| * |
| * <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 void recordTopicsDefaultConsent(boolean defaultConsent) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put(ConsentConstants.TOPICS_DEFAULT_CONSENT, defaultConsent); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.recordTopicsDefaultConsent(defaultConsent); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put(ConsentConstants.TOPICS_DEFAULT_CONSENT, defaultConsent); |
| mAdServicesManager.recordTopicsDefaultConsent(defaultConsent); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setConsent( |
| ConsentConstants.TOPICS_DEFAULT_CONSENT, defaultConsent); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException("Record topics default consent failed", e); |
| } |
| } |
| } |
| |
| /** |
| * Saves the FLEDGE default consent bit to storage. |
| * |
| * <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 void recordFledgeDefaultConsent(boolean defaultConsent) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put(ConsentConstants.FLEDGE_DEFAULT_CONSENT, defaultConsent); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.recordFledgeDefaultConsent(defaultConsent); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put(ConsentConstants.FLEDGE_DEFAULT_CONSENT, defaultConsent); |
| mAdServicesManager.recordFledgeDefaultConsent(defaultConsent); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setConsent( |
| ConsentConstants.FLEDGE_DEFAULT_CONSENT, defaultConsent); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException("Record FLEDGE default consent failed", e); |
| } |
| } |
| } |
| |
| /** |
| * Saves the measurement default consent bit to storage. |
| * |
| * <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 void recordMeasurementDefaultConsent(boolean defaultConsent) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put( |
| ConsentConstants.MEASUREMENT_DEFAULT_CONSENT, defaultConsent); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.recordMeasurementDefaultConsent(defaultConsent); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put( |
| ConsentConstants.MEASUREMENT_DEFAULT_CONSENT, defaultConsent); |
| mAdServicesManager.recordMeasurementDefaultConsent(defaultConsent); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setConsent( |
| ConsentConstants.MEASUREMENT_DEFAULT_CONSENT, defaultConsent); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException("Record measurement default consent failed", e); |
| } |
| } |
| } |
| |
| /** |
| * Saves the default AdId state. |
| * |
| * <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 void recordDefaultAdIdState(boolean defaultAdIdState) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put(ConsentConstants.DEFAULT_AD_ID_STATE, defaultAdIdState); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.recordDefaultAdIdState(defaultAdIdState); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put(ConsentConstants.DEFAULT_AD_ID_STATE, defaultAdIdState); |
| mAdServicesManager.recordDefaultAdIdState(defaultAdIdState); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setConsent( |
| ConsentConstants.DEFAULT_AD_ID_STATE, defaultAdIdState); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException("Record default AdId state failed", e); |
| } |
| } |
| } |
| |
| /** |
| * Set 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 void setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType currentFeatureType) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| for (PrivacySandboxFeatureType featureType : |
| PrivacySandboxFeatureType.values()) { |
| if (featureType.name().equals(currentFeatureType.name())) { |
| mDatastore.put(featureType.name(), true); |
| } else { |
| mDatastore.put(featureType.name(), false); |
| } |
| } |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.setCurrentPrivacySandboxFeature( |
| currentFeatureType.name()); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| for (PrivacySandboxFeatureType featureType : |
| PrivacySandboxFeatureType.values()) { |
| if (featureType.name().equals(currentFeatureType.name())) { |
| mDatastore.put(featureType.name(), true); |
| } else { |
| mDatastore.put(featureType.name(), false); |
| } |
| } |
| mAdServicesManager.setCurrentPrivacySandboxFeature( |
| currentFeatureType.name()); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setCurrentPrivacySandboxFeature( |
| currentFeatureType); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| ErrorLogUtil.e( |
| e, |
| AD_SERVICES_ERROR_REPORTED__ERROR_CODE__PRIVACY_SANDBOX_SAVE_FAILURE, |
| AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX); |
| throw new RuntimeException("Set current privacy sandbox feature failed.", e); |
| } |
| } |
| } |
| |
| /** Saves information to the storage that user interacted with consent manually. */ |
| public void recordUserManualInteractionWithConsent(@UserManualInteraction int interaction) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| storeUserManualInteractionToPpApi(interaction, mDatastore); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.recordUserManualInteractionWithConsent(interaction); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| storeUserManualInteractionToPpApi(interaction, mDatastore); |
| mAdServicesManager.recordUserManualInteractionWithConsent(interaction); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.recordUserManualInteractionWithConsent( |
| interaction); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED); |
| } |
| } catch (IOException | RuntimeException e) { |
| ErrorLogUtil.e( |
| e, |
| AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATASTORE_EXCEPTION_WHILE_RECORDING_MANUAL_CONSENT_INTERACTION, |
| AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX); |
| throw new RuntimeException("Record manual interaction with consent failed", e); |
| } |
| } |
| } |
| |
| /** |
| * 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() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| for (PrivacySandboxFeatureType featureType : |
| PrivacySandboxFeatureType.values()) { |
| if (Boolean.TRUE.equals(mDatastore.get(featureType.name()))) { |
| return featureType; |
| } |
| } |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| for (PrivacySandboxFeatureType featureType : |
| PrivacySandboxFeatureType.values()) { |
| if (mAdServicesManager |
| .getCurrentPrivacySandboxFeature() |
| .equals(featureType.name())) { |
| return featureType; |
| } |
| } |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.getCurrentPrivacySandboxFeature(); |
| } |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get privacy sandbox feature failed."); |
| 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; |
| } |
| } |
| |
| private static void storeUserManualInteractionToPpApi( |
| @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)); |
| } |
| } |
| |
| /** |
| * Returns information whether user interacted with consent manually. |
| * |
| * @return true if the user interacted with the consent manually, otherwise false. |
| */ |
| public @UserManualInteraction int getUserManualInteractionWithConsent() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| Boolean manualInteractionWithConsent = |
| mDatastore.get( |
| ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED); |
| if (manualInteractionWithConsent == null) { |
| return UNKNOWN; |
| } else if (Boolean.TRUE.equals(manualInteractionWithConsent)) { |
| return MANUAL_INTERACTIONS_RECORDED; |
| } else { |
| return NO_MANUAL_INTERACTIONS_RECORDED; |
| } |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.getUserManualInteractionWithConsent(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.getUserManualInteractionWithConsent(); |
| } |
| default: |
| LogUtil.e(ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED); |
| return UNKNOWN; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Record manual interaction with consent failed."); |
| 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; |
| } |
| } |
| |
| @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, |
| AdServicesManager adServicesManager, |
| @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.i("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); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| migratePpApiConsentToSystemService(context, datastore, adServicesManager); |
| 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; |
| } |
| } |
| |
| @VisibleForTesting |
| void setConsentToPpApi(boolean isGiven) throws IOException { |
| mDatastore.put(ConsentConstants.CONSENT_KEY, isGiven); |
| } |
| |
| @VisibleForTesting |
| void setConsentPerApiToPpApi(AdServicesApiType apiType, boolean isGiven) throws IOException { |
| mDatastore.put(apiType.toPpApiDatastoreKey(), 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 setAggregatedConsentToPpApi() throws IOException { |
| if (getConsent(AdServicesApiType.TOPICS).isGiven() |
| && getConsent(AdServicesApiType.MEASUREMENTS).isGiven() |
| && getConsent(AdServicesApiType.FLEDGE).isGiven()) { |
| setConsentToPpApi(true); |
| } else { |
| setConsentToPpApi(false); |
| } |
| } |
| |
| // Reset data for the specific AdServicesApiType |
| @VisibleForTesting |
| void resetByApi(AdServicesApiType apiType) throws IOException { |
| switch (apiType) { |
| case TOPICS: |
| resetTopicsAndBlockedTopics(); |
| break; |
| case FLEDGE: |
| resetAppsAndBlockedApps(); |
| break; |
| case MEASUREMENTS: |
| resetMeasurement(); |
| break; |
| } |
| } |
| |
| @VisibleForTesting |
| static void setConsentToSystemServer( |
| @NonNull AdServicesManager adServicesManager, boolean isGiven) { |
| Objects.requireNonNull(adServicesManager); |
| |
| ConsentParcel consentParcel = |
| new ConsentParcel.Builder() |
| .setConsentApiType(ConsentParcel.ALL_API) |
| .setIsGiven(isGiven) |
| .build(); |
| adServicesManager.setConsent(consentParcel); |
| } |
| |
| @VisibleForTesting |
| static void setPerApiConsentToSystemServer( |
| @NonNull AdServicesManager adServicesManager, |
| @ConsentParcel.ConsentApiType int consentApiType, |
| boolean isGiven) { |
| Objects.requireNonNull(adServicesManager); |
| |
| if (isGiven) { |
| adServicesManager.setConsent(ConsentParcel.createGivenConsent(consentApiType)); |
| } else { |
| adServicesManager.setConsent(ConsentParcel.createRevokedConsent(consentApiType)); |
| } |
| } |
| |
| // Perform a one-time migration to migrate existing PPAPI Consent |
| @VisibleForTesting |
| static void migratePpApiConsentToSystemService( |
| @NonNull Context context, |
| @NonNull BooleanFileDatastore datastore, |
| @NonNull AdServicesManager adServicesManager) { |
| Objects.requireNonNull(context); |
| Objects.requireNonNull(datastore); |
| Objects.requireNonNull(adServicesManager); |
| |
| // Exit if migration has happened. |
| SharedPreferences sharedPreferences = |
| context.getSharedPreferences( |
| 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"); |
| |
| // Migrate Consent and Notification Displayed to System Service. |
| // Set consent enabled only when value is TRUE. FALSE and null are regarded as disabled. |
| setConsentToSystemServer( |
| adServicesManager, |
| Boolean.TRUE.equals(datastore.get(ConsentConstants.CONSENT_KEY))); |
| |
| // 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(); |
| } |
| |
| 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); |
| |
| if (editor.commit()) { |
| LogUtil.d("Finished migrating Consent from PPAPI to System Service"); |
| } 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, |
| ConsentManager.class.getSimpleName(), |
| new Object() { |
| }.getClass().getEnclosingMethod().getName()); |
| } |
| } |
| |
| // 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 = |
| context.getSharedPreferences( |
| 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, |
| ConsentManager.class.getSimpleName(), |
| ConsentManager.class.getEnclosingMethod().getName()); |
| } |
| } |
| |
| // 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 = |
| context.getSharedPreferences( |
| 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, |
| ConsentManager.class.getSimpleName(), |
| ConsentManager.class.getEnclosingMethod().getName()); |
| } |
| } |
| |
| // 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) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| setConsentToPpApi(isGiven); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| setConsentToSystemServer(mAdServicesManager, isGiven); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| // Ensure data is consistent in PPAPI and system server. |
| setConsentToPpApi(isGiven); |
| setConsentToSystemServer(mAdServicesManager, isGiven); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setConsent( |
| ConsentConstants.CONSENT_KEY_FOR_ALL, isGiven); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| void setPerApiConsentToSourceOfTruth(boolean isGiven, AdServicesApiType apiType) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| setConsentPerApiToPpApi(apiType, isGiven); |
| setAggregatedConsentToPpApi(); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| setPerApiConsentToSystemServer( |
| mAdServicesManager, apiType.toConsentApiType(), isGiven); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| // Ensure data is consistent in PPAPI and system server. |
| setConsentPerApiToPpApi(apiType, isGiven); |
| setPerApiConsentToSystemServer( |
| mAdServicesManager, apiType.toConsentApiType(), isGiven); |
| setAggregatedConsentToPpApi(); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setConsent( |
| apiType.toPpApiDatastoreKey(), isGiven); |
| break; |
| } |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e); |
| } |
| } |
| } |
| |
| /** |
| * 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 AppSearchConsentManager appSearchConsentManager, |
| @NonNull AdServicesManager adServicesManager) { |
| Objects.requireNonNull(context); |
| Objects.requireNonNull(appSearchConsentManager); |
| 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.i( |
| "Aborting attempt to migrate AppSearch to PPAPI and System Service in" |
| + " ExtServices"); |
| return; |
| } |
| |
| 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 = |
| context.getSharedPreferences( |
| ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE); |
| // If we did not migrate notification data, we should not attempt to migrate anything. |
| if (!appSearchConsentManager.migrateConsentDataIfNeeded( |
| context, sharedPreferences, datastore, adServicesManager, appConsentDao)) { |
| LogUtil.d("Skipping consent migration from AppSearch"); |
| return; |
| } |
| |
| // Migrate Consent for all APIs and per API to PP API and System Service. |
| migrateAppSearchConsents(appSearchConsentManager, adServicesManager, datastore); |
| |
| // Record interactions data only if we recorded an interaction in AppSearch. |
| int manualInteractionRecorded = |
| appSearchConsentManager.getUserManualInteractionWithConsent(); |
| if (manualInteractionRecorded == MANUAL_INTERACTIONS_RECORDED) { |
| // Initialize PP API datastore. |
| storeUserManualInteractionToPpApi(manualInteractionRecorded, datastore); |
| // Initialize system service. |
| adServicesManager.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"); |
| } else { |
| LogUtil.e( |
| "Finished migrating Consent from AppSearch to PPAPI + System Service " |
| + "but shared preference is not updated."); |
| } |
| } 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); |
| } |
| } |
| |
| /** |
| * This method migrates the consent states (opt in/out) for all PPAPIs, each API and their |
| * default consent values. |
| */ |
| @VisibleForTesting |
| static void migrateAppSearchConsents( |
| AppSearchConsentManager appSearchConsentManager, |
| AdServicesManager adServicesManager, |
| BooleanFileDatastore datastore) |
| throws IOException { |
| boolean consented = appSearchConsentManager.getConsent(ConsentConstants.CONSENT_KEY); |
| datastore.put(ConsentConstants.CONSENT_KEY, consented); |
| adServicesManager.setConsent(getConsentParcel(ConsentParcel.ALL_API, consented)); |
| |
| // Record default consents. |
| boolean defaultConsent = |
| appSearchConsentManager.getConsent(ConsentConstants.DEFAULT_CONSENT); |
| datastore.put(ConsentConstants.DEFAULT_CONSENT, defaultConsent); |
| adServicesManager.recordDefaultConsent(defaultConsent); |
| boolean topicsDefaultConsented = |
| appSearchConsentManager.getConsent(ConsentConstants.TOPICS_DEFAULT_CONSENT); |
| datastore.put(ConsentConstants.TOPICS_DEFAULT_CONSENT, topicsDefaultConsented); |
| adServicesManager.recordTopicsDefaultConsent(topicsDefaultConsented); |
| boolean fledgeDefaultConsented = |
| appSearchConsentManager.getConsent(ConsentConstants.FLEDGE_DEFAULT_CONSENT); |
| datastore.put(ConsentConstants.FLEDGE_DEFAULT_CONSENT, fledgeDefaultConsented); |
| adServicesManager.recordFledgeDefaultConsent(fledgeDefaultConsented); |
| boolean measurementDefaultConsented = |
| appSearchConsentManager.getConsent(ConsentConstants.MEASUREMENT_DEFAULT_CONSENT); |
| datastore.put(ConsentConstants.MEASUREMENT_DEFAULT_CONSENT, measurementDefaultConsented); |
| adServicesManager.recordMeasurementDefaultConsent(measurementDefaultConsented); |
| |
| // Record per API consents. |
| boolean topicsConsented = |
| appSearchConsentManager.getConsent(AdServicesApiType.TOPICS.toPpApiDatastoreKey()); |
| datastore.put(AdServicesApiType.TOPICS.toPpApiDatastoreKey(), topicsConsented); |
| setPerApiConsentToSystemServer( |
| adServicesManager, AdServicesApiType.TOPICS.toConsentApiType(), topicsConsented); |
| boolean fledgeConsented = |
| appSearchConsentManager.getConsent(AdServicesApiType.FLEDGE.toPpApiDatastoreKey()); |
| datastore.put(AdServicesApiType.FLEDGE.toPpApiDatastoreKey(), fledgeConsented); |
| setPerApiConsentToSystemServer( |
| adServicesManager, AdServicesApiType.FLEDGE.toConsentApiType(), fledgeConsented); |
| boolean measurementConsented = |
| appSearchConsentManager.getConsent( |
| AdServicesApiType.MEASUREMENTS.toPpApiDatastoreKey()); |
| datastore.put(AdServicesApiType.MEASUREMENTS.toPpApiDatastoreKey(), measurementConsented); |
| setPerApiConsentToSystemServer( |
| adServicesManager, |
| AdServicesApiType.MEASUREMENTS.toConsentApiType(), |
| measurementConsented); |
| } |
| |
| @NonNull |
| private static ConsentParcel getConsentParcel( |
| @NonNull Integer apiType, @NonNull Boolean consented) { |
| return new ConsentParcel.Builder().setConsentApiType(apiType).setIsGiven(consented).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().ordinal()) |
| .build()); |
| } |
| |
| /** Returns whether the isU18Account bit is true based on consent_source_of_truth. */ |
| public Boolean isU18Account() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.IS_U18_ACCOUNT); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.isU18Account(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.isU18Account(); |
| } |
| break; |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get isU18Account bit failed. " + e.getMessage()); |
| } |
| return false; |
| } |
| } |
| |
| /** Set the U18Account bit to storage based on consent_source_of_truth. */ |
| public void setU18Account(boolean isU18Account) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put(ConsentConstants.IS_U18_ACCOUNT, isU18Account); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.setU18Account(isU18Account); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put(ConsentConstants.IS_U18_ACCOUNT, isU18Account); |
| mAdServicesManager.setU18Account(isU18Account); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setU18Account(isU18Account); |
| } |
| break; |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException("setisU18Account operation failed. " + e.getMessage()); |
| } |
| } |
| } |
| |
| /** Returns whether the isEntryPointEnabled bit is true based on consent_source_of_truth. */ |
| public Boolean isEntryPointEnabled() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.IS_ENTRY_POINT_ENABLED); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.isEntryPointEnabled(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.isEntryPointEnabled(); |
| } |
| break; |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get isEntryPointEnabled bit failed. " + e.getMessage()); |
| } |
| return false; |
| } |
| } |
| |
| /** Set the EntryPointEnabled bit to storage based on consent_source_of_truth. */ |
| public void setEntryPointEnabled(boolean isEntryPointEnabled) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put( |
| ConsentConstants.IS_ENTRY_POINT_ENABLED, isEntryPointEnabled); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.setEntryPointEnabled(isEntryPointEnabled); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put( |
| ConsentConstants.IS_ENTRY_POINT_ENABLED, isEntryPointEnabled); |
| mAdServicesManager.setEntryPointEnabled(isEntryPointEnabled); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setEntryPointEnabled(isEntryPointEnabled); |
| } |
| break; |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException( |
| "setisEntryPointEnabled operation failed. " + e.getMessage()); |
| } |
| } |
| } |
| |
| /** Returns whether the isAdultAccount bit is true based on consent_source_of_truth. */ |
| public Boolean isAdultAccount() { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| return mDatastore.get(ConsentConstants.IS_ADULT_ACCOUNT); |
| case Flags.SYSTEM_SERVER_ONLY: |
| // Intentional fallthrough |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| return mAdServicesManager.isAdultAccount(); |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| return mAppSearchConsentManager.isAdultAccount(); |
| } |
| break; |
| default: |
| LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| return false; |
| } |
| } catch (RuntimeException e) { |
| LogUtil.e(e, "Get isAdultAccount bit failed. " + e.getMessage()); |
| } |
| return false; |
| } |
| } |
| |
| /** Set the AdultAccount bit to storage based on consent_source_of_truth. */ |
| public void setAdultAccount(boolean isAdultAccount) { |
| synchronized (LOCK) { |
| try { |
| switch (mConsentSourceOfTruth) { |
| case Flags.PPAPI_ONLY: |
| mDatastore.put(ConsentConstants.IS_ADULT_ACCOUNT, isAdultAccount); |
| break; |
| case Flags.SYSTEM_SERVER_ONLY: |
| mAdServicesManager.setAdultAccount(isAdultAccount); |
| break; |
| case Flags.PPAPI_AND_SYSTEM_SERVER: |
| mDatastore.put(ConsentConstants.IS_ADULT_ACCOUNT, isAdultAccount); |
| mAdServicesManager.setAdultAccount(isAdultAccount); |
| break; |
| case Flags.APPSEARCH_ONLY: |
| if (mFlags.getEnableAppsearchConsentData()) { |
| mAppSearchConsentManager.setAdultAccount(isAdultAccount); |
| } |
| break; |
| default: |
| throw new RuntimeException( |
| ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH); |
| } |
| } catch (IOException | RuntimeException e) { |
| throw new RuntimeException("setisAdultAccount operation failed. " + e.getMessage()); |
| } |
| } |
| } |
| } |