• 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 android.adservices.common.AdServicesStatusUtils;
25 import android.adservices.measurement.DeletionParam;
26 import android.adservices.measurement.RegistrationRequest;
27 import android.adservices.measurement.WebSourceRegistrationRequest;
28 import android.adservices.measurement.WebSourceRegistrationRequestInternal;
29 import android.adservices.measurement.WebTriggerRegistrationRequest;
30 import android.adservices.measurement.WebTriggerRegistrationRequestInternal;
31 import android.annotation.NonNull;
32 import android.annotation.WorkerThread;
33 import android.app.adservices.AdServicesManager;
34 import android.content.ComponentName;
35 import android.content.ContentResolver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.pm.ApplicationInfo;
39 import android.content.pm.PackageManager;
40 import android.net.Uri;
41 import android.os.Build;
42 import android.view.InputEvent;
43 
44 import androidx.annotation.RequiresApi;
45 
46 import com.android.adservices.LogUtil;
47 import com.android.adservices.data.measurement.DatastoreManager;
48 import com.android.adservices.data.measurement.DatastoreManagerFactory;
49 import com.android.adservices.data.measurement.deletion.MeasurementDataDeleter;
50 import com.android.adservices.service.Flags;
51 import com.android.adservices.service.FlagsFactory;
52 import com.android.adservices.service.appsearch.AppSearchMeasurementRollbackManager;
53 import com.android.adservices.service.common.compat.PackageManagerCompatUtils;
54 import com.android.adservices.service.measurement.inputverification.ClickVerifier;
55 import com.android.adservices.service.measurement.registration.EnqueueAsyncRegistration;
56 import com.android.adservices.service.measurement.util.Web;
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.modules.utils.build.SdkLevel;
59 
60 import java.net.URISyntaxException;
61 import java.util.Collections;
62 import java.util.List;
63 import java.util.Optional;
64 import java.util.concurrent.locks.ReadWriteLock;
65 import java.util.concurrent.locks.ReentrantReadWriteLock;
66 import java.util.stream.Collectors;
67 
68 import javax.annotation.concurrent.ThreadSafe;
69 
70 /**
71  * This class is thread safe.
72  *
73  * @hide
74  */
75 // TODO(b/269798827): Enable for R.
76 @RequiresApi(Build.VERSION_CODES.S)
77 @ThreadSafe
78 @WorkerThread
79 public final class MeasurementImpl {
80     private static final String ANDROID_APP_SCHEME = "android-app";
81     private static volatile MeasurementImpl sMeasurementImpl;
82     private final Context mContext;
83     private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
84     private final DatastoreManager mDatastoreManager;
85     private final ContentResolver mContentResolver;
86     private final ClickVerifier mClickVerifier;
87     private final MeasurementDataDeleter mMeasurementDataDeleter;
88     private final Flags mFlags;
89 
90     @VisibleForTesting
MeasurementImpl(Context context)91     MeasurementImpl(Context context) {
92         mContext = context;
93         mDatastoreManager = DatastoreManagerFactory.getDatastoreManager(context);
94         mClickVerifier = new ClickVerifier(context);
95         mFlags = FlagsFactory.getFlags();
96         mMeasurementDataDeleter = new MeasurementDataDeleter(mDatastoreManager);
97         mContentResolver = mContext.getContentResolver();
98         deleteOnRollback();
99     }
100 
101     @VisibleForTesting
MeasurementImpl( Context context, DatastoreManager datastoreManager, ClickVerifier clickVerifier, MeasurementDataDeleter measurementDataDeleter, ContentResolver contentResolver)102     MeasurementImpl(
103             Context context,
104             DatastoreManager datastoreManager,
105             ClickVerifier clickVerifier,
106             MeasurementDataDeleter measurementDataDeleter,
107             ContentResolver contentResolver) {
108         mContext = context;
109         mDatastoreManager = datastoreManager;
110         mClickVerifier = clickVerifier;
111         mMeasurementDataDeleter = measurementDataDeleter;
112         mFlags = FlagsFactory.getFlagsForTest();
113         mContentResolver = contentResolver;
114     }
115 
116     /**
117      * Gets an instance of MeasurementImpl to be used.
118      *
119      * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
120      * existing instance will be returned.
121      */
122     @NonNull
getInstance(Context context)123     public static MeasurementImpl getInstance(Context context) {
124         if (sMeasurementImpl == null) {
125             synchronized (MeasurementImpl.class) {
126                 if (sMeasurementImpl == null) {
127                     sMeasurementImpl = new MeasurementImpl(context);
128                 }
129             }
130         }
131         return sMeasurementImpl;
132     }
133 
134     /**
135      * Invoked when a package is installed.
136      *
137      * @param packageUri installed package {@link Uri}.
138      * @param eventTime  time when the package was installed.
139      */
doInstallAttribution(@onNull Uri packageUri, long eventTime)140     public void doInstallAttribution(@NonNull Uri packageUri, long eventTime) {
141         LogUtil.d("Attributing installation for: " + packageUri);
142         Uri appUri = getAppUri(packageUri);
143         mReadWriteLock.readLock().lock();
144         try {
145             mDatastoreManager.runInTransaction(
146                     (dao) -> dao.doInstallAttribution(appUri, eventTime));
147         } finally {
148             mReadWriteLock.readLock().unlock();
149         }
150     }
151 
152     /** Implement a registration request, returning a {@link AdServicesStatusUtils.StatusCode}. */
153     @AdServicesStatusUtils.StatusCode
register(@onNull RegistrationRequest request, boolean adIdPermission, long requestTime)154     int register(@NonNull RegistrationRequest request, boolean adIdPermission, long requestTime) {
155         mReadWriteLock.readLock().lock();
156         try {
157             switch (request.getRegistrationType()) {
158                 case RegistrationRequest.REGISTER_SOURCE:
159                 case RegistrationRequest.REGISTER_TRIGGER:
160                     return EnqueueAsyncRegistration.appSourceOrTriggerRegistrationRequest(
161                                     request,
162                                     adIdPermission,
163                                     getRegistrant(request.getAppPackageName()),
164                                     requestTime,
165                                     request.getRegistrationType()
166                                                     == RegistrationRequest.REGISTER_TRIGGER
167                                             ? null
168                                             : getSourceType(
169                                                     request.getInputEvent(),
170                                                     request.getRequestTime()),
171                                     mDatastoreManager,
172                                     mContentResolver)
173                             ? STATUS_SUCCESS
174                             : STATUS_IO_ERROR;
175 
176                 default:
177                     return STATUS_INVALID_ARGUMENT;
178             }
179         } finally {
180             mReadWriteLock.readLock().unlock();
181         }
182     }
183 
184     /**
185      * Processes a source registration request delegated to OS from the caller, e.g. Chrome,
186      * returning a status code.
187      */
registerWebSource( @onNull WebSourceRegistrationRequestInternal request, boolean adIdPermission, long requestTime)188     int registerWebSource(
189             @NonNull WebSourceRegistrationRequestInternal request,
190             boolean adIdPermission,
191             long requestTime) {
192         WebSourceRegistrationRequest sourceRegistrationRequest =
193                 request.getSourceRegistrationRequest();
194         if (!isValid(sourceRegistrationRequest)) {
195             LogUtil.e("registerWebSource received invalid parameters");
196             return STATUS_INVALID_ARGUMENT;
197         }
198         mReadWriteLock.readLock().lock();
199         try {
200             boolean enqueueStatus =
201                     EnqueueAsyncRegistration.webSourceRegistrationRequest(
202                             sourceRegistrationRequest,
203                             adIdPermission,
204                             getRegistrant(request.getAppPackageName()),
205                             requestTime,
206                             getSourceType(
207                                     sourceRegistrationRequest.getInputEvent(),
208                                     request.getRequestTime()),
209                             mDatastoreManager,
210                             mContentResolver);
211             if (enqueueStatus) {
212                 return STATUS_SUCCESS;
213             } else {
214 
215                 return STATUS_IO_ERROR;
216             }
217         } finally {
218             mReadWriteLock.readLock().unlock();
219         }
220     }
221 
222     /**
223      * Processes a trigger registration request delegated to OS from the caller, e.g. Chrome,
224      * returning a status code.
225      */
registerWebTrigger( @onNull WebTriggerRegistrationRequestInternal request, boolean adIdPermission, long requestTime)226     int registerWebTrigger(
227             @NonNull WebTriggerRegistrationRequestInternal request,
228             boolean adIdPermission,
229             long requestTime) {
230         WebTriggerRegistrationRequest triggerRegistrationRequest =
231                 request.getTriggerRegistrationRequest();
232         if (!isValid(triggerRegistrationRequest)) {
233             LogUtil.e("registerWebTrigger received invalid parameters");
234             return STATUS_INVALID_ARGUMENT;
235         }
236         mReadWriteLock.readLock().lock();
237         try {
238             boolean enqueueStatus =
239                     EnqueueAsyncRegistration.webTriggerRegistrationRequest(
240                             triggerRegistrationRequest,
241                             adIdPermission,
242                             getRegistrant(request.getAppPackageName()),
243                             requestTime,
244                             mDatastoreManager,
245                             mContentResolver);
246             if (enqueueStatus) {
247                 return STATUS_SUCCESS;
248             } else {
249 
250                 return STATUS_IO_ERROR;
251             }
252         } finally {
253             mReadWriteLock.readLock().unlock();
254         }
255     }
256 
257     /**
258      * Implement a deleteRegistrations request, returning a r{@link
259      * AdServicesStatusUtils.StatusCode}.
260      */
261     @AdServicesStatusUtils.StatusCode
deleteRegistrations(@onNull DeletionParam request)262     int deleteRegistrations(@NonNull DeletionParam request) {
263         mReadWriteLock.readLock().lock();
264         try {
265             boolean deleteResult = mMeasurementDataDeleter.delete(request);
266             if (deleteResult) {
267                 markDeletion();
268             }
269             return deleteResult ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR;
270         } catch (NullPointerException | IllegalArgumentException e) {
271             LogUtil.e(e, "Delete registration received invalid parameters");
272             return STATUS_INVALID_ARGUMENT;
273         } finally {
274             mReadWriteLock.readLock().unlock();
275         }
276     }
277 
278     /**
279      * Delete all records from a specific package.
280      */
deletePackageRecords(Uri packageUri)281     public void deletePackageRecords(Uri packageUri) {
282         Uri appUri = getAppUri(packageUri);
283         LogUtil.d("Deleting records for " + appUri);
284         mReadWriteLock.writeLock().lock();
285         try {
286             Optional<Boolean> didDeletionOccurOpt =
287                     mDatastoreManager.runInTransactionWithResult(
288                             (dao) -> {
289                                 dao.undoInstallAttribution(appUri);
290                                 return dao.deleteAppRecords(appUri);
291                             });
292             if (didDeletionOccurOpt.isPresent() && didDeletionOccurOpt.get()) {
293                 markDeletion();
294             }
295         } catch (NullPointerException | IllegalArgumentException e) {
296             LogUtil.e(e, "Delete package records received invalid parameters");
297         } finally {
298             mReadWriteLock.writeLock().unlock();
299         }
300     }
301 
302     /**
303      * Delete all data generated by Measurement API, except for tables in the exclusion list.
304      *
305      * @param tablesToExclude a {@link List} of tables that won't be deleted.
306      */
deleteAllMeasurementData(@onNull List<String> tablesToExclude)307     public void deleteAllMeasurementData(@NonNull List<String> tablesToExclude) {
308         mReadWriteLock.writeLock().lock();
309         try {
310             mDatastoreManager.runInTransaction(
311                     (dao) -> dao.deleteAllMeasurementData(tablesToExclude));
312             LogUtil.v(
313                     "All data is cleared for Measurement API except: %s",
314                     tablesToExclude.toString());
315             markDeletion();
316         } finally {
317             mReadWriteLock.writeLock().unlock();
318         }
319     }
320 
321     /** Delete all data generated from apps that are not currently installed. */
deleteAllUninstalledMeasurementData()322     public void deleteAllUninstalledMeasurementData() {
323         List<Uri> installedApplicationsList = getCurrentInstalledApplicationsList(mContext);
324         mReadWriteLock.writeLock().lock();
325         try {
326             Optional<Boolean> didDeletionOccurOpt =
327                     mDatastoreManager.runInTransactionWithResult(
328                             (dao) -> dao.deleteAppRecordsNotPresent(installedApplicationsList));
329             if (didDeletionOccurOpt.isPresent() && didDeletionOccurOpt.get()) {
330                 markDeletion();
331             }
332         } finally {
333             mReadWriteLock.writeLock().unlock();
334         }
335     }
336 
getCurrentInstalledApplicationsList(Context context)337     private List<Uri> getCurrentInstalledApplicationsList(Context context) {
338         PackageManager packageManager = context.getPackageManager();
339         List<ApplicationInfo> applicationInfoList =
340                 PackageManagerCompatUtils.getInstalledApplications(
341                         packageManager, PackageManager.GET_META_DATA);
342         return applicationInfoList.stream()
343                 .map(applicationInfo -> Uri.parse("android-app://" + applicationInfo.packageName))
344                 .collect(Collectors.toList());
345     }
346 
347     @VisibleForTesting
getSourceType(InputEvent inputEvent, long requestTime)348     Source.SourceType getSourceType(InputEvent inputEvent, long requestTime) {
349         // If click verification is enabled and the InputEvent is not null, but it cannot be
350         // verified, then the SourceType is demoted to EVENT.
351         if (mFlags.getMeasurementIsClickVerificationEnabled()
352                 && inputEvent != null
353                 && !mClickVerifier.isInputEventVerifiable(inputEvent, requestTime)) {
354             return Source.SourceType.EVENT;
355         } else {
356             return inputEvent == null ? Source.SourceType.EVENT : Source.SourceType.NAVIGATION;
357         }
358     }
359 
getRegistrant(String packageName)360     private Uri getRegistrant(String packageName) {
361         return Uri.parse(ANDROID_APP_SCHEME + "://" + packageName);
362     }
363 
getAppUri(Uri packageUri)364     private Uri getAppUri(Uri packageUri) {
365         return Uri.parse(ANDROID_APP_SCHEME + "://" + packageUri.getEncodedSchemeSpecificPart());
366     }
367 
isValid(WebSourceRegistrationRequest sourceRegistrationRequest)368     private boolean isValid(WebSourceRegistrationRequest sourceRegistrationRequest) {
369         Uri verifiedDestination = sourceRegistrationRequest.getVerifiedDestination();
370         Uri webDestination = sourceRegistrationRequest.getWebDestination();
371 
372         if (verifiedDestination == null) {
373             return webDestination == null
374                     ? true
375                     : Web.topPrivateDomainAndScheme(webDestination).isPresent();
376         }
377 
378         return isVerifiedDestination(
379                 verifiedDestination, webDestination, sourceRegistrationRequest.getAppDestination());
380     }
381 
isVerifiedDestination( Uri verifiedDestination, Uri webDestination, Uri appDestination)382     private boolean isVerifiedDestination(
383             Uri verifiedDestination, Uri webDestination, Uri appDestination) {
384         String destinationPackage = null;
385         if (appDestination != null) {
386             destinationPackage = appDestination.getHost();
387         }
388         String verifiedScheme = verifiedDestination.getScheme();
389         String verifiedHost = verifiedDestination.getHost();
390 
391         // Verified destination matches appDestination value
392         if (destinationPackage != null
393                 && verifiedHost != null
394                 && (verifiedScheme == null || verifiedScheme.equals(ANDROID_APP_SCHEME))
395                 && verifiedHost.equals(destinationPackage)) {
396             return true;
397         }
398 
399         try {
400             Intent intent = Intent.parseUri(verifiedDestination.toString(), 0);
401             ComponentName componentName = intent.resolveActivity(mContext.getPackageManager());
402             if (componentName == null) {
403                 return false;
404             }
405 
406             // (ComponentName::getPackageName cannot be null)
407             String verifiedPackage = componentName.getPackageName();
408 
409             // Try to match an app vendor store and extract a target package
410             if (destinationPackage != null
411                     && verifiedPackage.equals(AppVendorPackages.PLAY_STORE)) {
412                 String targetPackage = getTargetPackageFromPlayStoreUri(verifiedDestination);
413                 return targetPackage != null && targetPackage.equals(destinationPackage);
414 
415             // Try to match web destination
416             } else if (webDestination == null) {
417                 return false;
418             } else {
419                 Optional<Uri> webDestinationTopPrivateDomainAndScheme =
420                         Web.topPrivateDomainAndScheme(webDestination);
421                 Optional<Uri> verifiedDestinationTopPrivateDomainAndScheme =
422                         Web.topPrivateDomainAndScheme(verifiedDestination);
423                 return webDestinationTopPrivateDomainAndScheme.isPresent()
424                         && verifiedDestinationTopPrivateDomainAndScheme.isPresent()
425                         && webDestinationTopPrivateDomainAndScheme.get().equals(
426                                 verifiedDestinationTopPrivateDomainAndScheme.get());
427             }
428         } catch (URISyntaxException e) {
429             LogUtil.e(e,
430                     "MeasurementImpl::handleVerifiedDestination: failed to parse intent URI: %s",
431                     verifiedDestination.toString());
432             return false;
433         }
434     }
435 
isValid(WebTriggerRegistrationRequest triggerRegistrationRequest)436     private static boolean isValid(WebTriggerRegistrationRequest triggerRegistrationRequest) {
437         Uri destination = triggerRegistrationRequest.getDestination();
438         return Web.topPrivateDomainAndScheme(destination).isPresent();
439     }
440 
getTargetPackageFromPlayStoreUri(Uri uri)441     private static String getTargetPackageFromPlayStoreUri(Uri uri) {
442         return uri.getQueryParameter("id");
443     }
444 
445     private interface AppVendorPackages {
446         String PLAY_STORE = "com.android.vending";
447     }
448 
449     /**
450      * Checks if the module was rollback and if there was a deletion in the version rolled back
451      * from. If there was, delete all measurement data to prioritize user privacy.
452      */
deleteOnRollback()453     private void deleteOnRollback() {
454         if (FlagsFactory.getFlags().getMeasurementRollbackDeletionKillSwitch()) {
455             LogUtil.e("Rollback deletion is disabled. Not checking system server for rollback.");
456             return;
457         }
458 
459         LogUtil.d("Checking rollback status.");
460         boolean needsToHandleRollbackReconciliation = checkIfNeedsToHandleReconciliation();
461         if (needsToHandleRollbackReconciliation) {
462             LogUtil.d("Rollback and deletion detected, deleting all measurement data.");
463             mReadWriteLock.writeLock().lock();
464             try {
465                 mDatastoreManager.runInTransaction(
466                         (dao) -> dao.deleteAllMeasurementData(Collections.emptyList()));
467             } finally {
468                 mReadWriteLock.writeLock().unlock();
469             }
470         }
471     }
472 
473     @VisibleForTesting
checkIfNeedsToHandleReconciliation()474     boolean checkIfNeedsToHandleReconciliation() {
475         if (SdkLevel.isAtLeastT()) {
476             return AdServicesManager.getInstance(mContext)
477                     .needsToHandleRollbackReconciliation(AdServicesManager.MEASUREMENT_DELETION);
478         }
479 
480         // Not on Android T+
481         if (FlagsFactory.getFlags().getMeasurementRollbackDeletionAppSearchKillSwitch()) {
482             LogUtil.e("Rollback deletion is disabled. Not checking App Search for rollback.");
483             return false;
484         }
485 
486         return AppSearchMeasurementRollbackManager.getInstance(
487                         mContext, AdServicesManager.MEASUREMENT_DELETION)
488                 .needsToHandleRollbackReconciliation();
489     }
490 
491     /**
492      * Stores a bit in the system server indicating that a deletion happened for the current
493      * AdServices module version. This information is used for deleting data after it has been
494      * restored by a module rollback.
495      */
markDeletion()496     private void markDeletion() {
497         if (FlagsFactory.getFlags().getMeasurementRollbackDeletionKillSwitch()) {
498             LogUtil.e("Rollback deletion is disabled. Not storing status in system server.");
499             return;
500         }
501 
502         if (SdkLevel.isAtLeastT()) {
503             LogUtil.d("Marking deletion in system server.");
504             AdServicesManager.getInstance(mContext)
505                     .recordAdServicesDeletionOccurred(AdServicesManager.MEASUREMENT_DELETION);
506             return;
507         }
508 
509         // On Android S or lower.
510         if (FlagsFactory.getFlags().getMeasurementRollbackDeletionAppSearchKillSwitch()) {
511             LogUtil.e("Rollback deletion in AppSearch disabled. Not storing status in AppSearch.");
512             return;
513         }
514 
515         LogUtil.d("Marking deletion in AppSearch");
516         AppSearchMeasurementRollbackManager.getInstance(
517                         mContext, AdServicesManager.MEASUREMENT_DELETION)
518                 .recordAdServicesDeletionOccurred();
519     }
520 }
521