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.common; 18 19 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 20 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; 21 22 import static com.android.adservices.AdServicesCommon.ADEXTSERVICES_PACKAGE_NAME_SUFFIX; 23 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_WIPEOUT; 24 25 import android.annotation.NonNull; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.PackageManager; 31 import android.net.Uri; 32 import android.os.Build; 33 34 import androidx.annotation.RequiresApi; 35 36 import com.android.adservices.LogUtil; 37 import com.android.adservices.concurrency.AdServicesExecutors; 38 import com.android.adservices.data.adselection.SharedStorageDatabase; 39 import com.android.adservices.data.customaudience.CustomAudienceDatabase; 40 import com.android.adservices.service.Flags; 41 import com.android.adservices.service.FlagsFactory; 42 import com.android.adservices.service.common.compat.PackageManagerCompatUtils; 43 import com.android.adservices.service.consent.ConsentManager; 44 import com.android.adservices.service.measurement.MeasurementImpl; 45 import com.android.adservices.service.measurement.WipeoutStatus; 46 import com.android.adservices.service.stats.AdServicesLoggerImpl; 47 import com.android.adservices.service.stats.MeasurementWipeoutStats; 48 import com.android.adservices.service.topics.TopicsWorker; 49 import com.android.internal.annotations.VisibleForTesting; 50 import com.android.modules.utils.build.SdkLevel; 51 52 import java.util.Objects; 53 import java.util.concurrent.Executor; 54 55 /** 56 * Receiver to receive a com.android.adservices.PACKAGE_CHANGED broadcast from the AdServices system 57 * service when package install/uninstalls occur. 58 */ 59 // TODO(b/269798827): Enable for R. 60 @RequiresApi(Build.VERSION_CODES.S) 61 public class PackageChangedReceiver extends BroadcastReceiver { 62 63 /** 64 * Broadcast send from the system service to the AdServices module when a package has been 65 * installed/uninstalled. 66 */ 67 public static final String PACKAGE_CHANGED_BROADCAST = "com.android.adservices.PACKAGE_CHANGED"; 68 69 /** Key for designating if the action was an installation or an uninstallation. */ 70 public static final String ACTION_KEY = "action"; 71 72 /** Value if the package change was an uninstallation. */ 73 public static final String PACKAGE_FULLY_REMOVED = "package_fully_removed"; 74 75 /** Value if the package change was an installation. */ 76 public static final String PACKAGE_ADDED = "package_added"; 77 78 /** Value if the package had its data cleared. */ 79 public static final String PACKAGE_DATA_CLEARED = "package_data_cleared"; 80 81 private static final Executor sBackgroundExecutor = AdServicesExecutors.getBackgroundExecutor(); 82 83 private static final int DEFAULT_PACKAGE_UID = -1; 84 private static boolean sFrequencyCapFilteringEnabled; 85 private static boolean sAppInstallFilteringEnabled; 86 87 private static final Object LOCK = new Object(); 88 89 /** Enable the PackageChangedReceiver */ enableReceiver(@onNull Context context, @NonNull Flags flags)90 public static boolean enableReceiver(@NonNull Context context, @NonNull Flags flags) { 91 return changeReceiverState(context, flags, COMPONENT_ENABLED_STATE_ENABLED); 92 } 93 94 /** Disable the PackageChangedReceiver */ disableReceiver(@onNull Context context, @NonNull Flags flags)95 public static boolean disableReceiver(@NonNull Context context, @NonNull Flags flags) { 96 return changeReceiverState(context, flags, COMPONENT_ENABLED_STATE_DISABLED); 97 } 98 changeReceiverState( @onNull Context context, @NonNull Flags flags, int state)99 private static boolean changeReceiverState( 100 @NonNull Context context, @NonNull Flags flags, int state) { 101 synchronized (LOCK) { 102 sFrequencyCapFilteringEnabled = 103 BinderFlagReader.readFlag(flags::getFledgeFrequencyCapFilteringEnabled); 104 sAppInstallFilteringEnabled = 105 BinderFlagReader.readFlag(flags::getFledgeAppInstallFilteringEnabled); 106 try { 107 context.getPackageManager() 108 .setComponentEnabledSetting( 109 new ComponentName(context, PackageChangedReceiver.class), 110 state, 111 PackageManager.DONT_KILL_APP); 112 } catch (IllegalArgumentException e) { 113 LogUtil.e("enableService failed for %s", context.getPackageName()); 114 return false; 115 } 116 return true; 117 } 118 } 119 120 /** 121 * This receiver will be used for both T+ and S-. For T+, the AdServices System Service will 122 * listen to the system broadcasts and rebroadcast to this receiver. For S-, since we don't have 123 * AdServices in System Service, we have to listen to system broadcasts directly. Note: This is 124 * best effort since AdServices process is not a persistent process, so any processing that 125 * happens here should be verified in a background job. TODO(b/263904417): Register for 126 * PACKAGE_ADDED receiver for S-. 127 */ 128 @Override onReceive(Context context, Intent intent)129 public void onReceive(Context context, Intent intent) { 130 LogUtil.d("PackageChangedReceiver received a broadcast: " + intent.getAction()); 131 132 // On T+, this should never be executed from ext services module 133 String packageName = context.getPackageName(); 134 if (SdkLevel.isAtLeastT() 135 && packageName != null 136 && packageName.endsWith(ADEXTSERVICES_PACKAGE_NAME_SUFFIX)) { 137 LogUtil.d( 138 "Aborting attempt to receive in PackageChangedReceiver on T+ for" 139 + " ExtServices"); 140 return; 141 } 142 synchronized (LOCK) { 143 Uri packageUri = Uri.parse(intent.getData().getSchemeSpecificPart()); 144 int packageUid = intent.getIntExtra(Intent.EXTRA_UID, -1); 145 switch (intent.getAction()) { 146 // The broadcast is received from the system. On S- devices, we do this because 147 // there is no service running in the system server. 148 case Intent.ACTION_PACKAGE_FULLY_REMOVED: 149 handlePackageFullyRemoved(context, packageUri, packageUid); 150 break; 151 case Intent.ACTION_PACKAGE_DATA_CLEARED: 152 handlePackageDataCleared(context, packageUri); 153 break; 154 // The broadcast is received from the system service. On T+ devices, we do this 155 // so 156 // that the PP API process is not woken up if the flag is disabled. 157 case PACKAGE_CHANGED_BROADCAST: 158 switch (intent.getStringExtra(ACTION_KEY)) { 159 case PACKAGE_FULLY_REMOVED: 160 handlePackageFullyRemoved(context, packageUri, packageUid); 161 break; 162 case PACKAGE_ADDED: 163 handlePackageAdded(context, packageUri); 164 break; 165 case PACKAGE_DATA_CLEARED: 166 handlePackageDataCleared(context, packageUri); 167 break; 168 } 169 break; 170 } 171 } 172 } 173 handlePackageFullyRemoved(Context context, Uri packageUri, int packageUid)174 private void handlePackageFullyRemoved(Context context, Uri packageUri, int packageUid) { 175 measurementOnPackageFullyRemoved(context, packageUri); 176 topicsOnPackageFullyRemoved(packageUri); 177 fledgeOnPackageFullyRemovedOrDataCleared(context, packageUri); 178 consentOnPackageFullyRemoved(context, packageUri, packageUid); 179 } 180 handlePackageAdded(Context context, Uri packageUri)181 private void handlePackageAdded(Context context, Uri packageUri) { 182 measurementOnPackageAdded(context, packageUri); 183 topicsOnPackageAdded(packageUri); 184 } 185 handlePackageDataCleared(Context context, Uri packageUri)186 private void handlePackageDataCleared(Context context, Uri packageUri) { 187 measurementOnPackageDataCleared(context, packageUri); 188 fledgeOnPackageFullyRemovedOrDataCleared(context, packageUri); 189 } 190 191 @VisibleForTesting measurementOnPackageFullyRemoved(Context context, Uri packageUri)192 void measurementOnPackageFullyRemoved(Context context, Uri packageUri) { 193 if (FlagsFactory.getFlags().getMeasurementReceiverDeletePackagesKillSwitch()) { 194 LogUtil.e("Measurement Delete Packages Receiver is disabled"); 195 return; 196 } 197 198 LogUtil.d("Package Fully Removed:" + packageUri); 199 sBackgroundExecutor.execute( 200 () -> 201 MeasurementImpl.getInstance( 202 SdkLevel.isAtLeastS() 203 ? context 204 : context.getApplicationContext()) 205 .deletePackageRecords(packageUri)); 206 207 // Log wipeout event triggered by request to uninstall package on device 208 WipeoutStatus wipeoutStatus = new WipeoutStatus(); 209 wipeoutStatus.setWipeoutType(WipeoutStatus.WipeoutType.UNINSTALL); 210 logWipeoutStats(wipeoutStatus, packageUri.toString()); 211 } 212 213 @VisibleForTesting measurementOnPackageDataCleared(Context context, Uri packageUri)214 void measurementOnPackageDataCleared(Context context, Uri packageUri) { 215 if (FlagsFactory.getFlags().getMeasurementReceiverDeletePackagesKillSwitch()) { 216 LogUtil.e("Measurement Delete Packages Receiver is disabled"); 217 return; 218 } 219 220 LogUtil.d("Package Data Cleared: " + packageUri); 221 sBackgroundExecutor.execute( 222 () -> { 223 MeasurementImpl.getInstance(context).deletePackageRecords(packageUri); 224 }); 225 226 WipeoutStatus wipeoutStatus = new WipeoutStatus(); 227 wipeoutStatus.setWipeoutType(WipeoutStatus.WipeoutType.CLEAR_DATA); 228 logWipeoutStats(wipeoutStatus, packageUri.toString()); 229 } 230 231 @VisibleForTesting measurementOnPackageAdded(Context context, Uri packageUri)232 void measurementOnPackageAdded(Context context, Uri packageUri) { 233 if (FlagsFactory.getFlags().getMeasurementReceiverInstallAttributionKillSwitch()) { 234 LogUtil.e("Measurement Install Attribution Receiver is disabled"); 235 return; 236 } 237 238 LogUtil.d("Package Added: " + packageUri); 239 sBackgroundExecutor.execute( 240 () -> 241 MeasurementImpl.getInstance(context) 242 .doInstallAttribution(packageUri, System.currentTimeMillis())); 243 } 244 245 @VisibleForTesting topicsOnPackageFullyRemoved(@onNull Uri packageUri)246 void topicsOnPackageFullyRemoved(@NonNull Uri packageUri) { 247 if (FlagsFactory.getFlags().getTopicsKillSwitch()) { 248 LogUtil.e("Topics API is disabled"); 249 return; 250 } 251 252 LogUtil.d( 253 "Handling App Uninstallation in Topics API for package: " + packageUri.toString()); 254 sBackgroundExecutor.execute( 255 () -> TopicsWorker.getInstance().handleAppUninstallation(packageUri)); 256 } 257 258 @VisibleForTesting topicsOnPackageAdded(@onNull Uri packageUri)259 void topicsOnPackageAdded(@NonNull Uri packageUri) { 260 LogUtil.d("Package Added for topics API: " + packageUri.toString()); 261 sBackgroundExecutor.execute( 262 () -> TopicsWorker.getInstance().handleAppInstallation(packageUri)); 263 } 264 265 /** Deletes FLEDGE custom audience data belonging to the given application. */ 266 @VisibleForTesting fledgeOnPackageFullyRemovedOrDataCleared( @onNull Context context, @NonNull Uri packageUri)267 void fledgeOnPackageFullyRemovedOrDataCleared( 268 @NonNull Context context, @NonNull Uri packageUri) { 269 Objects.requireNonNull(context); 270 Objects.requireNonNull(packageUri); 271 272 if (FlagsFactory.getFlags().getFledgeCustomAudienceServiceKillSwitch()) { 273 LogUtil.v("FLEDGE CA API is disabled"); 274 return; 275 } 276 277 LogUtil.d("Deleting custom audience data for package: " + packageUri); 278 sBackgroundExecutor.execute( 279 () -> 280 getCustomAudienceDatabase(context) 281 .customAudienceDao() 282 .deleteCustomAudienceDataByOwner( 283 packageUri.toString(), 284 FlagsFactory.getFlags() 285 .getFledgeFetchCustomAudienceEnabled())); 286 if (sFrequencyCapFilteringEnabled) { 287 LogUtil.d("Deleting frequency cap histogram data for package: " + packageUri); 288 sBackgroundExecutor.execute( 289 () -> 290 getSharedStorageDatabase(context) 291 .frequencyCapDao() 292 .deleteHistogramDataBySourceApp(packageUri.toString())); 293 } 294 if (sAppInstallFilteringEnabled) { 295 LogUtil.d("Deleting app install data for package: " + packageUri); 296 sBackgroundExecutor.execute( 297 () -> 298 getSharedStorageDatabase(context) 299 .appInstallDao() 300 .deleteByPackageName(packageUri.toString())); 301 } 302 } 303 304 /** 305 * Deletes a consent setting for the given application and UID. If the UID is equal to 306 * DEFAULT_PACKAGE_UID, all consent data is deleted. 307 */ 308 @VisibleForTesting consentOnPackageFullyRemoved( @onNull Context context, @NonNull Uri packageUri, int packageUid)309 void consentOnPackageFullyRemoved( 310 @NonNull Context context, @NonNull Uri packageUri, int packageUid) { 311 if (!SdkLevel.isAtLeastS()) { 312 LogUtil.d("consentOnPackageFullyRemoved is not needed on Android R, returning..."); 313 return; 314 } 315 if (SdkLevel.isAtLeastT() && packageUid == DEFAULT_PACKAGE_UID) { 316 // Event is caused by internal sdk libraries which have a UID of -1 317 LogUtil.d( 318 "returning, as events on App Uninstallation on T+ should always" 319 + " have valid UID"); 320 return; 321 } 322 Objects.requireNonNull(context); 323 Objects.requireNonNull(packageUri); 324 325 String packageName = packageUri.toString(); 326 LogUtil.d("Deleting consent data for package %s with UID %d", packageName, packageUid); 327 sBackgroundExecutor.execute( 328 () -> { 329 ConsentManager instance = ConsentManager.getInstance(); 330 if (packageUid == DEFAULT_PACKAGE_UID) { 331 // There can be multiple instances of PackageChangedReceiver, e.g. in 332 // different user profiles. The system broadcasts a package change 333 // notification when any package is installed/uninstalled/cleared on any 334 // profile, to all PackageChangedReceivers. However, if the 335 // uninstallation is in a different user profile than the one this 336 // instance of PackageChangedReceiver is in, it should ignore that 337 // notification. 338 // Because the Package UID is absent, we need to figure out 339 // if this package was deleted in the current profile or a different one. 340 // We can do that by querying the list of installed packages and checking 341 // if the package name appears there. If it does, then this package was 342 // uninstalled in a different profile, and so the method should no-op. 343 344 if (!isPackageStillInstalled(context, packageName)) { 345 instance.clearConsentForUninstalledApp(packageName); 346 LogUtil.d("Deleted all consent data for package %s", packageName); 347 } else { 348 LogUtil.d( 349 "Uninstalled package %s is present in list of installed" 350 + " packages; ignoring", 351 packageName); 352 } 353 } else { 354 instance.clearConsentForUninstalledApp(packageName, packageUid); 355 LogUtil.d( 356 "Deleted consent data for package %s with UID %d", 357 packageName, packageUid); 358 } 359 }); 360 } 361 362 /** 363 * Checks if the removed package name is still present in the list of installed packages 364 * 365 * @param context the context passed along with the package notification 366 * @param packageName the name of the package that was removed 367 * @return {@code true} if the removed package name still exists in the list of installed 368 * packages on the system retrieved from {@code PackageManager.getInstalledPackages}; {@code 369 * false} otherwise. 370 */ 371 @VisibleForTesting isPackageStillInstalled(@onNull Context context, @NonNull String packageName)372 boolean isPackageStillInstalled(@NonNull Context context, @NonNull String packageName) { 373 Objects.requireNonNull(context); 374 Objects.requireNonNull(packageName); 375 PackageManager packageManager = context.getPackageManager(); 376 return PackageManagerCompatUtils.getInstalledPackages(packageManager, 0).stream() 377 .anyMatch(s -> packageName.equals(s.packageName)); 378 } 379 380 /** 381 * Returns an instance of the {@link CustomAudienceDatabase}. 382 * 383 * <p>This is split out for testing/mocking purposes only, since the {@link 384 * CustomAudienceDatabase} is abstract and therefore unmockable. 385 */ 386 @VisibleForTesting getCustomAudienceDatabase(@onNull Context context)387 CustomAudienceDatabase getCustomAudienceDatabase(@NonNull Context context) { 388 Objects.requireNonNull(context); 389 return CustomAudienceDatabase.getInstance(context); 390 } 391 392 /** 393 * Returns an instance of the {@link SharedStorageDatabase}. 394 * 395 * <p>This is split out for testing/mocking purposes only, since the {@link 396 * SharedStorageDatabase} is abstract and therefore unmockable. 397 */ 398 @VisibleForTesting getSharedStorageDatabase(@onNull Context context)399 SharedStorageDatabase getSharedStorageDatabase(@NonNull Context context) { 400 Objects.requireNonNull(context); 401 return SharedStorageDatabase.getInstance(context); 402 } 403 logWipeoutStats(WipeoutStatus wipeoutStatus, String appPackageName)404 private void logWipeoutStats(WipeoutStatus wipeoutStatus, String appPackageName) { 405 AdServicesLoggerImpl.getInstance() 406 .logMeasurementWipeoutStats( 407 new MeasurementWipeoutStats.Builder() 408 .setCode(AD_SERVICES_MEASUREMENT_WIPEOUT) 409 .setWipeoutType(wipeoutStatus.getWipeoutType().getValue()) 410 .setSourceRegistrant(appPackageName) 411 .build()); 412 } 413 } 414