• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.adservices.service.consent;
18 
19 import static com.android.adservices.AdServicesCommon.ADEXTSERVICES_PACKAGE_NAME_SUFFIX;
20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_SEARCH_DATA_MIGRATION_FAILURE;
21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_RESET_FAILURE;
22 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE;
23 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX;
24 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_WIPEOUT;
25 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU;
26 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
27 
28 import android.annotation.IntDef;
29 import android.annotation.Nullable;
30 import android.app.adservices.AdServicesManager;
31 import android.app.job.JobScheduler;
32 import android.content.Context;
33 import android.content.SharedPreferences;
34 import android.os.Build;
35 import android.os.SystemClock;
36 
37 import androidx.annotation.RequiresApi;
38 
39 import com.android.adservices.LogUtil;
40 import com.android.adservices.concurrency.AdServicesExecutors;
41 import com.android.adservices.data.adselection.AppInstallDao;
42 import com.android.adservices.data.adselection.FrequencyCapDao;
43 import com.android.adservices.data.adselection.SharedStorageDatabase;
44 import com.android.adservices.data.common.LegacyAtomicFileDatastoreFactory;
45 import com.android.adservices.data.consent.AppConsentDao;
46 import com.android.adservices.data.customaudience.CustomAudienceDao;
47 import com.android.adservices.data.customaudience.CustomAudienceDatabase;
48 import com.android.adservices.data.enrollment.EnrollmentDao;
49 import com.android.adservices.data.signals.ProtectedSignalsDao;
50 import com.android.adservices.data.signals.ProtectedSignalsDatabase;
51 import com.android.adservices.data.topics.Topic;
52 import com.android.adservices.data.topics.TopicsTables;
53 import com.android.adservices.errorlogging.AdServicesErrorLoggerImpl;
54 import com.android.adservices.errorlogging.ErrorLogUtil;
55 import com.android.adservices.service.DebugFlags;
56 import com.android.adservices.service.Flags;
57 import com.android.adservices.service.FlagsFactory;
58 import com.android.adservices.service.appsearch.AppSearchConsentStorageManager;
59 import com.android.adservices.service.common.BackgroundJobsManager;
60 import com.android.adservices.service.common.UserProfileIdManager;
61 import com.android.adservices.service.common.compat.FileCompatUtils;
62 import com.android.adservices.service.common.feature.PrivacySandboxFeatureType;
63 import com.android.adservices.service.measurement.MeasurementImpl;
64 import com.android.adservices.service.measurement.WipeoutStatus;
65 import com.android.adservices.service.stats.AdServicesLoggerImpl;
66 import com.android.adservices.service.stats.ConsentMigrationStats;
67 import com.android.adservices.service.stats.MeasurementWipeoutStats;
68 import com.android.adservices.service.stats.StatsdAdServicesLogger;
69 import com.android.adservices.service.stats.UiStatsLogger;
70 import com.android.adservices.service.topics.TopicsWorker;
71 import com.android.adservices.service.ui.data.UxStatesDao;
72 import com.android.adservices.service.ui.enrollment.collection.PrivacySandboxEnrollmentChannelCollection;
73 import com.android.adservices.service.ui.ux.collection.PrivacySandboxUxCollection;
74 import com.android.adservices.shared.common.ApplicationContextSingleton;
75 import com.android.adservices.shared.errorlogging.AdServicesErrorLogger;
76 import com.android.adservices.shared.storage.AtomicFileDatastore;
77 import com.android.internal.annotations.VisibleForTesting;
78 import com.android.modules.utils.build.SdkLevel;
79 
80 import com.google.common.collect.ImmutableList;
81 
82 import java.io.IOException;
83 import java.io.PrintWriter;
84 import java.lang.annotation.Retention;
85 import java.lang.annotation.RetentionPolicy;
86 import java.util.ArrayList;
87 import java.util.HashMap;
88 import java.util.List;
89 import java.util.Map;
90 import java.util.Objects;
91 import java.util.stream.Collectors;
92 
93 /**
94  * Manager all critical user data such as per API consent.
95  *
96  * <p>For Beta the consent is given for all {@link AdServicesApiType} or for none.
97  *
98  * <p>Currently there are three types of source of truth to store consent data,
99  *
100  * <ul>
101  *   <li>SYSTEM_SERVER_ONLY: Write and read consent from system server only.
102  *   <li>PPAPI_ONLY: Write and read consent from PPAPI only.
103  *   <li>PPAPI_AND_SYSTEM_SERVER: Write consent to both PPAPI and system server. Read consent from
104  *       system server only.
105  *   <li>APPSEARCH_ONLY: Write and read consent from appSearch only for back compat.
106  * </ul>
107  *
108  * IMPORTANT: Until ConsentManagerV2 is launched, keep in sync with ConsentManager.
109  */
110 // TODO(b/279042385): move UI logs to UI.
111 @RequiresApi(Build.VERSION_CODES.S)
112 public final class ConsentManagerV2 {
113 
114     // Used on dump() / log only
115     private static int sDataMigrationDurationMs;
116     private static int sInstantiationDurationMs;
117 
118     private static volatile ConsentManagerV2 sConsentManager;
119 
120     @IntDef(value = {NO_MANUAL_INTERACTIONS_RECORDED, UNKNOWN, MANUAL_INTERACTIONS_RECORDED})
121     @Retention(RetentionPolicy.SOURCE)
122     public @interface UserManualInteraction {}
123 
124     public static final int NO_MANUAL_INTERACTIONS_RECORDED = -1;
125     public static final int UNKNOWN = 0;
126     public static final int MANUAL_INTERACTIONS_RECORDED = 1;
127 
128     private final Flags mFlags;
129     private final DebugFlags mDebugFlags;
130     private final TopicsWorker mTopicsWorker;
131     private final AtomicFileDatastore mDatastore;
132     private final EnrollmentDao mEnrollmentDao;
133     private final MeasurementImpl mMeasurementImpl;
134     private final CustomAudienceDao mCustomAudienceDao;
135     private final AppInstallDao mAppInstallDao;
136     private final ProtectedSignalsDao mProtectedSignalsDao;
137     private final FrequencyCapDao mFrequencyCapDao;
138     private final AdServicesStorageManager mAdServicesStorageManager;
139     private final AppSearchConsentStorageManager mAppSearchConsentStorageManager;
140     private final UserProfileIdManager mUserProfileIdManager;
141 
142     private static final Object LOCK = new Object();
143 
144     private ConsentCompositeStorage mConsentCompositeStorage;
145 
146     private AppConsentStorageManager mAppConsentStorageManager;
147 
ConsentManagerV2( TopicsWorker topicsWorker, AppConsentDao appConsentDao, EnrollmentDao enrollmentDao, MeasurementImpl measurementImpl, CustomAudienceDao customAudienceDao, AppConsentStorageManager appConsentStorageManager, AppInstallDao appInstallDao, ProtectedSignalsDao protectedSignalsDao, FrequencyCapDao frequencyCapDao, AdServicesStorageManager adServicesStorageManager, AtomicFileDatastore atomicFileDatastore, AppSearchConsentStorageManager appSearchConsentStorageManager, UserProfileIdManager userProfileIdManager, Flags flags, DebugFlags debugFlags, @Flags.ConsentSourceOfTruth int consentSourceOfTruth, boolean enableAppsearchConsentData)148     ConsentManagerV2(
149             TopicsWorker topicsWorker,
150             AppConsentDao appConsentDao,
151             EnrollmentDao enrollmentDao,
152             MeasurementImpl measurementImpl,
153             CustomAudienceDao customAudienceDao,
154             AppConsentStorageManager appConsentStorageManager,
155             AppInstallDao appInstallDao,
156             ProtectedSignalsDao protectedSignalsDao,
157             FrequencyCapDao frequencyCapDao,
158             AdServicesStorageManager adServicesStorageManager,
159             AtomicFileDatastore atomicFileDatastore,
160             AppSearchConsentStorageManager appSearchConsentStorageManager,
161             UserProfileIdManager userProfileIdManager,
162             Flags flags,
163             DebugFlags debugFlags,
164             @Flags.ConsentSourceOfTruth int consentSourceOfTruth,
165             boolean enableAppsearchConsentData) {
166         Objects.requireNonNull(topicsWorker);
167         Objects.requireNonNull(appConsentDao);
168         Objects.requireNonNull(measurementImpl);
169         Objects.requireNonNull(customAudienceDao);
170         Objects.requireNonNull(appInstallDao);
171         Objects.requireNonNull(protectedSignalsDao);
172         Objects.requireNonNull(frequencyCapDao);
173         Objects.requireNonNull(atomicFileDatastore);
174         Objects.requireNonNull(userProfileIdManager);
175 
176         if (consentSourceOfTruth != Flags.PPAPI_ONLY
177                 && consentSourceOfTruth != Flags.APPSEARCH_ONLY) {
178             Objects.requireNonNull(adServicesStorageManager);
179         }
180 
181         if (enableAppsearchConsentData) {
182             Objects.requireNonNull(appSearchConsentStorageManager);
183         }
184 
185         mAdServicesStorageManager = adServicesStorageManager;
186         mTopicsWorker = topicsWorker;
187         mDatastore = atomicFileDatastore;
188         mEnrollmentDao = enrollmentDao;
189         mMeasurementImpl = measurementImpl;
190         mCustomAudienceDao = customAudienceDao;
191         mAppInstallDao = appInstallDao;
192         mProtectedSignalsDao = protectedSignalsDao;
193         mFrequencyCapDao = frequencyCapDao;
194 
195         mAppSearchConsentStorageManager = appSearchConsentStorageManager;
196         mUserProfileIdManager = userProfileIdManager;
197 
198         mFlags = flags;
199         mDebugFlags = debugFlags;
200         mAppConsentStorageManager = appConsentStorageManager;
201 
202         mConsentCompositeStorage =
203                 new ConsentCompositeStorage(getStorageListBySourceOfTruth(consentSourceOfTruth));
204     }
205 
getStorageListBySourceOfTruth( @lags.ConsentSourceOfTruth int consentSourceOfTruth)206     private ImmutableList<IConsentStorage> getStorageListBySourceOfTruth(
207             @Flags.ConsentSourceOfTruth int consentSourceOfTruth) {
208         switch (consentSourceOfTruth) {
209             case Flags.PPAPI_ONLY:
210                 return ImmutableList.of(mAppConsentStorageManager);
211             case Flags.SYSTEM_SERVER_ONLY:
212                 return ImmutableList.of(mAdServicesStorageManager);
213             case Flags.PPAPI_AND_SYSTEM_SERVER:
214                 // System storage has higher priority
215                 return ImmutableList.of(mAdServicesStorageManager, mAppConsentStorageManager);
216             case Flags.APPSEARCH_ONLY:
217                 return ImmutableList.of(mAppSearchConsentStorageManager);
218             default:
219                 LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
220                 return ImmutableList.of();
221         }
222     }
223 
224     /**
225      * Gets an instance of {@link ConsentManagerV2} to be used.
226      *
227      * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
228      * existing instance will be returned.
229      */
230     // TODO: apply the lazy initialization to this class b/366283605
getInstance()231     public static ConsentManagerV2 getInstance() {
232         Context context = ApplicationContextSingleton.get();
233 
234         if (sConsentManager == null) {
235             synchronized (LOCK) {
236                 if (sConsentManager == null) {
237                     long startedTime = SystemClock.uptimeMillis();
238                     // Execute one-time consent migration if needed.
239                     int consentSourceOfTruth = FlagsFactory.getFlags().getConsentSourceOfTruth();
240                     AtomicFileDatastore datastore =
241                             createAndInitializeDataStore(
242                                     context, AdServicesErrorLoggerImpl.getInstance());
243                     AdServicesStorageManager adServicesManager =
244                             AdServicesStorageManager.getInstance(
245                                     AdServicesManager.getInstance(context));
246                     AppConsentDao appConsentDao = AppConsentDao.getInstance();
247 
248                     // It is possible that the old value of the flag lingers after OTA until the
249                     // first PH sync. In that case, we should not use the stale value, but use the
250                     // default instead. The next PH sync will restore the T+ value.
251                     if (SdkLevel.isAtLeastT() && consentSourceOfTruth == Flags.APPSEARCH_ONLY) {
252                         consentSourceOfTruth = Flags.DEFAULT_CONSENT_SOURCE_OF_TRUTH;
253                     }
254                     AppSearchConsentStorageManager appSearchConsentStorageManager = null;
255                     StatsdAdServicesLogger statsdAdServicesLogger =
256                             StatsdAdServicesLogger.getInstance();
257                     // Flag enable_appsearch_consent_data is true on S- and T+ only when we want to
258                     // use AppSearch to write to or read from.
259                     boolean enableAppsearchConsentData =
260                             FlagsFactory.getFlags().getEnableAppsearchConsentData();
261                     if (enableAppsearchConsentData) {
262                         appSearchConsentStorageManager =
263                                 AppSearchConsentStorageManager.getInstance();
264                         handleConsentMigrationFromAppSearchIfNeeded(
265                                 context,
266                                 datastore,
267                                 appConsentDao,
268                                 appSearchConsentStorageManager,
269                                 adServicesManager,
270                                 statsdAdServicesLogger);
271                     }
272                     UxStatesDao uxStatesDao = UxStatesDao.getInstance();
273 
274                     // Attempt to migrate consent data from PPAPI to System server if needed.
275                     handleConsentMigrationIfNeeded(
276                             context,
277                             datastore,
278                             adServicesManager,
279                             statsdAdServicesLogger,
280                             consentSourceOfTruth);
281 
282                     AppConsentStorageManager appConsentStorageManager =
283                             new AppConsentStorageManager(datastore, appConsentDao, uxStatesDao);
284                     long postDataMigrationTime = SystemClock.uptimeMillis();
285                     sDataMigrationDurationMs = (int) (postDataMigrationTime - startedTime);
286                     sConsentManager =
287                             new ConsentManagerV2(
288                                     TopicsWorker.getInstance(),
289                                     appConsentDao,
290                                     EnrollmentDao.getInstance(),
291                                     MeasurementImpl.getInstance(),
292                                     CustomAudienceDatabase.getInstance().customAudienceDao(),
293                                     appConsentStorageManager,
294                                     SharedStorageDatabase.getInstance().appInstallDao(),
295                                     ProtectedSignalsDatabase.getInstance().protectedSignalsDao(),
296                                     SharedStorageDatabase.getInstance().frequencyCapDao(),
297                                     adServicesManager,
298                                     datastore,
299                                     appSearchConsentStorageManager,
300                                     UserProfileIdManager.getInstance(),
301                                     // TODO(b/260601944): Remove Flag Instance.
302                                     FlagsFactory.getFlags(),
303                                     DebugFlags.getInstance(),
304                                     consentSourceOfTruth,
305                                     enableAppsearchConsentData);
306                     sInstantiationDurationMs =
307                             (int) (postDataMigrationTime - sDataMigrationDurationMs);
308                     LogUtil.d(
309                             "finished consent manager initialization: data migration in %dms,"
310                                     + " instantiation in %dms",
311                             sDataMigrationDurationMs, sInstantiationDurationMs);
312                 }
313             }
314         }
315         return sConsentManager;
316     }
317 
318     /**
319      * Enables all PP API services. It gives consent to Topics, Fledge and Measurements services.
320      *
321      * <p>To write consent to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To
322      * write to system server consent if source of truth is system server or dual sources.
323      */
enable(Context context)324     public void enable(Context context) {
325         Objects.requireNonNull(context);
326 
327         // Check current value, if it is already enabled, skip this enable process. so that the Api
328         // won't be reset. Only add this logic to "enable" not "disable", since if it already
329         // disabled, there is no harm to reset the api again.
330         if (mFlags.getConsentManagerLazyEnableMode() && getConsentFromSourceOfTruth()) {
331             LogUtil.d("CONSENT_KEY already enable. Skipping enable process.");
332             return;
333         }
334         UiStatsLogger.logOptInSelected();
335 
336         BackgroundJobsManager.scheduleAllBackgroundJobs(context);
337         try {
338             // reset all state data which should be removed
339             resetTopicsAndBlockedTopics();
340             resetAppsAndBlockedApps();
341             resetMeasurement();
342             resetUserProfileId();
343             mUserProfileIdManager.getOrCreateId();
344         } catch (IOException e) {
345             throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e);
346         }
347         setConsentToSourceOfTruth(/* isGiven */ true);
348     }
349 
350     /**
351      * Disables all PP API services. It revokes consent to Topics, Fledge and Measurements services.
352      *
353      * <p>To write consent to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To
354      * write to system server consent if source of truth is system server or dual sources.
355      */
disable(Context context)356     public void disable(Context context) {
357         Objects.requireNonNull(context);
358         UiStatsLogger.logOptOutSelected();
359         // Disable all the APIs
360         try {
361             // reset all data
362             resetTopicsAndBlockedTopics();
363             resetAppsAndBlockedApps();
364             resetMeasurement();
365             resetEnrollment();
366             resetUserProfileId();
367 
368             BackgroundJobsManager.unscheduleAllBackgroundJobs(
369                     context.getSystemService(JobScheduler.class));
370         } catch (IOException e) {
371             throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e);
372         }
373         setConsentToSourceOfTruth(/* isGiven */ false);
374     }
375 
376     /**
377      * Enables the {@code apiType} PP API service. It gives consent to an API which is provided in
378      * the parameter.
379      *
380      * <p>To write consent to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To
381      * write to system server consent if source of truth is system server or dual sources.
382      *
383      * @param context Context of the application.
384      * @param apiType Type of the API (Topics, Fledge, Measurement) which should be enabled.
385      */
enable(Context context, AdServicesApiType apiType)386     public void enable(Context context, AdServicesApiType apiType) {
387         Objects.requireNonNull(context);
388         // Check current value, if it is already enabled, skip this enable process. so that the Api
389         // won't be reset.
390         if (mFlags.getConsentManagerLazyEnableMode()
391                 && getPerApiConsentFromSourceOfTruth(apiType)) {
392             LogUtil.d(
393                     "ApiType: is %s already enable. Skipping enable process.",
394                     apiType.toPpApiDatastoreKey());
395             return;
396         }
397 
398         UiStatsLogger.logOptInSelected(apiType);
399 
400         BackgroundJobsManager.scheduleJobsPerApi(context, apiType);
401 
402         try {
403             // reset all state data which should be removed
404             resetByApi(apiType);
405 
406             if (AdServicesApiType.FLEDGE == apiType) {
407                 mUserProfileIdManager.getOrCreateId();
408             }
409         } catch (IOException e) {
410             throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e);
411         }
412 
413         setPerApiConsentToSourceOfTruth(/* isGiven */ true, apiType);
414     }
415 
416     /**
417      * Disables {@code apiType} PP API service. It revokes consent to an API which is provided in
418      * the parameter.
419      *
420      * <p>To write consent to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To
421      * write to system server consent if source of truth is system server or dual sources.
422      */
disable(Context context, AdServicesApiType apiType)423     public void disable(Context context, AdServicesApiType apiType) {
424         Objects.requireNonNull(context);
425 
426         UiStatsLogger.logOptOutSelected(apiType);
427 
428         try {
429             resetByApi(apiType);
430             BackgroundJobsManager.unscheduleJobsPerApi(
431                     context.getSystemService(JobScheduler.class), apiType);
432         } catch (IOException e) {
433             throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_WHILE_SET_CONTENT, e);
434         }
435 
436         setPerApiConsentToSourceOfTruth(/* isGiven */ false, apiType);
437 
438         if (areAllApisDisabled()) {
439             BackgroundJobsManager.unscheduleAllBackgroundJobs(
440                     context.getSystemService(JobScheduler.class));
441         }
442     }
443 
areAllApisDisabled()444     private boolean areAllApisDisabled() {
445         if (getConsent(AdServicesApiType.TOPICS).isGiven()
446                 || getConsent(AdServicesApiType.MEASUREMENTS).isGiven()
447                 || getConsent(AdServicesApiType.FLEDGE).isGiven()) {
448             return false;
449         }
450         return true;
451     }
452 
453     /**
454      * Retrieves the consent for all PP API services.
455      *
456      * <p>To read from PPAPI consent if source of truth is PPAPI. To read from system server consent
457      * if source of truth is system server or dual sources.
458      *
459      * @return AdServicesApiConsent the consent
460      */
getConsent()461     public AdServicesApiConsent getConsent() {
462         if (mDebugFlags.getConsentManagerDebugMode()) {
463             return AdServicesApiConsent.GIVEN;
464         }
465         return mConsentCompositeStorage.getConsent(AdServicesApiType.ALL_API);
466     }
467 
468     /**
469      * Retrieves the consent per API.
470      *
471      * @param apiType apiType for which the consent should be provided
472      * @return {@link AdServicesApiConsent} providing information whether the consent was given or
473      *     revoked.
474      */
getConsent(AdServicesApiType apiType)475     public AdServicesApiConsent getConsent(AdServicesApiType apiType) {
476         if (mDebugFlags.getConsentManagerDebugMode()) {
477             return AdServicesApiConsent.GIVEN;
478         }
479         return mConsentCompositeStorage.getConsent(apiType);
480     }
481 
482     /**
483      * Proxy call to {@link TopicsWorker} to get {@link ImmutableList} of {@link Topic}s which could
484      * be returned to the {@link TopicsWorker} clients.
485      *
486      * @return {@link ImmutableList} of {@link Topic}s.
487      */
getKnownTopicsWithConsent()488     public ImmutableList<Topic> getKnownTopicsWithConsent() {
489         return mTopicsWorker.getKnownTopicsWithConsent();
490     }
491 
492     /**
493      * Proxy call to {@link TopicsWorker} to get {@link ImmutableList} of {@link Topic}s which were
494      * blocked by the user.
495      *
496      * @return {@link ImmutableList} of blocked {@link Topic}s.
497      */
getTopicsWithRevokedConsent()498     public ImmutableList<Topic> getTopicsWithRevokedConsent() {
499         return mTopicsWorker.getTopicsWithRevokedConsent();
500     }
501 
502     /**
503      * Proxy call to {@link TopicsWorker} to revoke consent for provided {@link Topic} (block
504      * topic).
505      *
506      * @param topic {@link Topic} to block.
507      */
revokeConsentForTopic(Topic topic)508     public void revokeConsentForTopic(Topic topic) {
509         mTopicsWorker.revokeConsentForTopic(topic);
510     }
511 
512     /**
513      * Proxy call to {@link TopicsWorker} to restore consent for provided {@link Topic} (unblock the
514      * topic).
515      *
516      * @param topic {@link Topic} to restore consent for.
517      */
restoreConsentForTopic(Topic topic)518     public void restoreConsentForTopic(Topic topic) {
519         mTopicsWorker.restoreConsentForTopic(topic);
520     }
521 
522     /** Wipes out all the data gathered by Topics API but blocked topics. */
resetTopics()523     public void resetTopics() {
524         ArrayList<String> tablesToBlock = new ArrayList<>();
525         tablesToBlock.add(TopicsTables.BlockedTopicsContract.TABLE);
526         mTopicsWorker.clearAllTopicsData(tablesToBlock);
527     }
528 
529     /** Wipes out all the data gathered by Topics API. */
resetTopicsAndBlockedTopics()530     public void resetTopicsAndBlockedTopics() {
531         mTopicsWorker.clearAllTopicsData(new ArrayList<>());
532     }
533 
534     /**
535      * @return an {@link ImmutableList} of all known apps in the database that have not had user
536      *     consent revoked
537      */
getKnownAppsWithConsent()538     public ImmutableList<App> getKnownAppsWithConsent() {
539         return ImmutableList.copyOf(
540                 mConsentCompositeStorage.getKnownAppsWithConsent().stream()
541                         .map(App::create)
542                         .collect(Collectors.toList()));
543     }
544 
545     /**
546      * @return an {@link ImmutableList} of all known apps in the database that have had user consent
547      *     revoked
548      */
getAppsWithRevokedConsent()549     public ImmutableList<App> getAppsWithRevokedConsent() {
550         return ImmutableList.copyOf(
551                 mConsentCompositeStorage.getAppsWithRevokedConsent().stream()
552                         .map(App::create)
553                         .collect(Collectors.toList()));
554     }
555 
556     /**
557      * Proxy call to {@link AppConsentDao} to revoke consent for provided {@link App}.
558      *
559      * <p>Also clears all app data related to the provided {@link App}.
560      *
561      * @param app {@link App} to block.
562      * @throws IOException if the operation fails
563      */
revokeConsentForApp(App app)564     public void revokeConsentForApp(App app) throws IOException {
565         mConsentCompositeStorage.setConsentForApp(app.getPackageName(), true);
566 
567         asyncExecute(
568                 () ->
569                         mCustomAudienceDao.deleteCustomAudienceDataByOwner(
570                                 app.getPackageName(),
571                                 mFlags.getFledgeScheduleCustomAudienceUpdateEnabled()));
572         if (mFlags.getFledgeFrequencyCapFilteringEnabled()) {
573             asyncExecute(
574                     () -> mFrequencyCapDao.deleteHistogramDataBySourceApp(app.getPackageName()));
575         }
576         if (mFlags.getFledgeAppInstallFilteringEnabled()) {
577             asyncExecute(() -> mAppInstallDao.deleteByPackageName(app.getPackageName()));
578         }
579     }
580 
581     /**
582      * Proxy call to {@link AppConsentDao} to restore consent for provided {@link App}.
583      *
584      * @param app {@link App} to restore consent for.
585      * @throws IOException if the operation fails
586      */
restoreConsentForApp(App app)587     public void restoreConsentForApp(App app) throws IOException {
588         mConsentCompositeStorage.setConsentForApp(app.getPackageName(), false);
589     }
590 
591     /**
592      * Deletes all app consent data and all app data gathered or generated by the Privacy Sandbox.
593      *
594      * <p>This should be called when the Privacy Sandbox has been disabled.
595      *
596      * @throws IOException if the operation fails
597      */
resetAppsAndBlockedApps()598     public void resetAppsAndBlockedApps() throws IOException {
599         mConsentCompositeStorage.clearAllAppConsentData();
600 
601         asyncExecute(
602                 () ->
603                         mCustomAudienceDao.deleteAllCustomAudienceData(
604                                 mFlags.getFledgeScheduleCustomAudienceUpdateEnabled()));
605         if (mFlags.getFledgeFrequencyCapFilteringEnabled()) {
606             asyncExecute(mFrequencyCapDao::deleteAllHistogramData);
607         }
608         if (mFlags.getFledgeAppInstallFilteringEnabled()) {
609             asyncExecute(mAppInstallDao::deleteAllAppInstallData);
610         }
611         if (mFlags.getProtectedSignalsCleanupEnabled()) {
612             asyncExecute(mProtectedSignalsDao::deleteAllSignals);
613         }
614     }
615 
616     /**
617      * Deletes the list of known allowed apps as well as all app data from the Privacy Sandbox.
618      *
619      * <p>The list of blocked apps is not reset.
620      *
621      * @throws IOException if the operation fails
622      */
resetApps()623     public void resetApps() throws IOException {
624         mConsentCompositeStorage.clearKnownAppsWithConsent();
625         asyncExecute(
626                 () ->
627                         mCustomAudienceDao.deleteAllCustomAudienceData(
628                                 mFlags.getFledgeScheduleCustomAudienceUpdateEnabled()));
629         if (mFlags.getFledgeFrequencyCapFilteringEnabled()) {
630             asyncExecute(mFrequencyCapDao::deleteAllHistogramData);
631         }
632         if (mFlags.getFledgeAppInstallFilteringEnabled()) {
633             asyncExecute(mAppInstallDao::deleteAllAppInstallData);
634         }
635     }
636 
637     /**
638      * Checks whether a single given installed application (identified by its package name) has had
639      * user consent to use the FLEDGE APIs revoked.
640      *
641      * <p>This method also checks whether a user has opted out of the FLEDGE Privacy Sandbox
642      * initiative.
643      *
644      * @param packageName String package name that uniquely identifies an installed application to
645      *     check
646      * @return {@code true} if either the FLEDGE Privacy Sandbox initiative has been opted out or if
647      *     the user has revoked consent for the given application to use the FLEDGE APIs
648      * @throws IllegalArgumentException if the package name is invalid or not found as an installed
649      *     application
650      */
isFledgeConsentRevokedForApp(String packageName)651     public boolean isFledgeConsentRevokedForApp(String packageName)
652             throws IllegalArgumentException {
653         AdServicesApiConsent consent = getConsent(AdServicesApiType.FLEDGE);
654 
655         if (!consent.isGiven()) {
656             return true;
657         }
658 
659         return mConsentCompositeStorage.isConsentRevokedForApp(packageName);
660     }
661 
662     /**
663      * Persists the use of a FLEDGE API by a single given installed application (identified by its
664      * package name) if the app has not already had its consent revoked.
665      *
666      * <p>This method also checks whether a user has opted out of the FLEDGE Privacy Sandbox
667      * initiative.
668      *
669      * <p>This is only meant to be called by the FLEDGE APIs.
670      *
671      * @param packageName String package name that uniquely identifies an installed application that
672      *     has used a FLEDGE API
673      * @return {@code true} if user consent has been revoked for the application or API, {@code
674      *     false} otherwise
675      * @throws IllegalArgumentException if the package name is invalid or not found as an installed
676      *     application
677      */
isFledgeConsentRevokedForAppAfterSettingFledgeUse(String packageName)678     public boolean isFledgeConsentRevokedForAppAfterSettingFledgeUse(String packageName)
679             throws IllegalArgumentException {
680         AdServicesApiConsent consent = getConsent(AdServicesApiType.FLEDGE);
681 
682         if (!consent.isGiven()) {
683             return true;
684         }
685 
686         return mConsentCompositeStorage.setConsentForAppIfNew(packageName, false);
687     }
688 
689     /**
690      * Clear consent data after an app was uninstalled.
691      *
692      * @param packageName the package name that had been uninstalled.
693      */
clearConsentForUninstalledApp(String packageName, int packageUid)694     public void clearConsentForUninstalledApp(String packageName, int packageUid) {
695         mConsentCompositeStorage.clearConsentForUninstalledApp(packageName, packageUid);
696     }
697 
698     /**
699      * Clear consent data after an app was uninstalled, but the package Uid is unavailable. This
700      * could happen because the INTERACT_ACROSS_USERS_FULL permission is not available on Android
701      * versions prior to T.
702      *
703      * <p><strong>This method should only be used for R/S back-compat scenarios.</strong>
704      *
705      * @param packageName the package name that had been uninstalled.
706      */
clearConsentForUninstalledApp(String packageName)707     public void clearConsentForUninstalledApp(String packageName) {
708         mConsentCompositeStorage.clearConsentForUninstalledApp(packageName);
709     }
710 
711     /** Wipes out all the data gathered by Measurement API. */
resetMeasurement()712     public void resetMeasurement() {
713         mMeasurementImpl.deleteAllMeasurementData(List.of());
714         // Log wipeout event triggered by consent flip to delete data of package
715         WipeoutStatus wipeoutStatus = new WipeoutStatus();
716         wipeoutStatus.setWipeoutType(WipeoutStatus.WipeoutType.CONSENT_FLIP);
717         logWipeoutStats(wipeoutStatus);
718     }
719 
720     /** Wipes out all the Enrollment data */
721     @VisibleForTesting
resetEnrollment()722     void resetEnrollment() {
723         mEnrollmentDao.deleteAll();
724     }
725 
726     /**
727      * Saves information to the storage that notification was displayed for the first time to the
728      * user.
729      */
recordNotificationDisplayed(boolean wasNotificationDisplayed)730     public void recordNotificationDisplayed(boolean wasNotificationDisplayed) {
731         mConsentCompositeStorage.recordNotificationDisplayed(wasNotificationDisplayed);
732     }
733 
734     /**
735      * Retrieves if notification has been displayed.
736      *
737      * @return true if Consent Notification was displayed, otherwise false.
738      */
wasNotificationDisplayed()739     public boolean wasNotificationDisplayed() {
740         return mConsentCompositeStorage.wasNotificationDisplayed();
741     }
742 
743     /**
744      * Saves information to the storage that GA UX notification was displayed for the first time to
745      * the user.
746      */
recordGaUxNotificationDisplayed(boolean wasGaUxDisplayed)747     public void recordGaUxNotificationDisplayed(boolean wasGaUxDisplayed) {
748         mConsentCompositeStorage.recordGaUxNotificationDisplayed(wasGaUxDisplayed);
749     }
750 
751     /**
752      * Retrieves if GA UX notification has been displayed.
753      *
754      * @return true if GA UX Consent Notification was displayed, otherwise false.
755      */
wasGaUxNotificationDisplayed()756     public boolean wasGaUxNotificationDisplayed() {
757         return mConsentCompositeStorage.wasGaUxNotificationDisplayed();
758     }
759 
760     /** Set the current privacy sandbox feature. */
setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType currentFeatureType)761     public void setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType currentFeatureType) {
762         mConsentCompositeStorage.setCurrentPrivacySandboxFeature(currentFeatureType);
763     }
764 
765     /** Saves information to the storage that user interacted with consent manually. */
recordUserManualInteractionWithConsent(@serManualInteraction int interaction)766     public void recordUserManualInteractionWithConsent(@UserManualInteraction int interaction) {
767         mConsentCompositeStorage.recordUserManualInteractionWithConsent(interaction);
768     }
769 
770     /**
771      * Get the current privacy sandbox feature.
772      *
773      * <p>To write to PPAPI if consent source of truth is PPAPI_ONLY or dual sources. To write to
774      * system server if consent source of truth is SYSTEM_SERVER_ONLY or dual sources.
775      */
getCurrentPrivacySandboxFeature()776     public PrivacySandboxFeatureType getCurrentPrivacySandboxFeature() {
777         return mConsentCompositeStorage.getCurrentPrivacySandboxFeature();
778     }
779 
780     /**
781      * Returns information whether user interacted with consent manually.
782      *
783      * @return true if the user interacted with the consent manually, otherwise false.
784      */
getUserManualInteractionWithConsent()785     public @UserManualInteraction int getUserManualInteractionWithConsent() {
786         return mConsentCompositeStorage.getUserManualInteractionWithConsent();
787     }
788 
789     @VisibleForTesting
790     // TODO(b/311183933): Remove passed in Context from static method.
791     @SuppressWarnings("AvoidStaticContext")
createAndInitializeDataStore( Context context, AdServicesErrorLogger adServicesErrorLogger)792     static AtomicFileDatastore createAndInitializeDataStore(
793             Context context, AdServicesErrorLogger adServicesErrorLogger) {
794         @SuppressWarnings("deprecation")
795         AtomicFileDatastore atomicFileDatastore =
796                 LegacyAtomicFileDatastoreFactory.createAtomicFileDatastore(
797                         context,
798                         ConsentConstants.STORAGE_XML_IDENTIFIER,
799                         ConsentConstants.STORAGE_VERSION,
800                         adServicesErrorLogger);
801 
802         try {
803             atomicFileDatastore.initialize();
804             // TODO(b/259607624): implement a method in the datastore which would support
805             // this exact scenario - if the value is null, return default value provided
806             // in the parameter (similar to SP apply etc.)
807             if (atomicFileDatastore.getBoolean(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE)
808                     == null) {
809                 atomicFileDatastore.putBoolean(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE, false);
810             }
811             if (atomicFileDatastore.getBoolean(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE)
812                     == null) {
813                 atomicFileDatastore.putBoolean(
814                         ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE, false);
815             }
816         } catch (IOException | IllegalArgumentException | NullPointerException e) {
817             throw new RuntimeException("Failed to initialize the File Datastore!", e);
818         }
819 
820         return atomicFileDatastore;
821     }
822 
823     // Handle different migration requests based on current consent source of Truth
824     // PPAPI_ONLY: reset the shared preference to reset status of migrating consent from PPAPI to
825     //             system server.
826     // PPAPI_AND_SYSTEM_SERVER: migrate consent from PPAPI to system server.
827     // SYSTEM_SERVER_ONLY: migrate consent from PPAPI to system server and clear PPAPI consent
828     @VisibleForTesting
829     // TODO(b/311183933): Remove passed in Context from static method.
830     @SuppressWarnings("AvoidStaticContext")
handleConsentMigrationIfNeeded( Context context, AtomicFileDatastore datastore, AdServicesStorageManager adServicesManager, StatsdAdServicesLogger statsdAdServicesLogger, @Flags.ConsentSourceOfTruth int consentSourceOfTruth)831     static void handleConsentMigrationIfNeeded(
832             Context context,
833             AtomicFileDatastore datastore,
834             AdServicesStorageManager adServicesManager,
835             StatsdAdServicesLogger statsdAdServicesLogger,
836             @Flags.ConsentSourceOfTruth int consentSourceOfTruth) {
837         Objects.requireNonNull(context);
838         // It is a T+ feature. On T+, this function should only execute if it's within the
839         // AdServices
840         // APK and not ExtServices. So check if it's within ExtServices, and bail out if that's the
841         // case on any platform.
842         String packageName = context.getPackageName();
843         if (packageName != null && packageName.endsWith(ADEXTSERVICES_PACKAGE_NAME_SUFFIX)) {
844             LogUtil.d("Aborting attempt to migrate consent in ExtServices");
845             return;
846         }
847         Objects.requireNonNull(datastore);
848         if (consentSourceOfTruth == Flags.PPAPI_AND_SYSTEM_SERVER
849                 || consentSourceOfTruth == Flags.SYSTEM_SERVER_ONLY) {
850             Objects.requireNonNull(adServicesManager);
851         }
852 
853         switch (consentSourceOfTruth) {
854             case Flags.PPAPI_ONLY:
855                 // Technically we only need to reset the SHARED_PREFS_KEY_HAS_MIGRATED bit once.
856                 // What we need is clearIfSet operation which is not available in SP. So here we
857                 // always reset the bit since otherwise we need to read the SP to read the value and
858                 // the clear the value.
859                 // The only flow we would do are:
860                 // Case 1: DUAL-> PPAPI if there is a bug in System Server
861                 // Case 2: DUAL -> SYSTEM_SERVER_ONLY: if everything goes smoothly.
862                 resetSharedPreference(context, ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED);
863                 break;
864             case Flags.PPAPI_AND_SYSTEM_SERVER:
865                 migratePpApiConsentToSystemService(
866                         context, datastore, adServicesManager, statsdAdServicesLogger);
867                 break;
868             case Flags.SYSTEM_SERVER_ONLY:
869                 migratePpApiConsentToSystemService(
870                         context, datastore, adServicesManager, statsdAdServicesLogger);
871                 clearPpApiConsent(context, datastore);
872                 break;
873             case Flags.APPSEARCH_ONLY:
874                 // If this is an S- device, the consent source of truth is always APPSEARCH_ONLY.
875                 break;
876             default:
877                 break;
878         }
879     }
880 
881     // Reset data for the specific AdServicesApiType
882     @VisibleForTesting
resetByApi(AdServicesApiType apiType)883     void resetByApi(AdServicesApiType apiType) throws IOException {
884         switch (apiType) {
885             case TOPICS:
886                 resetTopicsAndBlockedTopics();
887                 break;
888             case FLEDGE:
889                 resetAppsAndBlockedApps();
890                 resetUserProfileId();
891                 break;
892             case MEASUREMENTS:
893                 resetMeasurement();
894                 break;
895             default:
896                 break;
897         }
898     }
899 
resetUserProfileId()900     private void resetUserProfileId() {
901         mUserProfileIdManager.deleteId();
902     }
903 
904     // Perform a one-time migration to migrate existing PPAPI Consent
905     @VisibleForTesting
906     @SuppressWarnings({
907         "NewApi", // Suppress lint warning for context.getUser in R since this code is unused in R
908         "AvoidStaticContext", // TODO(b/311183933): Remove passed in Context from static method.
909     })
migratePpApiConsentToSystemService( Context context, AtomicFileDatastore datastore, AdServicesStorageManager adServicesManager, StatsdAdServicesLogger statsdAdServicesLogger)910     static void migratePpApiConsentToSystemService(
911             Context context,
912             AtomicFileDatastore datastore,
913             AdServicesStorageManager adServicesManager,
914             StatsdAdServicesLogger statsdAdServicesLogger) {
915         Objects.requireNonNull(context);
916         Objects.requireNonNull(datastore);
917         Objects.requireNonNull(adServicesManager);
918 
919         AppConsents appConsents = null;
920         try {
921             // Exit if migration has happened.
922             SharedPreferences sharedPreferences =
923                     FileCompatUtils.getSharedPreferencesHelper(
924                             context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
925             // If we migrated data to system server either from PPAPI or from AppSearch, do not
926             // attempt another migration of data to system server.
927             boolean shouldSkipMigration =
928                     sharedPreferences.getBoolean(
929                                     ConsentConstants.SHARED_PREFS_KEY_APPSEARCH_HAS_MIGRATED,
930                                     /* default= */ false)
931                             || sharedPreferences.getBoolean(
932                                     ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED,
933                                     /* default= */ false);
934             if (shouldSkipMigration) {
935                 LogUtil.v(
936                         "Consent migration has happened to user %d, skip...",
937                         context.getUser().getIdentifier());
938                 return;
939             }
940             LogUtil.d("Started migrating Consent from PPAPI to System Service");
941 
942             Boolean consentKey =
943                     Boolean.TRUE.equals(datastore.getBoolean(ConsentConstants.CONSENT_KEY));
944 
945             // Migrate Consent and Notification Displayed to System Service.
946             // Set consent enabled only when value is TRUE. FALSE and null are regarded as disabled.
947             adServicesManager.setConsent(AdServicesApiType.ALL_API, consentKey);
948             // Set notification displayed only when value is TRUE. FALSE and null are regarded as
949             // not displayed.
950             if (Boolean.TRUE.equals(
951                     datastore.getBoolean(ConsentConstants.NOTIFICATION_DISPLAYED_ONCE))) {
952                 adServicesManager.recordNotificationDisplayed(true);
953             }
954 
955             Boolean manualInteractionRecorded =
956                     datastore.getBoolean(ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED);
957             if (manualInteractionRecorded != null) {
958                 adServicesManager.recordUserManualInteractionWithConsent(
959                         manualInteractionRecorded ? 1 : -1);
960             }
961 
962             // Save migration has happened into shared preferences.
963             SharedPreferences.Editor editor = sharedPreferences.edit();
964             editor.putBoolean(ConsentConstants.SHARED_PREFS_KEY_HAS_MIGRATED, true);
965             appConsents =
966                     AppConsents.builder()
967                             .setMsmtConsent(consentKey)
968                             .setFledgeConsent(consentKey)
969                             .setTopicsConsent(consentKey)
970                             .build();
971 
972             if (editor.commit()) {
973                 LogUtil.d("Finished migrating Consent from PPAPI to System Service");
974                 statsdAdServicesLogger.logConsentMigrationStats(
975                         getConsentManagerStatsForLogging(
976                                 appConsents,
977                                 ConsentMigrationStats.MigrationStatus
978                                         .SUCCESS_WITH_SHARED_PREF_UPDATED,
979                                 ConsentMigrationStats.MigrationType.PPAPI_TO_SYSTEM_SERVICE,
980                                 context));
981             } else {
982                 LogUtil.e(
983                         "Finished migrating Consent from PPAPI to System Service but shared"
984                                 + " preference is not updated.");
985                 ErrorLogUtil.e(
986                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE,
987                         AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
988                 statsdAdServicesLogger.logConsentMigrationStats(
989                         getConsentManagerStatsForLogging(
990                                 appConsents,
991                                 ConsentMigrationStats.MigrationStatus
992                                         .SUCCESS_WITH_SHARED_PREF_NOT_UPDATED,
993                                 ConsentMigrationStats.MigrationType.PPAPI_TO_SYSTEM_SERVICE,
994                                 context));
995             }
996         } catch (Exception e) {
997             LogUtil.e("PPAPI consent data migration failed: ", e);
998             statsdAdServicesLogger.logConsentMigrationStats(
999                     getConsentManagerStatsForLogging(
1000                             appConsents,
1001                             ConsentMigrationStats.MigrationStatus.FAILURE,
1002                             ConsentMigrationStats.MigrationType.PPAPI_TO_SYSTEM_SERVICE,
1003                             context));
1004         }
1005     }
1006 
1007     // Clear PPAPI Consent if fully migrated to use system server consent. This is because system
1008     // consent cannot be migrated back to PPAPI. This data clearing should only happen once.
1009     @VisibleForTesting
1010     // TODO(b/311183933): Remove passed in Context from static method.
1011     @SuppressWarnings("AvoidStaticContext")
clearPpApiConsent(Context context, AtomicFileDatastore datastore)1012     static void clearPpApiConsent(Context context, AtomicFileDatastore datastore) {
1013         // Exit if PPAPI consent has cleared.
1014         SharedPreferences sharedPreferences =
1015                 FileCompatUtils.getSharedPreferencesHelper(
1016                         context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
1017         if (sharedPreferences.getBoolean(
1018                 ConsentConstants.SHARED_PREFS_KEY_PPAPI_HAS_CLEARED, /* defValue */ false)) {
1019             return;
1020         }
1021 
1022         LogUtil.d("Started clearing Consent in PPAPI.");
1023 
1024         try {
1025             datastore.clear();
1026         } catch (IOException e) {
1027             throw new RuntimeException("Failed to clear PPAPI Consent", e);
1028         }
1029 
1030         // Save that PPAPI consent has cleared into shared preferences.
1031         SharedPreferences.Editor editor = sharedPreferences.edit();
1032         editor.putBoolean(ConsentConstants.SHARED_PREFS_KEY_PPAPI_HAS_CLEARED, true);
1033 
1034         if (editor.commit()) {
1035             LogUtil.d("Finished clearing Consent in PPAPI.");
1036         } else {
1037             LogUtil.e("Finished clearing Consent in PPAPI but shared preference is not updated.");
1038             ErrorLogUtil.e(
1039                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE,
1040                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
1041         }
1042     }
1043 
1044     // Set the shared preference to false for given key.
1045     @VisibleForTesting
1046     // TODO(b/311183933): Remove passed in Context from static method.
1047     @SuppressWarnings("AvoidStaticContext")
resetSharedPreference(Context context, String sharedPreferenceKey)1048     static void resetSharedPreference(Context context, String sharedPreferenceKey) {
1049         Objects.requireNonNull(context);
1050         Objects.requireNonNull(sharedPreferenceKey);
1051 
1052         SharedPreferences sharedPreferences =
1053                 FileCompatUtils.getSharedPreferencesHelper(
1054                         context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
1055         SharedPreferences.Editor editor = sharedPreferences.edit();
1056         editor.putBoolean(sharedPreferenceKey, false);
1057 
1058         if (editor.commit()) {
1059             LogUtil.d("Finished resetting shared preference for " + sharedPreferenceKey);
1060         } else {
1061             LogUtil.e("Failed to reset shared preference for " + sharedPreferenceKey);
1062             ErrorLogUtil.e(
1063                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_RESET_FAILURE,
1064                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
1065         }
1066     }
1067 
1068     // To write to PPAPI if consent source of truth is PPAPI_ONLY or dual sources.
1069     // To write to system server if consent source of truth is SYSTEM_SERVER_ONLY or dual sources.
1070     @VisibleForTesting
setConsentToSourceOfTruth(boolean isGiven)1071     void setConsentToSourceOfTruth(boolean isGiven) {
1072         mConsentCompositeStorage.setConsent(AdServicesApiType.ALL_API, isGiven);
1073     }
1074 
1075     @VisibleForTesting
getConsentFromSourceOfTruth()1076     boolean getConsentFromSourceOfTruth() {
1077         return mConsentCompositeStorage.getConsent(AdServicesApiType.ALL_API).isGiven();
1078     }
1079 
1080     @VisibleForTesting
getPerApiConsentFromSourceOfTruth(AdServicesApiType apiType)1081     boolean getPerApiConsentFromSourceOfTruth(AdServicesApiType apiType) {
1082         return mConsentCompositeStorage.getConsent(apiType).isGiven();
1083     }
1084 
1085     @VisibleForTesting
setPerApiConsentToSourceOfTruth(boolean isGiven, AdServicesApiType apiType)1086     void setPerApiConsentToSourceOfTruth(boolean isGiven, AdServicesApiType apiType) {
1087         mConsentCompositeStorage.setConsent(apiType, isGiven);
1088     }
1089 
storeUserManualInteractionToPpApi( @onsentManagerV2.UserManualInteraction int interaction, AtomicFileDatastore datastore)1090     private static void storeUserManualInteractionToPpApi(
1091             @ConsentManagerV2.UserManualInteraction int interaction, AtomicFileDatastore datastore)
1092             throws IOException {
1093         switch (interaction) {
1094             case NO_MANUAL_INTERACTIONS_RECORDED:
1095                 datastore.putBoolean(
1096                         ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED, false);
1097                 break;
1098             case UNKNOWN:
1099                 datastore.remove(ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED);
1100                 break;
1101             case MANUAL_INTERACTIONS_RECORDED:
1102                 datastore.putBoolean(
1103                         ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED, true);
1104                 break;
1105             default:
1106                 throw new IllegalArgumentException(
1107                         String.format("InteractionId < %d > can not be handled.", interaction));
1108         }
1109     }
1110 
1111     /**
1112      * This method handles migration of consent data from AppSearch to AdServices. Consent data is
1113      * written to AppSearch on S- and ported to AdServices after OTA to T. If any new data is
1114      * written for consent, we need to make sure it is migrated correctly post-OTA in this method.
1115      */
1116     @VisibleForTesting
1117     // TODO(b/311183933): Remove passed in Context from static method.
1118     @SuppressWarnings("AvoidStaticContext")
handleConsentMigrationFromAppSearchIfNeeded( Context context, AtomicFileDatastore datastore, AppConsentDao appConsentDao, AppSearchConsentStorageManager appSearchConsentStorageManager, AdServicesStorageManager adServicesStorageManager, StatsdAdServicesLogger statsdAdServicesLogger)1119     static void handleConsentMigrationFromAppSearchIfNeeded(
1120             Context context,
1121             AtomicFileDatastore datastore,
1122             AppConsentDao appConsentDao,
1123             AppSearchConsentStorageManager appSearchConsentStorageManager,
1124             AdServicesStorageManager adServicesStorageManager,
1125             StatsdAdServicesLogger statsdAdServicesLogger) {
1126         Objects.requireNonNull(context);
1127         Objects.requireNonNull(appSearchConsentStorageManager);
1128         LogUtil.d("Check migrating Consent from AppSearch to PPAPI and System Service");
1129 
1130         // On R/S, this function should never be executed because AppSearch to PPAPI and
1131         // System Server migration is a T+ feature. On T+, this function should only execute
1132         // if it's within the AdServices APK and not ExtServices. So check if it's within
1133         // ExtServices, and bail out if that's the case on any platform.
1134         String packageName = context.getPackageName();
1135         if (packageName != null && packageName.endsWith(ADEXTSERVICES_PACKAGE_NAME_SUFFIX)) {
1136             LogUtil.d(
1137                     "Aborting attempt to migrate AppSearch to PPAPI and System Service in"
1138                             + " ExtServices");
1139             return;
1140         }
1141 
1142         AppConsents appConsents = null;
1143         try {
1144             // This should be called only once after OTA (if flag is enabled). If we did not record
1145             // showing the notification on T+ yet and we have shown the notification on S- (as
1146             // recorded
1147             // in AppSearch), initialize T+ consent data so that we don't show notification twice
1148             // (after
1149             // OTA upgrade).
1150             SharedPreferences sharedPreferences =
1151                     FileCompatUtils.getSharedPreferencesHelper(
1152                             context, ConsentConstants.SHARED_PREFS_CONSENT, Context.MODE_PRIVATE);
1153             // If we did not migrate notification data, we should not attempt to migrate anything.
1154             if (!appSearchConsentStorageManager.migrateConsentDataIfNeeded(
1155                     sharedPreferences, datastore, adServicesStorageManager, appConsentDao)) {
1156                 LogUtil.d("Skipping consent migration from AppSearch");
1157                 return;
1158             }
1159             // Migrate Consent for all APIs and per API to PP API and System Service.
1160             appConsents =
1161                     migrateAppSearchConsents(
1162                             appSearchConsentStorageManager, adServicesStorageManager, datastore);
1163             // Record interactions data only if we recorded an interaction in AppSearch.
1164             int manualInteractionRecorded =
1165                     appSearchConsentStorageManager.getUserManualInteractionWithConsent();
1166             if (manualInteractionRecorded == MANUAL_INTERACTIONS_RECORDED) {
1167                 // Initialize PP API datastore.
1168                 storeUserManualInteractionToPpApi(MANUAL_INTERACTIONS_RECORDED, datastore);
1169                 // Initialize system service.
1170                 adServicesStorageManager.recordUserManualInteractionWithConsent(
1171                         manualInteractionRecorded);
1172             }
1173 
1174             // Record that we migrated consent data from AppSearch. We write the notification data
1175             // to system server and perform migration only if system server did not record any
1176             // notification having been displayed.
1177             SharedPreferences.Editor editor = sharedPreferences.edit();
1178             editor.putBoolean(ConsentConstants.SHARED_PREFS_KEY_APPSEARCH_HAS_MIGRATED, true);
1179             if (editor.commit()) {
1180                 LogUtil.d("Finished migrating Consent from AppSearch to PPAPI + System Service");
1181                 statsdAdServicesLogger.logConsentMigrationStats(
1182                         getConsentManagerStatsForLogging(
1183                                 appConsents,
1184                                 ConsentMigrationStats.MigrationStatus
1185                                         .SUCCESS_WITH_SHARED_PREF_UPDATED,
1186                                 ConsentMigrationStats.MigrationType.APPSEARCH_TO_SYSTEM_SERVICE,
1187                                 context));
1188             } else {
1189                 LogUtil.e(
1190                         "Finished migrating Consent from AppSearch to PPAPI + System Service "
1191                                 + "but shared preference is not updated.");
1192                 statsdAdServicesLogger.logConsentMigrationStats(
1193                         getConsentManagerStatsForLogging(
1194                                 appConsents,
1195                                 ConsentMigrationStats.MigrationStatus
1196                                         .SUCCESS_WITH_SHARED_PREF_NOT_UPDATED,
1197                                 ConsentMigrationStats.MigrationType.APPSEARCH_TO_SYSTEM_SERVICE,
1198                                 context));
1199             }
1200         } catch (IOException e) {
1201             LogUtil.e("AppSearch consent data migration failed: ", e);
1202             ErrorLogUtil.e(
1203                     e,
1204                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_SEARCH_DATA_MIGRATION_FAILURE,
1205                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
1206             statsdAdServicesLogger.logConsentMigrationStats(
1207                     getConsentManagerStatsForLogging(
1208                             appConsents,
1209                             ConsentMigrationStats.MigrationStatus.FAILURE,
1210                             ConsentMigrationStats.MigrationType.APPSEARCH_TO_SYSTEM_SERVICE,
1211                             context));
1212         }
1213     }
1214 
1215     /**
1216      * This method returns and migrates the consent states (opt in/out) for all PPAPIs, each API and
1217      * their default consent values.
1218      */
1219     @VisibleForTesting
migrateAppSearchConsents( AppSearchConsentStorageManager appSearchConsentManager, AdServicesStorageManager adServicesManager, AtomicFileDatastore datastore)1220     static AppConsents migrateAppSearchConsents(
1221             AppSearchConsentStorageManager appSearchConsentManager,
1222             AdServicesStorageManager adServicesManager,
1223             AtomicFileDatastore datastore)
1224             throws IOException {
1225         Map<String, Boolean> consentMap = new HashMap<>();
1226         for (AdServicesApiType apiType : AdServicesApiType.values()) {
1227             if (apiType == AdServicesApiType.UNKNOWN) {
1228                 continue;
1229             }
1230             boolean consented = appSearchConsentManager.getConsent(apiType).isGiven();
1231             datastore.putBoolean(apiType.toPpApiDatastoreKey(), consented);
1232             adServicesManager.setConsent(apiType, consented);
1233             consentMap.put(apiType.toPpApiDatastoreKey(), consented);
1234         }
1235         return AppConsents.builder()
1236                 .setMsmtConsent(
1237                         consentMap.get(AdServicesApiType.MEASUREMENTS.toPpApiDatastoreKey()))
1238                 .setTopicsConsent(consentMap.get(AdServicesApiType.TOPICS.toPpApiDatastoreKey()))
1239                 .setFledgeConsent(consentMap.get(AdServicesApiType.FLEDGE.toPpApiDatastoreKey()))
1240                 .build();
1241     }
1242 
1243     /**
1244      * Represents revoked consent as internally determined by the PP APIs.
1245      *
1246      * <p>This is an internal-only exception and is not meant to be returned to external callers.
1247      */
1248     public static class RevokedConsentException extends IllegalStateException {
1249         public static final String REVOKED_CONSENT_ERROR_MESSAGE =
1250                 "Error caused by revoked user consent";
1251 
1252         /** Creates an instance of a {@link RevokedConsentException}. */
RevokedConsentException()1253         public RevokedConsentException() {
1254             super(REVOKED_CONSENT_ERROR_MESSAGE);
1255         }
1256     }
1257 
asyncExecute(Runnable runnable)1258     private void asyncExecute(Runnable runnable) {
1259         AdServicesExecutors.getBackgroundExecutor().execute(runnable);
1260     }
1261 
logWipeoutStats(WipeoutStatus wipeoutStatus)1262     private void logWipeoutStats(WipeoutStatus wipeoutStatus) {
1263         AdServicesLoggerImpl.getInstance()
1264                 .logMeasurementWipeoutStats(
1265                         new MeasurementWipeoutStats.Builder()
1266                                 .setCode(AD_SERVICES_MEASUREMENT_WIPEOUT)
1267                                 .setWipeoutType(wipeoutStatus.getWipeoutType().getValue())
1268                                 .build());
1269     }
1270 
1271     /** Returns whether the isAdIdEnabled bit is true based on consent_source_of_truth. */
isAdIdEnabled()1272     public boolean isAdIdEnabled() {
1273         return mConsentCompositeStorage.isAdIdEnabled();
1274     }
1275 
1276     /** Set the AdIdEnabled bit to storage based on consent_source_of_truth. */
setAdIdEnabled(boolean isAdIdEnabled)1277     public void setAdIdEnabled(boolean isAdIdEnabled) {
1278         mConsentCompositeStorage.setAdIdEnabled(isAdIdEnabled);
1279     }
1280 
1281     /** Returns whether the isU18Account bit is true based on consent_source_of_truth. */
isU18Account()1282     public boolean isU18Account() {
1283         return mConsentCompositeStorage.isU18Account();
1284     }
1285 
1286     /** Set the U18Account bit to storage based on consent_source_of_truth. */
setU18Account(boolean isU18Account)1287     public void setU18Account(boolean isU18Account) {
1288         mConsentCompositeStorage.setU18Account(isU18Account);
1289     }
1290 
1291     /** Returns whether the isEntryPointEnabled bit is true based on consent_source_of_truth. */
isEntryPointEnabled()1292     public boolean isEntryPointEnabled() {
1293         return mConsentCompositeStorage.isEntryPointEnabled();
1294     }
1295 
1296     /** Set the EntryPointEnabled bit to storage based on consent_source_of_truth. */
setEntryPointEnabled(boolean isEntryPointEnabled)1297     public void setEntryPointEnabled(boolean isEntryPointEnabled) {
1298         mConsentCompositeStorage.setEntryPointEnabled(isEntryPointEnabled);
1299     }
1300 
1301     /** Returns whether the isAdultAccount bit is true based on consent_source_of_truth. */
isAdultAccount()1302     public boolean isAdultAccount() {
1303         return mConsentCompositeStorage.isAdultAccount();
1304     }
1305 
1306     /** Set the AdultAccount bit to storage based on consent_source_of_truth. */
setAdultAccount(boolean isAdultAccount)1307     public void setAdultAccount(boolean isAdultAccount) {
1308         mConsentCompositeStorage.setAdultAccount(isAdultAccount);
1309     }
1310 
1311     /**
1312      * Returns whether the wasU18NotificationDisplayed bit is true based on consent_source_of_truth.
1313      */
wasU18NotificationDisplayed()1314     public boolean wasU18NotificationDisplayed() {
1315         return mConsentCompositeStorage.wasU18NotificationDisplayed();
1316     }
1317 
1318     /** Set the U18NotificationDisplayed bit to storage based on consent_source_of_truth. */
setU18NotificationDisplayed(boolean wasU18NotificationDisplayed)1319     public void setU18NotificationDisplayed(boolean wasU18NotificationDisplayed) {
1320         mConsentCompositeStorage.setU18NotificationDisplayed(wasU18NotificationDisplayed);
1321     }
1322 
1323     /** Returns current UX based on consent_source_of_truth. */
getUx()1324     public PrivacySandboxUxCollection getUx() {
1325         return mConsentCompositeStorage.getUx();
1326     }
1327 
1328     /** Set the current UX to storage based on consent_source_of_truth. */
setUx(PrivacySandboxUxCollection ux)1329     public void setUx(PrivacySandboxUxCollection ux) {
1330         mConsentCompositeStorage.setUx(ux);
1331     }
1332 
1333     /** Returns current enrollment channel based on consent_source_of_truth. */
getEnrollmentChannel( PrivacySandboxUxCollection ux)1334     public PrivacySandboxEnrollmentChannelCollection getEnrollmentChannel(
1335             PrivacySandboxUxCollection ux) {
1336         return mConsentCompositeStorage.getEnrollmentChannel(ux);
1337     }
1338 
1339     /** Set the current enrollment channel to storage based on consent_source_of_truth. */
setEnrollmentChannel( PrivacySandboxUxCollection ux, PrivacySandboxEnrollmentChannelCollection channel)1340     public void setEnrollmentChannel(
1341             PrivacySandboxUxCollection ux, PrivacySandboxEnrollmentChannelCollection channel) {
1342         mConsentCompositeStorage.setEnrollmentChannel(ux, channel);
1343     }
1344 
1345     @VisibleForTesting
setConsentToPpApi(boolean isGiven)1346     void setConsentToPpApi(boolean isGiven) throws IOException {
1347         mDatastore.putBoolean(ConsentConstants.CONSENT_KEY, isGiven);
1348     }
1349 
1350     /* Returns the region od the device */
getConsentRegion(Context context)1351     private static int getConsentRegion(Context context) {
1352         return DeviceRegionProvider.isEuDevice(context)
1353                 ? AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__EU
1354                 : AD_SERVICES_SETTINGS_USAGE_REPORTED__REGION__ROW;
1355     }
1356 
1357     /* Returns an object of ConsentMigrationStats */
getConsentManagerStatsForLogging( AppConsents appConsents, ConsentMigrationStats.MigrationStatus migrationStatus, ConsentMigrationStats.MigrationType migrationType, Context context)1358     private static ConsentMigrationStats getConsentManagerStatsForLogging(
1359             AppConsents appConsents,
1360             ConsentMigrationStats.MigrationStatus migrationStatus,
1361             ConsentMigrationStats.MigrationType migrationType,
1362             Context context) {
1363         ConsentMigrationStats consentMigrationStats =
1364                 ConsentMigrationStats.builder()
1365                         .setMigrationType(migrationType)
1366                         // When appConsents is null we log it as a failure
1367                         .setMigrationStatus(
1368                                 appConsents != null
1369                                         ? migrationStatus
1370                                         : ConsentMigrationStats.MigrationStatus.FAILURE)
1371                         .setMsmtConsent(appConsents == null || appConsents.getMsmtConsent())
1372                         .setTopicsConsent(appConsents == null || appConsents.getTopicsConsent())
1373                         .setFledgeConsent(appConsents == null || appConsents.getFledgeConsent())
1374                         .setRegion(getConsentRegion(context))
1375                         .build();
1376         return consentMigrationStats;
1377     }
1378 
1379     /** Returns whether the Fledge Consent is given. */
isPasFledgeConsentGiven()1380     public boolean isPasFledgeConsentGiven() {
1381         return mFlags.getPasUxEnabled()
1382                 && mConsentCompositeStorage.wasPasNotificationDisplayed()
1383                 && getConsent(AdServicesApiType.FLEDGE).isGiven();
1384     }
1385 
1386     /** Sets the isMeasurementDataReset bit to storage based on consent_source_of_truth. */
setMeasurementDataReset(boolean isMeasurementDataReset)1387     public void setMeasurementDataReset(boolean isMeasurementDataReset) {
1388         mConsentCompositeStorage.setMeasurementDataReset(isMeasurementDataReset);
1389     }
1390 
1391     /**
1392      * Returns whether the measurement data reset activity happens based on consent_source_of_truth.
1393      */
isMeasurementDataReset()1394     public boolean isMeasurementDataReset() {
1395         return mConsentCompositeStorage.isMeasurementDataReset();
1396     }
1397 
1398     /**
1399      * Retrieves if PAS notification has been displayed.
1400      *
1401      * @return true if PAS Consent Notification was displayed, otherwise false.
1402      */
wasPasNotificationDisplayed()1403     public boolean wasPasNotificationDisplayed() {
1404         return mConsentCompositeStorage.wasPasNotificationDisplayed();
1405     }
1406 
1407     /**
1408      * Saves information to the storage that PAS UX notification was displayed for the first time to
1409      * the user.
1410      */
recordPasNotificationDisplayed(boolean wasPasDisplayed)1411     public void recordPasNotificationDisplayed(boolean wasPasDisplayed) {
1412         mConsentCompositeStorage.recordPasNotificationDisplayed(wasPasDisplayed);
1413     }
1414 
1415     /** get pas conset for measurement */
isPasMeasurementConsentGiven()1416     public boolean isPasMeasurementConsentGiven() {
1417         if (mDebugFlags.getConsentManagerDebugMode()) {
1418             return true;
1419         }
1420 
1421         return mFlags.getPasUxEnabled()
1422                 && wasPasNotificationDisplayed()
1423                 && getConsent(AdServicesApiType.MEASUREMENTS).isGiven();
1424     }
1425 
1426     /**
1427      * Retrieves the PP API default consent.
1428      *
1429      * @return true if the default consent is true, false otherwise.
1430      */
getDefaultConsent()1431     public boolean getDefaultConsent() {
1432         return mConsentCompositeStorage.getDefaultConsent();
1433     }
1434 
1435     /**
1436      * Retrieves the topics default consent.
1437      *
1438      * @return true if the topics default consent is true, false otherwise.
1439      */
getTopicsDefaultConsent()1440     boolean getTopicsDefaultConsent() {
1441         return mConsentCompositeStorage.getTopicsDefaultConsent();
1442     }
1443 
1444     /**
1445      * Retrieves the FLEDGE default consent.
1446      *
1447      * @return true if the FLEDGE default consent is true, false otherwise.
1448      */
getFledgeDefaultConsent()1449     boolean getFledgeDefaultConsent() {
1450         return mConsentCompositeStorage.getFledgeDefaultConsent();
1451     }
1452 
1453     /**
1454      * Retrieves the measurement default consent.
1455      *
1456      * @return true if the measurement default consent is true, false otherwise.
1457      */
getMeasurementDefaultConsent()1458     boolean getMeasurementDefaultConsent() {
1459         return mConsentCompositeStorage.getMeasurementDefaultConsent();
1460     }
1461 
1462     /**
1463      * Retrieves the default AdId state.
1464      *
1465      * @return true if the AdId is enabled by default, false otherwise.
1466      */
getDefaultAdIdState()1467     boolean getDefaultAdIdState() {
1468         return mConsentCompositeStorage.getDefaultAdIdState();
1469     }
1470 
1471     /** Saves the default consent bit to data stores based on source of truth. */
recordDefaultConsent(boolean defaultConsent)1472     void recordDefaultConsent(boolean defaultConsent) {
1473         mConsentCompositeStorage.recordDefaultConsent(defaultConsent);
1474     }
1475 
1476     /** Saves the topics default consent bit to data stores based on source of truth. */
recordTopicsDefaultConsent(boolean defaultConsent)1477     void recordTopicsDefaultConsent(boolean defaultConsent) {
1478         mConsentCompositeStorage.recordTopicsDefaultConsent(defaultConsent);
1479     }
1480 
1481     /** Saves the FLEDGE default consent bit to data stores based on source of truth. */
recordFledgeDefaultConsent(boolean defaultConsent)1482     void recordFledgeDefaultConsent(boolean defaultConsent) {
1483         mConsentCompositeStorage.recordFledgeDefaultConsent(defaultConsent);
1484     }
1485 
1486     /** Saves the measurement default consent bit to data stores based on source of truth. */
recordMeasurementDefaultConsent(boolean defaultConsent)1487     void recordMeasurementDefaultConsent(boolean defaultConsent) {
1488         mConsentCompositeStorage.recordMeasurementDefaultConsent(defaultConsent);
1489     }
1490 
1491     /** Saves the default AdId state bit to data stores based on source of truth. */
recordDefaultAdIdState(boolean defaultAdIdState)1492     void recordDefaultAdIdState(boolean defaultAdIdState) {
1493         mConsentCompositeStorage.recordDefaultAdIdState(defaultAdIdState);
1494     }
1495 
1496     /**
1497      * Returns whether the measurement data reset activity happens based on consent_source_of_truth.
1498      */
isPaDataReset()1499     boolean isPaDataReset() {
1500         return mConsentCompositeStorage.isPaDataReset();
1501     }
1502 
1503     /** Set the isPaDataReset bit to storage based on consent_source_of_truth. */
setPaDataReset(boolean isPaDataReset)1504     void setPaDataReset(boolean isPaDataReset) {
1505         mConsentCompositeStorage.setPaDataReset(isPaDataReset);
1506     }
1507 
1508     /** Dumps its internal state. */
dump(PrintWriter writer, @Nullable String[] args)1509     public void dump(PrintWriter writer, @Nullable String[] args) {
1510         writer.println("ConsentManagerV2");
1511         String prefix = "  ";
1512 
1513         writer.printf("%ssDataMigrationDuration: %dms\n", prefix, sDataMigrationDurationMs);
1514         writer.printf("%ssInstantiationDuration: %dms\n", prefix, sInstantiationDurationMs);
1515 
1516         writer.printf("%sDatastore:\n", prefix);
1517         String prefix2 = "    ";
1518         mDatastore.dump(writer, prefix2, /* args= */ null);
1519     }
1520 }
1521