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.measurement; 18 19 import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR; 20 import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT; 21 import static android.adservices.common.AdServicesStatusUtils.STATUS_IO_ERROR; 22 import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS; 23 24 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_WIPEOUT; 25 26 import android.adservices.adid.AdId; 27 import android.adservices.common.AdServicesStatusUtils; 28 import android.adservices.measurement.DeletionParam; 29 import android.adservices.measurement.RegistrationRequest; 30 import android.adservices.measurement.SourceRegistrationRequestInternal; 31 import android.adservices.measurement.WebSourceRegistrationRequest; 32 import android.adservices.measurement.WebSourceRegistrationRequestInternal; 33 import android.adservices.measurement.WebTriggerRegistrationRequest; 34 import android.adservices.measurement.WebTriggerRegistrationRequestInternal; 35 import android.annotation.NonNull; 36 import android.annotation.Nullable; 37 import android.annotation.WorkerThread; 38 import android.app.adservices.AdServicesManager; 39 import android.content.ComponentName; 40 import android.content.ContentResolver; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.net.Uri; 44 import android.os.Build; 45 import android.os.SystemClock; 46 import android.view.InputEvent; 47 48 import androidx.annotation.RequiresApi; 49 50 import com.android.adservices.LoggerFactory; 51 import com.android.adservices.data.measurement.DatastoreManager; 52 import com.android.adservices.data.measurement.DatastoreManagerFactory; 53 import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter; 54 import com.android.adservices.service.Flags; 55 import com.android.adservices.service.FlagsFactory; 56 import com.android.adservices.service.common.WebAddresses; 57 import com.android.adservices.service.measurement.inputverification.ClickVerifier; 58 import com.android.adservices.service.measurement.registration.EnqueueAsyncRegistration; 59 import com.android.adservices.service.measurement.rollback.MeasurementRollbackCompatManager; 60 import com.android.adservices.service.measurement.util.Applications; 61 import com.android.adservices.service.stats.AdServicesLoggerImpl; 62 import com.android.adservices.service.stats.MeasurementWipeoutStats; 63 import com.android.internal.annotations.VisibleForTesting; 64 import com.android.modules.utils.build.SdkLevel; 65 66 import java.net.URISyntaxException; 67 import java.util.Collections; 68 import java.util.List; 69 import java.util.Objects; 70 import java.util.Optional; 71 import java.util.concurrent.locks.ReadWriteLock; 72 import java.util.concurrent.locks.ReentrantReadWriteLock; 73 74 import javax.annotation.concurrent.ThreadSafe; 75 76 /** 77 * This class is thread safe. 78 * 79 * @hide 80 */ 81 // TODO(b/269798827): Enable for R. 82 @RequiresApi(Build.VERSION_CODES.S) 83 @ThreadSafe 84 @WorkerThread 85 public final class MeasurementImpl { 86 private static final String ANDROID_APP_SCHEME = "android-app"; 87 private static volatile MeasurementImpl sMeasurementImpl; 88 private final Context mContext; 89 private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); 90 private final DatastoreManager mDatastoreManager; 91 private final ContentResolver mContentResolver; 92 private final ClickVerifier mClickVerifier; 93 private final MeasurementDataDeleter mMeasurementDataDeleter; 94 private final Flags mFlags; 95 96 @VisibleForTesting MeasurementImpl(Context context)97 MeasurementImpl(Context context) { 98 mContext = context; 99 mDatastoreManager = DatastoreManagerFactory.getDatastoreManager(context); 100 mClickVerifier = new ClickVerifier(context); 101 mFlags = FlagsFactory.getFlags(); 102 mMeasurementDataDeleter = new MeasurementDataDeleter(mDatastoreManager, mFlags); 103 mContentResolver = mContext.getContentResolver(); 104 deleteOnRollback(); 105 } 106 107 @VisibleForTesting MeasurementImpl( Context context, Flags flags, DatastoreManager datastoreManager, ClickVerifier clickVerifier, MeasurementDataDeleter measurementDataDeleter, ContentResolver contentResolver)108 public MeasurementImpl( 109 Context context, 110 Flags flags, 111 DatastoreManager datastoreManager, 112 ClickVerifier clickVerifier, 113 MeasurementDataDeleter measurementDataDeleter, 114 ContentResolver contentResolver) { 115 mContext = context; 116 mDatastoreManager = datastoreManager; 117 mClickVerifier = clickVerifier; 118 mMeasurementDataDeleter = measurementDataDeleter; 119 mFlags = flags; 120 mContentResolver = contentResolver; 121 } 122 123 /** 124 * Gets an instance of MeasurementImpl to be used. 125 * 126 * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the 127 * existing instance will be returned. 128 */ 129 @NonNull getInstance(Context context)130 public static MeasurementImpl getInstance(Context context) { 131 if (sMeasurementImpl == null) { 132 synchronized (MeasurementImpl.class) { 133 if (sMeasurementImpl == null) { 134 sMeasurementImpl = new MeasurementImpl(context); 135 } 136 } 137 } 138 return sMeasurementImpl; 139 } 140 141 /** 142 * Invoked when a package is installed. 143 * 144 * @param packageUri installed package {@link Uri}. 145 * @param eventTime time when the package was installed. 146 */ doInstallAttribution(@onNull Uri packageUri, long eventTime)147 public void doInstallAttribution(@NonNull Uri packageUri, long eventTime) { 148 LoggerFactory.getMeasurementLogger().d("Attributing installation for: " + packageUri); 149 Uri appUri = getAppUri(packageUri); 150 mReadWriteLock.readLock().lock(); 151 try { 152 mDatastoreManager.runInTransaction( 153 (dao) -> dao.doInstallAttribution(appUri, eventTime)); 154 } finally { 155 mReadWriteLock.readLock().unlock(); 156 } 157 } 158 159 /** Implement a registration request, returning a {@link AdServicesStatusUtils.StatusCode}. */ 160 @AdServicesStatusUtils.StatusCode register(@onNull RegistrationRequest request, boolean adIdPermission, long requestTime)161 int register(@NonNull RegistrationRequest request, boolean adIdPermission, long requestTime) { 162 mReadWriteLock.readLock().lock(); 163 try { 164 switch (request.getRegistrationType()) { 165 case RegistrationRequest.REGISTER_SOURCE: 166 case RegistrationRequest.REGISTER_TRIGGER: 167 return EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest( 168 request, 169 adIdPermission, 170 getRegistrant(request.getAppPackageName()), 171 requestTime, 172 request.getRegistrationType() 173 == RegistrationRequest.REGISTER_TRIGGER 174 ? null 175 : getSourceType( 176 request.getInputEvent(), 177 request.getRequestTime(), 178 request.getAppPackageName()), 179 /* postBody */ null, 180 mDatastoreManager, 181 mContentResolver) 182 ? STATUS_SUCCESS 183 : STATUS_IO_ERROR; 184 185 default: 186 return STATUS_INVALID_ARGUMENT; 187 } 188 } finally { 189 mReadWriteLock.readLock().unlock(); 190 } 191 } 192 193 /** 194 * Implement a sources registration request, returning a {@link 195 * AdServicesStatusUtils.StatusCode}. 196 */ 197 @AdServicesStatusUtils.StatusCode registerSources(@onNull SourceRegistrationRequestInternal request, long requestTime)198 int registerSources(@NonNull SourceRegistrationRequestInternal request, long requestTime) { 199 mReadWriteLock.readLock().lock(); 200 try { 201 return EnqueueAsyncRegistration.appSourcesRegistrationRequest( 202 request, 203 isAdIdPermissionGranted(request.getAdIdValue()), 204 getRegistrant(request.getAppPackageName()), 205 requestTime, 206 getSourceType( 207 request.getSourceRegistrationRequest().getInputEvent(), 208 request.getBootRelativeRequestTime(), 209 request.getAppPackageName()), 210 /* postBody*/ null, 211 mDatastoreManager, 212 mContentResolver) 213 ? STATUS_SUCCESS 214 : STATUS_IO_ERROR; 215 } finally { 216 mReadWriteLock.readLock().unlock(); 217 } 218 } 219 220 /** 221 * Processes a source registration request delegated to OS from the caller, e.g. Chrome, 222 * returning a status code. 223 */ registerWebSource( @onNull WebSourceRegistrationRequestInternal request, boolean adIdPermission, long requestTime)224 int registerWebSource( 225 @NonNull WebSourceRegistrationRequestInternal request, 226 boolean adIdPermission, 227 long requestTime) { 228 WebSourceRegistrationRequest sourceRegistrationRequest = 229 request.getSourceRegistrationRequest(); 230 if (!isValid(sourceRegistrationRequest)) { 231 LoggerFactory.getMeasurementLogger().e("registerWebSource received invalid parameters"); 232 return STATUS_INVALID_ARGUMENT; 233 } 234 mReadWriteLock.readLock().lock(); 235 try { 236 boolean enqueueStatus = 237 EnqueueAsyncRegistration.webSourceRegistrationRequest( 238 sourceRegistrationRequest, 239 adIdPermission, 240 getRegistrant(request.getAppPackageName()), 241 requestTime, 242 getSourceType( 243 sourceRegistrationRequest.getInputEvent(), 244 request.getRequestTime(), 245 request.getAppPackageName()), 246 mDatastoreManager, 247 mContentResolver); 248 if (enqueueStatus) { 249 return STATUS_SUCCESS; 250 } else { 251 252 return STATUS_IO_ERROR; 253 } 254 } finally { 255 mReadWriteLock.readLock().unlock(); 256 } 257 } 258 259 /** 260 * Processes a trigger registration request delegated to OS from the caller, e.g. Chrome, 261 * returning a status code. 262 */ registerWebTrigger( @onNull WebTriggerRegistrationRequestInternal request, boolean adIdPermission, long requestTime)263 int registerWebTrigger( 264 @NonNull WebTriggerRegistrationRequestInternal request, 265 boolean adIdPermission, 266 long requestTime) { 267 WebTriggerRegistrationRequest triggerRegistrationRequest = 268 request.getTriggerRegistrationRequest(); 269 if (!isValid(triggerRegistrationRequest)) { 270 LoggerFactory.getMeasurementLogger() 271 .e("registerWebTrigger received invalid parameters"); 272 return STATUS_INVALID_ARGUMENT; 273 } 274 mReadWriteLock.readLock().lock(); 275 try { 276 boolean enqueueStatus = 277 EnqueueAsyncRegistration.webTriggerRegistrationRequest( 278 triggerRegistrationRequest, 279 adIdPermission, 280 getRegistrant(request.getAppPackageName()), 281 requestTime, 282 mDatastoreManager, 283 mContentResolver); 284 if (enqueueStatus) { 285 return STATUS_SUCCESS; 286 } else { 287 288 return STATUS_IO_ERROR; 289 } 290 } finally { 291 mReadWriteLock.readLock().unlock(); 292 } 293 } 294 295 /** Implement a source registration request from a report event */ registerEvent( @onNull Uri registrationUri, @NonNull String appPackageName, @NonNull String sdkPackageName, boolean isAdIdEnabled, @Nullable String postBody, @Nullable InputEvent inputEvent, @Nullable String adIdValue)296 public int registerEvent( 297 @NonNull Uri registrationUri, 298 @NonNull String appPackageName, 299 @NonNull String sdkPackageName, 300 boolean isAdIdEnabled, 301 @Nullable String postBody, 302 @Nullable InputEvent inputEvent, 303 @Nullable String adIdValue) { 304 Objects.requireNonNull(registrationUri); 305 Objects.requireNonNull(appPackageName); 306 Objects.requireNonNull(sdkPackageName); 307 308 final long apiRequestTime = System.currentTimeMillis(); 309 final RegistrationRequest.Builder builder = 310 new RegistrationRequest.Builder( 311 RegistrationRequest.REGISTER_SOURCE, 312 registrationUri, 313 appPackageName, 314 sdkPackageName) 315 .setAdIdPermissionGranted(isAdIdEnabled) 316 .setRequestTime(SystemClock.uptimeMillis()) 317 .setAdIdValue(adIdValue); 318 RegistrationRequest request = builder.build(); 319 320 mReadWriteLock.readLock().lock(); 321 try { 322 return EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest( 323 request, 324 request.isAdIdPermissionGranted(), 325 registrationUri, 326 apiRequestTime, 327 getSourceType( 328 inputEvent, 329 request.getRequestTime(), 330 request.getAppPackageName()), 331 postBody, 332 mDatastoreManager, 333 mContentResolver) 334 ? STATUS_SUCCESS 335 : STATUS_IO_ERROR; 336 } finally { 337 mReadWriteLock.readLock().unlock(); 338 } 339 } 340 341 /** 342 * Implement a deleteRegistrations request, returning a r{@link 343 * AdServicesStatusUtils.StatusCode}. 344 */ 345 @AdServicesStatusUtils.StatusCode deleteRegistrations(@onNull DeletionParam request)346 int deleteRegistrations(@NonNull DeletionParam request) { 347 mReadWriteLock.readLock().lock(); 348 try { 349 boolean deleteResult = mMeasurementDataDeleter.delete(request); 350 if (deleteResult) { 351 markDeletion(); 352 } 353 return deleteResult ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR; 354 } catch (NullPointerException | IllegalArgumentException e) { 355 LoggerFactory.getMeasurementLogger() 356 .e(e, "Delete registration received invalid parameters"); 357 return STATUS_INVALID_ARGUMENT; 358 } finally { 359 mReadWriteLock.readLock().unlock(); 360 } 361 } 362 363 /** 364 * Delete all records from a specific package and return a boolean value to indicate whether any 365 * data was deleted. 366 */ deletePackageRecords(Uri packageUri)367 public boolean deletePackageRecords(Uri packageUri) { 368 Uri appUri = getAppUri(packageUri); 369 LoggerFactory.getMeasurementLogger().d("Deleting records for " + appUri); 370 mReadWriteLock.writeLock().lock(); 371 boolean didDeletionOccur = false; 372 try { 373 didDeletionOccur = mMeasurementDataDeleter.deleteAppUninstalledData(appUri); 374 if (didDeletionOccur) { 375 markDeletion(); 376 } 377 } catch (NullPointerException | IllegalArgumentException e) { 378 LoggerFactory.getMeasurementLogger() 379 .e(e, "Delete package records received invalid parameters"); 380 } finally { 381 mReadWriteLock.writeLock().unlock(); 382 } 383 return didDeletionOccur; 384 } 385 386 /** 387 * Delete all data generated by Measurement API, except for tables in the exclusion list. 388 * 389 * @param tablesToExclude a {@link List} of tables that won't be deleted. 390 */ deleteAllMeasurementData(@onNull List<String> tablesToExclude)391 public void deleteAllMeasurementData(@NonNull List<String> tablesToExclude) { 392 mReadWriteLock.writeLock().lock(); 393 try { 394 mDatastoreManager.runInTransaction( 395 (dao) -> dao.deleteAllMeasurementData(tablesToExclude)); 396 LoggerFactory.getMeasurementLogger() 397 .v( 398 "All data is cleared for Measurement API except: %s", 399 tablesToExclude.toString()); 400 markDeletion(); 401 } finally { 402 mReadWriteLock.writeLock().unlock(); 403 } 404 } 405 406 /** Delete all data generated from apps that are not currently installed. */ deleteAllUninstalledMeasurementData()407 public void deleteAllUninstalledMeasurementData() { 408 final List<Uri> installedAppList = 409 Applications.getCurrentInstalledApplicationsList(mContext); 410 411 final Optional<List<Uri>> uninstalledAppsOpt = 412 mDatastoreManager.runInTransactionWithResult( 413 (dao) -> dao.getUninstalledAppNamesHavingMeasurementData(installedAppList)); 414 415 if (uninstalledAppsOpt.isPresent()) { 416 for (Uri uninstalledAppName : uninstalledAppsOpt.get()) { 417 deletePackageRecords(uninstalledAppName); 418 } 419 } 420 } 421 isAdIdPermissionGranted(@ullable String adIdValue)422 private static boolean isAdIdPermissionGranted(@Nullable String adIdValue) { 423 return adIdValue != null && !adIdValue.isEmpty() && !AdId.ZERO_OUT.equals(adIdValue); 424 } 425 426 @VisibleForTesting getSourceType( InputEvent inputEvent, long requestTime, String sourceRegistrant)427 Source.SourceType getSourceType( 428 InputEvent inputEvent, long requestTime, String sourceRegistrant) { 429 // If click verification is enabled and the InputEvent is not null, but it cannot be 430 // verified, then the SourceType is demoted to EVENT. 431 if (mFlags.getMeasurementIsClickVerificationEnabled() 432 && inputEvent != null 433 && !mClickVerifier.isInputEventVerifiable( 434 inputEvent, requestTime, sourceRegistrant)) { 435 return Source.SourceType.EVENT; 436 } else { 437 return inputEvent == null ? Source.SourceType.EVENT : Source.SourceType.NAVIGATION; 438 } 439 } 440 getRegistrant(String packageName)441 private Uri getRegistrant(String packageName) { 442 return Uri.parse(ANDROID_APP_SCHEME + "://" + packageName); 443 } 444 getAppUri(Uri packageUri)445 private Uri getAppUri(Uri packageUri) { 446 return packageUri.getScheme() == null 447 ? Uri.parse(ANDROID_APP_SCHEME + "://" + packageUri.getEncodedSchemeSpecificPart()) 448 : packageUri; 449 } 450 isValid(WebSourceRegistrationRequest sourceRegistrationRequest)451 private boolean isValid(WebSourceRegistrationRequest sourceRegistrationRequest) { 452 Uri verifiedDestination = sourceRegistrationRequest.getVerifiedDestination(); 453 Uri webDestination = sourceRegistrationRequest.getWebDestination(); 454 455 if (verifiedDestination == null) { 456 return webDestination == null 457 ? true 458 : WebAddresses.topPrivateDomainAndScheme(webDestination).isPresent(); 459 } 460 461 return isVerifiedDestination( 462 verifiedDestination, webDestination, sourceRegistrationRequest.getAppDestination()); 463 } 464 isVerifiedDestination( Uri verifiedDestination, Uri webDestination, Uri appDestination)465 private boolean isVerifiedDestination( 466 Uri verifiedDestination, Uri webDestination, Uri appDestination) { 467 String destinationPackage = null; 468 if (appDestination != null) { 469 destinationPackage = appDestination.getHost(); 470 } 471 String verifiedScheme = verifiedDestination.getScheme(); 472 String verifiedHost = verifiedDestination.getHost(); 473 474 // Verified destination matches appDestination value 475 if (destinationPackage != null 476 && verifiedHost != null 477 && (verifiedScheme == null || verifiedScheme.equals(ANDROID_APP_SCHEME)) 478 && verifiedHost.equals(destinationPackage)) { 479 return true; 480 } 481 482 try { 483 Intent intent = Intent.parseUri(verifiedDestination.toString(), 0); 484 ComponentName componentName = intent.resolveActivity(mContext.getPackageManager()); 485 if (componentName == null) { 486 return false; 487 } 488 489 // (ComponentName::getPackageName cannot be null) 490 String verifiedPackage = componentName.getPackageName(); 491 492 // Try to match an app vendor store and extract a target package 493 if (destinationPackage != null 494 && verifiedPackage.equals(AppVendorPackages.PLAY_STORE)) { 495 String targetPackage = getTargetPackageFromPlayStoreUri(verifiedDestination); 496 return targetPackage != null && targetPackage.equals(destinationPackage); 497 498 // Try to match web destination 499 } else if (webDestination == null) { 500 return false; 501 } else { 502 Optional<Uri> webDestinationTopPrivateDomainAndScheme = 503 WebAddresses.topPrivateDomainAndScheme(webDestination); 504 Optional<Uri> verifiedDestinationTopPrivateDomainAndScheme = 505 WebAddresses.topPrivateDomainAndScheme(verifiedDestination); 506 return webDestinationTopPrivateDomainAndScheme.isPresent() 507 && verifiedDestinationTopPrivateDomainAndScheme.isPresent() 508 && webDestinationTopPrivateDomainAndScheme.get().equals( 509 verifiedDestinationTopPrivateDomainAndScheme.get()); 510 } 511 } catch (URISyntaxException e) { 512 LoggerFactory.getMeasurementLogger() 513 .e( 514 e, 515 "MeasurementImpl::handleVerifiedDestination: failed to parse intent" 516 + " URI: %s", 517 verifiedDestination.toString()); 518 return false; 519 } 520 } 521 isValid(WebTriggerRegistrationRequest triggerRegistrationRequest)522 private static boolean isValid(WebTriggerRegistrationRequest triggerRegistrationRequest) { 523 Uri destination = triggerRegistrationRequest.getDestination(); 524 return WebAddresses.topPrivateDomainAndScheme(destination).isPresent(); 525 } 526 getTargetPackageFromPlayStoreUri(Uri uri)527 private static String getTargetPackageFromPlayStoreUri(Uri uri) { 528 return uri.getQueryParameter("id"); 529 } 530 531 private interface AppVendorPackages { 532 String PLAY_STORE = "com.android.vending"; 533 } 534 535 /** 536 * Checks if the module was rollback and if there was a deletion in the version rolled back 537 * from. If there was, delete all measurement data to prioritize user privacy. 538 */ deleteOnRollback()539 private void deleteOnRollback() { 540 if (FlagsFactory.getFlags().getMeasurementRollbackDeletionKillSwitch()) { 541 LoggerFactory.getMeasurementLogger() 542 .e("Rollback deletion is disabled. Not checking system server for rollback."); 543 return; 544 } 545 546 LoggerFactory.getMeasurementLogger().d("Checking rollback status."); 547 boolean needsToHandleRollbackReconciliation = checkIfNeedsToHandleReconciliation(); 548 if (needsToHandleRollbackReconciliation) { 549 LoggerFactory.getMeasurementLogger() 550 .d("Rollback and deletion detected, deleting all measurement data."); 551 mReadWriteLock.writeLock().lock(); 552 boolean success; 553 try { 554 success = 555 mDatastoreManager.runInTransaction( 556 (dao) -> dao.deleteAllMeasurementData(Collections.emptyList())); 557 } finally { 558 mReadWriteLock.writeLock().unlock(); 559 } 560 if (success) { 561 AdServicesLoggerImpl.getInstance() 562 .logMeasurementWipeoutStats( 563 new MeasurementWipeoutStats.Builder() 564 .setCode(AD_SERVICES_MEASUREMENT_WIPEOUT) 565 .setWipeoutType( 566 WipeoutStatus.WipeoutType.ROLLBACK_WIPEOUT_CAUSE 567 .getValue()) 568 .setSourceRegistrant("") 569 .build()); 570 } 571 } 572 } 573 574 @VisibleForTesting checkIfNeedsToHandleReconciliation()575 boolean checkIfNeedsToHandleReconciliation() { 576 if (SdkLevel.isAtLeastT()) { 577 return AdServicesManager.getInstance(mContext) 578 .needsToHandleRollbackReconciliation(AdServicesManager.MEASUREMENT_DELETION); 579 } 580 581 // Not on Android T+. Check if flag is enabled if on R/S. 582 if (isMeasurementRollbackCompatDisabled()) { 583 LoggerFactory.getMeasurementLogger() 584 .e("Rollback deletion disabled. Not checking compatible store for rollback."); 585 return false; 586 } 587 588 return MeasurementRollbackCompatManager.getInstance( 589 mContext, AdServicesManager.MEASUREMENT_DELETION) 590 .needsToHandleRollbackReconciliation(); 591 } 592 593 /** 594 * Stores a bit in the system server indicating that a deletion happened for the current 595 * AdServices module version. This information is used for deleting data after it has been 596 * restored by a module rollback. 597 */ markDeletion()598 private void markDeletion() { 599 if (FlagsFactory.getFlags().getMeasurementRollbackDeletionKillSwitch()) { 600 LoggerFactory.getMeasurementLogger() 601 .e("Rollback deletion is disabled. Not storing status in system server."); 602 return; 603 } 604 605 if (SdkLevel.isAtLeastT()) { 606 LoggerFactory.getMeasurementLogger().d("Marking deletion in system server."); 607 AdServicesManager.getInstance(mContext) 608 .recordAdServicesDeletionOccurred(AdServicesManager.MEASUREMENT_DELETION); 609 return; 610 } 611 612 // If on Android R/S, check if the appropriate flag is enabled, otherwise do nothing. 613 if (isMeasurementRollbackCompatDisabled()) { 614 LoggerFactory.getMeasurementLogger() 615 .e("Rollback deletion disabled. Not storing status in compatible store."); 616 return; 617 } 618 619 MeasurementRollbackCompatManager.getInstance( 620 mContext, AdServicesManager.MEASUREMENT_DELETION) 621 .recordAdServicesDeletionOccurred(); 622 } 623 isMeasurementRollbackCompatDisabled()624 private boolean isMeasurementRollbackCompatDisabled() { 625 if (SdkLevel.isAtLeastT()) { 626 // This method should never be called on T+. 627 return true; 628 } 629 630 Flags flags = FlagsFactory.getFlags(); 631 return SdkLevel.isAtLeastS() 632 ? flags.getMeasurementRollbackDeletionAppSearchKillSwitch() 633 : !flags.getMeasurementRollbackDeletionREnabled(); 634 } 635 } 636