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