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