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 package com.android.server.adservices.consent;
17 
18 import android.annotation.NonNull;
19 import android.app.adservices.consent.ConsentParcel;
20 
21 import com.android.adservices.shared.storage.BooleanFileDatastore;
22 import com.android.internal.annotations.VisibleForTesting;
23 import com.android.server.adservices.LogUtil;
24 import com.android.server.adservices.feature.PrivacySandboxEnrollmentChannelCollection;
25 import com.android.server.adservices.feature.PrivacySandboxFeatureType;
26 import com.android.server.adservices.feature.PrivacySandboxUxCollection;
27 
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.PrintWriter;
31 import java.util.Objects;
32 import java.util.concurrent.locks.ReadWriteLock;
33 import java.util.concurrent.locks.ReentrantReadWriteLock;
34 import java.util.stream.Stream;
35 
36 /**
37  * Manager to handle user's consent. We will have one ConsentManager instance per user.
38  *
39  * @hide
40  */
41 public final class ConsentManager {
42     public static final String ERROR_MESSAGE_DATASTORE_EXCEPTION_WHILE_GET_CONTENT =
43             "getConsent method failed. Revoked consent is returned as fallback.";
44 
45     public static final String VERSION_KEY = "android.app.adservices.consent.VERSION";
46 
47     @VisibleForTesting
48     static final String NOTIFICATION_DISPLAYED_ONCE = "NOTIFICATION-DISPLAYED-ONCE";
49 
50     static final String GA_UX_NOTIFICATION_DISPLAYED_ONCE = "GA-UX-NOTIFICATION-DISPLAYED-ONCE";
51 
52     static final String PAS_NOTIFICATION_DISPLAYED_ONCE = "PAS_NOTIFICATION_DISPLAYED_ONCE";
53 
54     static final String PAS_NOTIFICATION_OPENED = "PAS_NOTIFICATION_OPENED";
55 
56     static final String TOPICS_CONSENT_PAGE_DISPLAYED = "TOPICS-CONSENT-PAGE-DISPLAYED";
57 
58     static final String FLEDGE_AND_MSMT_CONSENT_PAGE_DISPLAYED =
59             "FLDEGE-AND-MSMT-CONDENT-PAGE-DISPLAYED";
60 
61     private static final String CONSENT_API_TYPE_PREFIX = "CONSENT_API_TYPE_";
62 
63     // Deprecate this since we store each version in its own folder.
64     static final int STORAGE_VERSION = 1;
65     static final String STORAGE_XML_IDENTIFIER = "ConsentManagerStorageIdentifier.xml";
66 
67     private final BooleanFileDatastore mDatastore;
68 
69     @VisibleForTesting static final String DEFAULT_CONSENT = "DEFAULT_CONSENT";
70 
71     @VisibleForTesting static final String TOPICS_DEFAULT_CONSENT = "TOPICS_DEFAULT_CONSENT";
72 
73     @VisibleForTesting static final String FLEDGE_DEFAULT_CONSENT = "FLEDGE_DEFAULT_CONSENT";
74 
75     @VisibleForTesting
76     static final String MEASUREMENT_DEFAULT_CONSENT = "MEASUREMENT_DEFAULT_CONSENT";
77 
78     @VisibleForTesting static final String DEFAULT_AD_ID_STATE = "DEFAULT_AD_ID_STATE";
79 
80     @VisibleForTesting
81     static final String MANUAL_INTERACTION_WITH_CONSENT_RECORDED =
82             "MANUAL_INTERACTION_WITH_CONSENT_RECORDED";
83 
84     private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
85 
ConsentManager(@onNull BooleanFileDatastore datastore)86     private ConsentManager(@NonNull BooleanFileDatastore datastore) {
87         Objects.requireNonNull(datastore);
88 
89         mDatastore = datastore;
90     }
91 
92     /** Create a ConsentManager with base directory and for userIdentifier */
93     @NonNull
createConsentManager(@onNull String baseDir, int userIdentifier)94     public static ConsentManager createConsentManager(@NonNull String baseDir, int userIdentifier)
95             throws IOException {
96         Objects.requireNonNull(baseDir, "Base dir must be provided.");
97 
98         // The Data store is in folder with the following format.
99         // /data/system/adservices/user_id/consent/data_schema_version/
100         // Create the consent directory if needed.
101         String consentDataStoreDir =
102                 ConsentDatastoreLocationHelper.getConsentDataStoreDirAndCreateDir(
103                         baseDir, userIdentifier);
104 
105         BooleanFileDatastore datastore = createAndInitBooleanFileDatastore(consentDataStoreDir);
106 
107         return new ConsentManager(datastore);
108     }
109 
110     @NonNull
111     @VisibleForTesting
createAndInitBooleanFileDatastore(String consentDataStoreDir)112     static BooleanFileDatastore createAndInitBooleanFileDatastore(String consentDataStoreDir)
113             throws IOException {
114         // Create the DataStore and initialize it.
115         BooleanFileDatastore datastore =
116                 new BooleanFileDatastore(
117                         consentDataStoreDir, STORAGE_XML_IDENTIFIER, STORAGE_VERSION, VERSION_KEY);
118         datastore.initialize();
119         // TODO(b/259607624): implement a method in the datastore which would support
120         // this exact scenario - if the value is null, return default value provided
121         // in the parameter (similar to SP apply etc.)
122         if (datastore.get(NOTIFICATION_DISPLAYED_ONCE) == null) {
123             datastore.put(NOTIFICATION_DISPLAYED_ONCE, false);
124         }
125         if (datastore.get(GA_UX_NOTIFICATION_DISPLAYED_ONCE) == null) {
126             datastore.put(GA_UX_NOTIFICATION_DISPLAYED_ONCE, false);
127         }
128         if (datastore.get(TOPICS_CONSENT_PAGE_DISPLAYED) == null) {
129             datastore.put(TOPICS_CONSENT_PAGE_DISPLAYED, false);
130         }
131         if (datastore.get(FLEDGE_AND_MSMT_CONSENT_PAGE_DISPLAYED) == null) {
132             datastore.put(FLEDGE_AND_MSMT_CONSENT_PAGE_DISPLAYED, false);
133         }
134         return datastore;
135     }
136 
137     /** Retrieves the consent for all PP API services. */
getConsent(@onsentParcel.ConsentApiType int consentApiType)138     public ConsentParcel getConsent(@ConsentParcel.ConsentApiType int consentApiType) {
139         LogUtil.d("ConsentManager.getConsent() is invoked for consentApiType = " + consentApiType);
140 
141         mReadWriteLock.readLock().lock();
142         try {
143             return new ConsentParcel.Builder()
144                     .setConsentApiType(consentApiType)
145                     .setIsGiven(mDatastore.get(getConsentApiTypeKey(consentApiType)))
146                     .build();
147         } catch (NullPointerException | IllegalArgumentException e) {
148             LogUtil.e(e, ERROR_MESSAGE_DATASTORE_EXCEPTION_WHILE_GET_CONTENT);
149             return ConsentParcel.createRevokedConsent(consentApiType);
150         } finally {
151             mReadWriteLock.readLock().unlock();
152         }
153     }
154 
155     /** Set Consent */
setConsent(ConsentParcel consentParcel)156     public void setConsent(ConsentParcel consentParcel) throws IOException {
157         mReadWriteLock.writeLock().lock();
158         try {
159             mDatastore.put(
160                     getConsentApiTypeKey(consentParcel.getConsentApiType()),
161                     consentParcel.isIsGiven());
162             if (consentParcel.getConsentApiType() == ConsentParcel.ALL_API) {
163                 // Convert from 1 to 3 consents.
164                 mDatastore.put(
165                         getConsentApiTypeKey(ConsentParcel.TOPICS), consentParcel.isIsGiven());
166                 mDatastore.put(
167                         getConsentApiTypeKey(ConsentParcel.FLEDGE), consentParcel.isIsGiven());
168                 mDatastore.put(
169                         getConsentApiTypeKey(ConsentParcel.MEASUREMENT), consentParcel.isIsGiven());
170             } else {
171                 // Convert from 3 consents to 1 consent.
172                 if (mDatastore.get(
173                                 getConsentApiTypeKey(ConsentParcel.TOPICS), /* defaultValue */
174                                 false)
175                         && mDatastore.get(
176                                 getConsentApiTypeKey(ConsentParcel.FLEDGE), /* defaultValue */
177                                 false)
178                         && mDatastore.get(
179                                 getConsentApiTypeKey(ConsentParcel.MEASUREMENT), /* defaultValue */
180                                 false)) {
181                     mDatastore.put(getConsentApiTypeKey(ConsentParcel.ALL_API), true);
182                 } else {
183                     mDatastore.put(getConsentApiTypeKey(ConsentParcel.ALL_API), false);
184                 }
185             }
186         } finally {
187             mReadWriteLock.writeLock().unlock();
188         }
189     }
190 
191     /**
192      * Saves information to the storage that notification was displayed for the first time to the
193      * user.
194      */
recordNotificationDisplayed(boolean wasNotificationDisplayed)195     public void recordNotificationDisplayed(boolean wasNotificationDisplayed) {
196         setValueWithLock(
197                 NOTIFICATION_DISPLAYED_ONCE,
198                 wasNotificationDisplayed,
199                 "recordNotificationDisplayed");
200     }
201 
202     /**
203      * Returns information whether Consent Notification was displayed or not.
204      *
205      * @return true if Consent Notification was displayed, otherwise false.
206      */
wasNotificationDisplayed()207     public boolean wasNotificationDisplayed() {
208         return getValueWithLock(NOTIFICATION_DISPLAYED_ONCE);
209     }
210 
211     /**
212      * Saves information to the storage that GA UX notification was displayed for the first time to
213      * the user.
214      */
recordGaUxNotificationDisplayed(boolean wasNotificationDisplayed)215     public void recordGaUxNotificationDisplayed(boolean wasNotificationDisplayed) {
216         setValueWithLock(
217                 GA_UX_NOTIFICATION_DISPLAYED_ONCE,
218                 wasNotificationDisplayed,
219                 "recordGaUxNotificationDisplayed");
220     }
221 
222     /**
223      * Returns information whether GA Ux Consent Notification was displayed or not.
224      *
225      * @return true if GA UX Consent Notification was displayed, otherwise false.
226      */
wasGaUxNotificationDisplayed()227     public boolean wasGaUxNotificationDisplayed() {
228         return getValueWithLock(GA_UX_NOTIFICATION_DISPLAYED_ONCE);
229     }
230 
231     /**
232      * Saves information to the storage that PAS notification was displayed for the first time to
233      * the user.
234      */
recordPasNotificationDisplayed(boolean wasNotificationDisplayed)235     public void recordPasNotificationDisplayed(boolean wasNotificationDisplayed) {
236         setValueWithLock(
237                 PAS_NOTIFICATION_DISPLAYED_ONCE,
238                 wasNotificationDisplayed,
239                 "recordPasNotificationDisplayed");
240     }
241 
242     /**
243      * Returns information whether PAS Consent Notification was displayed or not.
244      *
245      * @return true if PAS Consent Notification was displayed, otherwise false.
246      */
wasPasNotificationDisplayed()247     public boolean wasPasNotificationDisplayed() {
248         return getValueWithLock(PAS_NOTIFICATION_DISPLAYED_ONCE);
249     }
250 
251     /**
252      * Saves information to the storage that PAS notification was opened for the first time to the
253      * user.
254      */
recordPasNotificationOpened(boolean wasNotificationOpened)255     public void recordPasNotificationOpened(boolean wasNotificationOpened) {
256         setValueWithLock(
257                 PAS_NOTIFICATION_OPENED, wasNotificationOpened, "recordPasNotificationOpened");
258     }
259 
260     /**
261      * Returns information whether PAS Consent Notification was opened or not.
262      *
263      * @return true if PAS Consent Notification was opened, otherwise false.
264      */
wasPasNotificationOpened()265     public boolean wasPasNotificationOpened() {
266         return getValueWithLock(PAS_NOTIFICATION_OPENED);
267     }
268 
269     /** Saves the default consent of a user. */
recordDefaultConsent(boolean defaultConsent)270     public void recordDefaultConsent(boolean defaultConsent) {
271         setValueWithLock(DEFAULT_CONSENT, defaultConsent, /* callerName */ "recordDefaultConsent");
272     }
273 
274     /** Saves the default topics consent of a user. */
recordTopicsDefaultConsent(boolean defaultConsent)275     public void recordTopicsDefaultConsent(boolean defaultConsent) {
276         setValueWithLock(
277                 TOPICS_DEFAULT_CONSENT,
278                 defaultConsent, /* callerName */
279                 "recordTopicsDefaultConsent");
280     }
281 
282     /** Saves the default FLEDGE consent of a user. */
recordFledgeDefaultConsent(boolean defaultConsent)283     public void recordFledgeDefaultConsent(boolean defaultConsent) {
284         setValueWithLock(
285                 FLEDGE_DEFAULT_CONSENT,
286                 defaultConsent, /* callerName */
287                 "recordFledgeDefaultConsent");
288     }
289 
290     /** Saves the default measurement consent of a user. */
recordMeasurementDefaultConsent(boolean defaultConsent)291     public void recordMeasurementDefaultConsent(boolean defaultConsent) {
292         setValueWithLock(
293                 MEASUREMENT_DEFAULT_CONSENT,
294                 defaultConsent, /* callerName */
295                 "recordMeasurementDefaultConsent");
296     }
297 
298     /** Saves the default AdId state of a user. */
recordDefaultAdIdState(boolean defaultAdIdState)299     public void recordDefaultAdIdState(boolean defaultAdIdState) {
300         setValueWithLock(
301                 DEFAULT_AD_ID_STATE, defaultAdIdState, /* callerName */ "recordDefaultAdIdState");
302     }
303 
304     /** Saves the information whether the user interated manually with the consent. */
recordUserManualInteractionWithConsent(int interaction)305     public void recordUserManualInteractionWithConsent(int interaction) {
306         mReadWriteLock.writeLock().lock();
307         try {
308             switch (interaction) {
309                 case -1:
310                     mDatastore.put(MANUAL_INTERACTION_WITH_CONSENT_RECORDED, false);
311                     break;
312                 case 0:
313                     mDatastore.remove(MANUAL_INTERACTION_WITH_CONSENT_RECORDED);
314                     break;
315                 case 1:
316                     mDatastore.put(MANUAL_INTERACTION_WITH_CONSENT_RECORDED, true);
317                     break;
318                 default:
319                     throw new IllegalArgumentException(
320                             String.format("InteractionId < %d > can not be handled.", interaction));
321             }
322         } catch (IOException e) {
323             LogUtil.e(
324                     e,
325                     "Record manual interaction with consent failed due to IOException thrown"
326                             + " by Datastore: "
327                             + e.getMessage());
328         } finally {
329             mReadWriteLock.writeLock().unlock();
330         }
331     }
332 
333     /** Returns information whether user interacted with consent manually. */
getUserManualInteractionWithConsent()334     public int getUserManualInteractionWithConsent() {
335         mReadWriteLock.readLock().lock();
336         try {
337             Boolean userManualInteractionWithConsent =
338                     mDatastore.get(MANUAL_INTERACTION_WITH_CONSENT_RECORDED);
339             if (userManualInteractionWithConsent == null) {
340                 return 0;
341             } else if (Boolean.TRUE.equals(userManualInteractionWithConsent)) {
342                 return 1;
343             } else {
344                 return -1;
345             }
346         } finally {
347             mReadWriteLock.readLock().unlock();
348         }
349     }
350 
351     /**
352      * Returns the default consent state.
353      *
354      * @return true if default consent is given, otherwise false.
355      */
getDefaultConsent()356     public boolean getDefaultConsent() {
357         return getValueWithLock(DEFAULT_CONSENT);
358     }
359 
360     /**
361      * Returns the topics default consent state.
362      *
363      * @return true if topics default consent is given, otherwise false.
364      */
getTopicsDefaultConsent()365     public boolean getTopicsDefaultConsent() {
366         return getValueWithLock(TOPICS_DEFAULT_CONSENT);
367     }
368 
369 
370     /**
371      * Returns the FLEDGE default consent state.
372      *
373      * @return true if default consent is given, otherwise false.
374      */
getFledgeDefaultConsent()375     public boolean getFledgeDefaultConsent() {
376         return getValueWithLock(FLEDGE_DEFAULT_CONSENT);
377     }
378 
379     /**
380      * Returns the measurement default consent state.
381      *
382      * @return true if default consent is given, otherwise false.
383      */
getMeasurementDefaultConsent()384     public boolean getMeasurementDefaultConsent() {
385         return getValueWithLock(MEASUREMENT_DEFAULT_CONSENT);
386     }
387 
388     /**
389      * Returns the default AdId state when consent notification was sent.
390      *
391      * @return true if AdId is enabled by default, otherwise false.
392      */
getDefaultAdIdState()393     public boolean getDefaultAdIdState() {
394         return getValueWithLock(DEFAULT_AD_ID_STATE);
395     }
396 
397     /** Set the current enabled privacy sandbox feature. */
setCurrentPrivacySandboxFeature(String currentFeatureType)398     public void setCurrentPrivacySandboxFeature(String currentFeatureType) {
399         mReadWriteLock.writeLock().lock();
400         try {
401             for (PrivacySandboxFeatureType featureType : PrivacySandboxFeatureType.values()) {
402                 try {
403                     mDatastore.put(
404                             featureType.name(), currentFeatureType.equals(featureType.name()));
405                 } catch (IOException e) {
406                     LogUtil.e(
407                             "IOException caught while saving privacy sandbox feature."
408                                     + e.getMessage());
409                 }
410             }
411         } finally {
412             mReadWriteLock.writeLock().unlock();
413         }
414     }
415 
416     /** Returns whether a privacy sandbox feature is enabled. */
isPrivacySandboxFeatureEnabled(PrivacySandboxFeatureType featureType)417     public boolean isPrivacySandboxFeatureEnabled(PrivacySandboxFeatureType featureType) {
418         return getValueWithLock(featureType.name());
419     }
420 
421     /**
422      * Deletes the user directory which contains consent information present at
423      * /data/system/adservices/user_id
424      */
deleteUserDirectory(File dir)425     public boolean deleteUserDirectory(File dir) {
426         mReadWriteLock.writeLock().lock();
427         try {
428             boolean success = true;
429             File[] files = dir.listFiles();
430             // files will be null if dir is not a directory
431             if (files != null) {
432                 for (File file : files) {
433                     if (!deleteUserDirectory(file)) {
434                         LogUtil.d("Failed to delete " + file);
435                         success = false;
436                     }
437                 }
438             }
439             return success && dir.delete();
440         } finally {
441             mReadWriteLock.writeLock().unlock();
442         }
443     }
444 
445     @VisibleForTesting
getConsentApiTypeKey(@onsentParcel.ConsentApiType int consentApiType)446     String getConsentApiTypeKey(@ConsentParcel.ConsentApiType int consentApiType) {
447         return CONSENT_API_TYPE_PREFIX + consentApiType;
448     }
449 
450     /** tearDown method used for Testing only. */
451     @VisibleForTesting
tearDownForTesting()452     public void tearDownForTesting() {
453         mReadWriteLock.writeLock().lock();
454         try {
455             mDatastore.tearDownForTesting();
456         } finally {
457             mReadWriteLock.writeLock().unlock();
458         }
459     }
460 
461     @VisibleForTesting static final String IS_AD_ID_ENABLED = "IS_AD_ID_ENABLED";
462 
463     /** Returns whether the isAdIdEnabled bit is true. */
isAdIdEnabled()464     public boolean isAdIdEnabled() {
465         return getValueWithLock(IS_AD_ID_ENABLED);
466     }
467 
468     /** Set the AdIdEnabled bit in system server. */
setAdIdEnabled(boolean isAdIdEnabled)469     public void setAdIdEnabled(boolean isAdIdEnabled) {
470         setValueWithLock(IS_AD_ID_ENABLED, isAdIdEnabled, /* callerName */ "setAdIdEnabled");
471     }
472 
473     @VisibleForTesting static final String IS_U18_ACCOUNT = "IS_U18_ACCOUNT";
474 
475     /** Returns whether the isU18Account bit is true. */
isU18Account()476     public boolean isU18Account() {
477         return getValueWithLock(IS_U18_ACCOUNT);
478     }
479 
480     /** Set the U18Account bit in system server. */
setU18Account(boolean isU18Account)481     public void setU18Account(boolean isU18Account) {
482         setValueWithLock(IS_U18_ACCOUNT, isU18Account, /* callerName */ "setU18Account");
483     }
484 
485     @VisibleForTesting static final String IS_ENTRY_POINT_ENABLED = "IS_ENTRY_POINT_ENABLED";
486 
487     /** Returns whether the isEntryPointEnabled bit is true. */
isEntryPointEnabled()488     public boolean isEntryPointEnabled() {
489         return getValueWithLock(IS_ENTRY_POINT_ENABLED);
490     }
491 
492     /** Set the EntryPointEnabled bit in system server. */
setEntryPointEnabled(boolean isEntryPointEnabled)493     public void setEntryPointEnabled(boolean isEntryPointEnabled) {
494         setValueWithLock(
495                 IS_ENTRY_POINT_ENABLED,
496                 isEntryPointEnabled, /* callerName */
497                 "setEntryPointEnabled");
498     }
499 
500     @VisibleForTesting static final String IS_ADULT_ACCOUNT = "IS_ADULT_ACCOUNT";
501 
502     /** Returns whether the isAdultAccount bit is true. */
isAdultAccount()503     public boolean isAdultAccount() {
504         return getValueWithLock(IS_ADULT_ACCOUNT);
505     }
506 
507     /** Set the AdultAccount bit in system server. */
setAdultAccount(boolean isAdultAccount)508     public void setAdultAccount(boolean isAdultAccount) {
509         setValueWithLock(IS_ADULT_ACCOUNT, isAdultAccount, /* callerName */ "setAdultAccount");
510     }
511 
512     @VisibleForTesting
513     static final String WAS_U18_NOTIFICATION_DISPLAYED = "WAS_U18_NOTIFICATION_DISPLAYED";
514 
515     /** Returns whether the wasU18NotificationDisplayed bit is true. */
wasU18NotificationDisplayed()516     public boolean wasU18NotificationDisplayed() {
517         return getValueWithLock(WAS_U18_NOTIFICATION_DISPLAYED);
518     }
519 
520     /** Set the U18NotificationDisplayed bit in system server. */
setU18NotificationDisplayed(boolean wasU18NotificationDisplayed)521     public void setU18NotificationDisplayed(boolean wasU18NotificationDisplayed)
522             throws IOException {
523         setValueWithLock(
524                 WAS_U18_NOTIFICATION_DISPLAYED,
525                 wasU18NotificationDisplayed,
526                 /* callerName */ "setU18NotificationDisplayed");
527     }
528 
529     /** Set the current enabled privacy sux. */
setUx(String eligibleUx)530     public void setUx(String eligibleUx) {
531         mReadWriteLock.writeLock().lock();
532         try {
533             Stream.of(PrivacySandboxUxCollection.values())
534                     .forEach(
535                             ux -> {
536                                 try {
537                                     mDatastore.put(ux.toString(), ux.toString().equals(eligibleUx));
538                                 } catch (IOException e) {
539                                     LogUtil.e(
540                                             "IOException caught while setting the current UX."
541                                                     + e.getMessage());
542                                 }
543                             });
544         } finally {
545             mReadWriteLock.writeLock().unlock();
546         }
547     }
548 
549     /** Returns the current UX. */
getUx()550     public String getUx() {
551         mReadWriteLock.readLock().lock();
552         try {
553             return Stream.of(PrivacySandboxUxCollection.values())
554                     .filter(ux -> Boolean.TRUE.equals(mDatastore.get(ux.toString())))
555                     .findFirst()
556                     .orElse(PrivacySandboxUxCollection.UNSUPPORTED_UX)
557                     .toString();
558         } finally {
559             mReadWriteLock.readLock().unlock();
560         }
561     }
562 
563     /** Set the current enrollment channel. */
setEnrollmentChannel(String enrollmentChannel)564     public void setEnrollmentChannel(String enrollmentChannel) {
565         mReadWriteLock.writeLock().lock();
566         try {
567             Stream.of(PrivacySandboxEnrollmentChannelCollection.values())
568                     .forEach(
569                             channel -> {
570                                 try {
571                                     mDatastore.put(
572                                             channel.toString(),
573                                             channel.toString().equals(enrollmentChannel));
574                                 } catch (IOException e) {
575                                     LogUtil.e(
576                                             "IOException caught while setting the current "
577                                                     + "enrollment channel."
578                                                     + e.getMessage());
579                                 }
580                             });
581         } finally {
582             mReadWriteLock.writeLock().unlock();
583         }
584     }
585 
586     /** Returns the current enrollment channel. */
getEnrollmentChannel()587     public String getEnrollmentChannel() {
588         mReadWriteLock.readLock().lock();
589         try {
590             PrivacySandboxEnrollmentChannelCollection enrollmentChannel =
591                     Stream.of(PrivacySandboxEnrollmentChannelCollection.values())
592                             .filter(
593                                     channel ->
594                                             Boolean.TRUE.equals(mDatastore.get(channel.toString())))
595                             .findFirst()
596                             .orElse(null);
597             if (enrollmentChannel != null) {
598                 return enrollmentChannel.toString();
599             }
600         } finally {
601             mReadWriteLock.readLock().unlock();
602         }
603         return null;
604     }
605 
606     @VisibleForTesting static final String IS_MEASUREMENT_DATA_RESET = "IS_MEASUREMENT_DATA_RESET";
607 
608     /** Returns whether the isMeasurementDataReset bit is true. */
isMeasurementDataReset()609     public boolean isMeasurementDataReset() {
610         return getValueWithLock(IS_MEASUREMENT_DATA_RESET);
611     }
612 
613     /** Set the isMeasurementDataReset bit in system server. */
setMeasurementDataReset(boolean isMeasurementDataReset)614     public void setMeasurementDataReset(boolean isMeasurementDataReset) throws IOException {
615         setValueWithLock(
616                 IS_MEASUREMENT_DATA_RESET,
617                 isMeasurementDataReset,
618                 /* callerName */ "isMeasurementDataReset");
619     }
620 
621     @VisibleForTesting static final String IS_PA_DATA_RESET = "IS_Pa_DATA_RESET";
622 
623     /** Returns whether the isPaDataReset bit is true. */
isPaDataReset()624     public boolean isPaDataReset() {
625         return getValueWithLock(IS_PA_DATA_RESET);
626     }
627 
628     /** Set the isPaDataReset bit in system server. */
setPaDataReset(boolean isPaDataReset)629     public void setPaDataReset(boolean isPaDataReset) throws IOException {
630         setValueWithLock(IS_PA_DATA_RESET, isPaDataReset, /* callerName */ "isPaDataReset");
631     }
632 
getValueWithLock(String key)633     private boolean getValueWithLock(String key) {
634         mReadWriteLock.readLock().lock();
635         try {
636             Boolean value = mDatastore.get(key);
637             return value != null ? value : false;
638         } finally {
639             mReadWriteLock.readLock().unlock();
640         }
641     }
642 
643     /**
644      * Sets the boolean value to the Datastore, using read write to make sure it is thread safe.
645      *
646      * @param key for the datastore
647      * @param value boolean value to set
648      * @param callerName the function name who call this setValueWithLock
649      */
setValueWithLock(String key, Boolean value, String callerName)650     private void setValueWithLock(String key, Boolean value, String callerName) {
651         mReadWriteLock.writeLock().lock();
652         try {
653             mDatastore.put(key, value);
654         } catch (IOException e) {
655             LogUtil.e(
656                     e,
657                     callerName
658                             + " operation failed due to IOException thrown by Datastore: "
659                             + e.getMessage());
660         } finally {
661             mReadWriteLock.writeLock().unlock();
662         }
663     }
664 
665     /** Dumps its internal state. */
dump(PrintWriter writer, String prefix)666     public void dump(PrintWriter writer, String prefix) {
667         writer.printf("%sConsentManager:\n", prefix);
668         String prefix2 = prefix + "  ";
669 
670         mDatastore.dump(writer, prefix2);
671     }
672 }
673