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