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 sFilteringEnabled; 85 86 private static final Object LOCK = new Object(); 87 88 /** Enable the PackageChangedReceiver */ enableReceiver(@onNull Context context, @NonNull Flags flags)89 public static boolean enableReceiver(@NonNull Context context, @NonNull Flags flags) { 90 return changeReceiverState(context, flags, COMPONENT_ENABLED_STATE_ENABLED); 91 } 92 93 /** Disable the PackageChangedReceiver */ disableReceiver(@onNull Context context, @NonNull Flags flags)94 public static boolean disableReceiver(@NonNull Context context, @NonNull Flags flags) { 95 return changeReceiverState(context, flags, COMPONENT_ENABLED_STATE_DISABLED); 96 } 97 changeReceiverState( @onNull Context context, @NonNull Flags flags, int state)98 private static boolean changeReceiverState( 99 @NonNull Context context, @NonNull Flags flags, int state) { 100 synchronized (LOCK) { 101 sFilteringEnabled = 102 BinderFlagReader.readFlag(flags::getFledgeAdSelectionFilteringEnabled); 103 try { 104 context.getPackageManager() 105 .setComponentEnabledSetting( 106 new ComponentName(context, PackageChangedReceiver.class), 107 state, 108 PackageManager.DONT_KILL_APP); 109 } catch (IllegalArgumentException e) { 110 LogUtil.e("enableService failed for %s", context.getPackageName()); 111 return false; 112 } 113 return true; 114 } 115 } 116 117 /** 118 * This receiver will be used for both T+ and S-. For T+, the AdServices System Service will 119 * listen to the system broadcasts and rebroadcast to this receiver. For S-, since we don't have 120 * AdServices in System Service, we have to listen to system broadcasts directly. Note: This is 121 * best effort since AdServices process is not a persistent process, so any processing that 122 * happens here should be verified in a background job. TODO(b/263904417): Register for 123 * PACKAGE_ADDED receiver for S-. 124 */ 125 @Override onReceive(Context context, Intent intent)126 public void onReceive(Context context, Intent intent) { 127 LogUtil.d("PackageChangedReceiver received a broadcast: " + intent.getAction()); 128 129 // On T+, this should never be executed from ext services module 130 String packageName = context.getPackageName(); 131 if (SdkLevel.isAtLeastT() 132 && packageName != null 133 && packageName.endsWith(ADEXTSERVICES_PACKAGE_NAME_SUFFIX)) { 134 LogUtil.i( 135 "Aborting attempt to receive in PackageChangedReceiver on T+ for" 136 + " ExtServices"); 137 return; 138 } 139 synchronized (LOCK) { 140 Uri packageUri = Uri.parse(intent.getData().getSchemeSpecificPart()); 141 int packageUid = intent.getIntExtra(Intent.EXTRA_UID, -1); 142 switch (intent.getAction()) { 143 // The broadcast is received from the system. On S- devices, we do this because 144 // there is no service running in the system server. 145 case Intent.ACTION_PACKAGE_FULLY_REMOVED: 146 handlePackageFullyRemoved(context, packageUri, packageUid); 147 break; 148 case Intent.ACTION_PACKAGE_DATA_CLEARED: 149 handlePackageDataCleared(context, packageUri); 150 break; 151 // The broadcast is received from the system service. On T+ devices, we do this 152 // so 153 // that the PP API process is not woken up if the flag is disabled. 154 case PACKAGE_CHANGED_BROADCAST: 155 switch (intent.getStringExtra(ACTION_KEY)) { 156 case PACKAGE_FULLY_REMOVED: 157 handlePackageFullyRemoved(context, packageUri, packageUid); 158 break; 159 case PACKAGE_ADDED: 160 handlePackageAdded(context, packageUri); 161 break; 162 case PACKAGE_DATA_CLEARED: 163 handlePackageDataCleared(context, packageUri); 164 break; 165 } 166 break; 167 } 168 } 169 } 170 handlePackageFullyRemoved(Context context, Uri packageUri, int packageUid)171 private void handlePackageFullyRemoved(Context context, Uri packageUri, int packageUid) { 172 measurementOnPackageFullyRemoved(context, packageUri); 173 topicsOnPackageFullyRemoved(context, packageUri); 174 fledgeOnPackageFullyRemovedOrDataCleared(context, packageUri); 175 consentOnPackageFullyRemoved(context, packageUri, packageUid); 176 } 177 handlePackageAdded(Context context, Uri packageUri)178 private void handlePackageAdded(Context context, Uri packageUri) { 179 measurementOnPackageAdded(context, packageUri); 180 topicsOnPackageAdded(context, packageUri); 181 } 182 handlePackageDataCleared(Context context, Uri packageUri)183 private void handlePackageDataCleared(Context context, Uri packageUri) { 184 measurementOnPackageDataCleared(context, packageUri); 185 fledgeOnPackageFullyRemovedOrDataCleared(context, packageUri); 186 } 187 188 @VisibleForTesting measurementOnPackageFullyRemoved(Context context, Uri packageUri)189 void measurementOnPackageFullyRemoved(Context context, Uri packageUri) { 190 if (FlagsFactory.getFlags().getMeasurementReceiverDeletePackagesKillSwitch()) { 191 LogUtil.e("Measurement Delete Packages Receiver is disabled"); 192 return; 193 } 194 195 LogUtil.d("Package Fully Removed:" + packageUri); 196 sBackgroundExecutor.execute( 197 () -> MeasurementImpl.getInstance(context).deletePackageRecords(packageUri)); 198 199 // Log wipeout event triggered by request to uninstall package on device 200 WipeoutStatus wipeoutStatus = new WipeoutStatus(); 201 wipeoutStatus.setWipeoutType(WipeoutStatus.WipeoutType.UNINSTALL); 202 logWipeoutStats(wipeoutStatus); 203 } 204 205 @VisibleForTesting measurementOnPackageDataCleared(Context context, Uri packageUri)206 void measurementOnPackageDataCleared(Context context, Uri packageUri) { 207 if (FlagsFactory.getFlags().getMeasurementReceiverDeletePackagesKillSwitch()) { 208 LogUtil.e("Measurement Delete Packages Receiver is disabled"); 209 return; 210 } 211 212 LogUtil.d("Package Data Cleared: " + packageUri); 213 sBackgroundExecutor.execute( 214 () -> { 215 MeasurementImpl.getInstance(context).deletePackageRecords(packageUri); 216 }); 217 218 // Log wipeout event triggered by request (from Android) to delete data of package on device 219 WipeoutStatus wipeoutStatus = new WipeoutStatus(); 220 wipeoutStatus.setWipeoutType(WipeoutStatus.WipeoutType.CLEAR_DATA); 221 logWipeoutStats(wipeoutStatus); 222 } 223 224 @VisibleForTesting measurementOnPackageAdded(Context context, Uri packageUri)225 void measurementOnPackageAdded(Context context, Uri packageUri) { 226 if (FlagsFactory.getFlags().getMeasurementReceiverInstallAttributionKillSwitch()) { 227 LogUtil.e("Measurement Install Attribution Receiver is disabled"); 228 return; 229 } 230 231 LogUtil.d("Package Added: " + packageUri); 232 sBackgroundExecutor.execute( 233 () -> 234 MeasurementImpl.getInstance(context) 235 .doInstallAttribution(packageUri, System.currentTimeMillis())); 236 } 237 238 @VisibleForTesting topicsOnPackageFullyRemoved(Context context, @NonNull Uri packageUri)239 void topicsOnPackageFullyRemoved(Context context, @NonNull Uri packageUri) { 240 if (FlagsFactory.getFlags().getTopicsKillSwitch()) { 241 LogUtil.e("Topics API is disabled"); 242 return; 243 } 244 245 LogUtil.d( 246 "Handling App Uninstallation in Topics API for package: " + packageUri.toString()); 247 sBackgroundExecutor.execute( 248 () -> TopicsWorker.getInstance(context).handleAppUninstallation(packageUri)); 249 } 250 251 @VisibleForTesting topicsOnPackageAdded(Context context, @NonNull Uri packageUri)252 void topicsOnPackageAdded(Context context, @NonNull Uri packageUri) { 253 LogUtil.d("Package Added for topics API: " + packageUri.toString()); 254 sBackgroundExecutor.execute( 255 () -> TopicsWorker.getInstance(context).handleAppInstallation(packageUri)); 256 } 257 258 /** Deletes FLEDGE custom audience data belonging to the given application. */ 259 @VisibleForTesting fledgeOnPackageFullyRemovedOrDataCleared( @onNull Context context, @NonNull Uri packageUri)260 void fledgeOnPackageFullyRemovedOrDataCleared( 261 @NonNull Context context, @NonNull Uri packageUri) { 262 Objects.requireNonNull(context); 263 Objects.requireNonNull(packageUri); 264 265 if (FlagsFactory.getFlags().getFledgeCustomAudienceServiceKillSwitch()) { 266 LogUtil.v("FLEDGE CA API is disabled"); 267 return; 268 } 269 270 LogUtil.d("Deleting custom audience data for package: " + packageUri); 271 sBackgroundExecutor.execute( 272 () -> 273 getCustomAudienceDatabase(context) 274 .customAudienceDao() 275 .deleteCustomAudienceDataByOwner(packageUri.toString())); 276 if (sFilteringEnabled) { 277 LogUtil.d("Deleting app install data for package: " + packageUri); 278 sBackgroundExecutor.execute( 279 () -> 280 getSharedStorageDatabase(context) 281 .appInstallDao() 282 .deleteByPackageName(packageUri.toString())); 283 } 284 } 285 286 /** 287 * Deletes a consent setting for the given application and UID. If the UID is equal to 288 * DEFAULT_PACKAGE_UID, all consent data is deleted. 289 */ 290 @VisibleForTesting consentOnPackageFullyRemoved( @onNull Context context, @NonNull Uri packageUri, int packageUid)291 void consentOnPackageFullyRemoved( 292 @NonNull Context context, @NonNull Uri packageUri, int packageUid) { 293 Objects.requireNonNull(context); 294 Objects.requireNonNull(packageUri); 295 296 String packageName = packageUri.toString(); 297 LogUtil.d("Deleting consent data for package %s with UID %d", packageName, packageUid); 298 sBackgroundExecutor.execute( 299 () -> { 300 ConsentManager instance = ConsentManager.getInstance(context); 301 if (packageUid == DEFAULT_PACKAGE_UID) { 302 // There can be multiple instances of PackageChangedReceiver, e.g. in 303 // different user profiles. The system broadcasts a package change 304 // notification when any package is installed/uninstalled/cleared on any 305 // profile, to all PackageChangedReceivers. However, if the 306 // uninstallation is in a different user profile than the one this 307 // instance of PackageChangedReceiver is in, it should ignore that 308 // notification. 309 // Because the Package UID is absent, we need to figure out 310 // if this package was deleted in the current profile or a different one. 311 // We can do that by querying the list of installed packages and checking 312 // if the package name appears there. If it does, then this package was 313 // uninstalled in a different profile, and so the method should no-op. 314 315 if (!isPackageStillInstalled(context, packageName)) { 316 instance.clearConsentForUninstalledApp(packageName); 317 LogUtil.d("Deleted all consent data for package %s", packageName); 318 } else { 319 LogUtil.d( 320 "Uninstalled package %s is present in list of installed" 321 + " packages; ignoring", 322 packageName); 323 } 324 } else { 325 instance.clearConsentForUninstalledApp(packageName, packageUid); 326 LogUtil.d( 327 "Deleted consent data for package %s with UID %d", 328 packageName, packageUid); 329 } 330 }); 331 } 332 333 /** 334 * Checks if the removed package name is still present in the list of installed packages 335 * 336 * @param context the context passed along with the package notification 337 * @param packageName the name of the package that was removed 338 * @return {@code true} if the removed package name still exists in the list of installed 339 * packages on the system retrieved from {@code PackageManager.getInstalledPackages}; {@code 340 * false} otherwise. 341 */ 342 @VisibleForTesting isPackageStillInstalled(@onNull Context context, @NonNull String packageName)343 boolean isPackageStillInstalled(@NonNull Context context, @NonNull String packageName) { 344 Objects.requireNonNull(context); 345 Objects.requireNonNull(packageName); 346 PackageManager packageManager = context.getPackageManager(); 347 return PackageManagerCompatUtils.getInstalledPackages(packageManager, 0).stream() 348 .anyMatch(s -> packageName.equals(s.packageName)); 349 } 350 351 /** 352 * Returns an instance of the {@link CustomAudienceDatabase}. 353 * 354 * <p>This is split out for testing/mocking purposes only, since the {@link 355 * CustomAudienceDatabase} is abstract and therefore unmockable. 356 */ 357 @VisibleForTesting getCustomAudienceDatabase(@onNull Context context)358 CustomAudienceDatabase getCustomAudienceDatabase(@NonNull Context context) { 359 Objects.requireNonNull(context); 360 return CustomAudienceDatabase.getInstance(context); 361 } 362 363 /** 364 * Returns an instance of the {@link SharedStorageDatabase}. 365 * 366 * <p>This is split out for testing/mocking purposes only, since the {@link 367 * SharedStorageDatabase} is abstract and therefore unmockable. 368 */ 369 @VisibleForTesting getSharedStorageDatabase(@onNull Context context)370 SharedStorageDatabase getSharedStorageDatabase(@NonNull Context context) { 371 Objects.requireNonNull(context); 372 return SharedStorageDatabase.getInstance(context); 373 } 374 logWipeoutStats(WipeoutStatus wipeoutStatus)375 private void logWipeoutStats(WipeoutStatus wipeoutStatus) { 376 AdServicesLoggerImpl.getInstance() 377 .logMeasurementWipeoutStats( 378 new MeasurementWipeoutStats.Builder() 379 .setCode(AD_SERVICES_MEASUREMENT_WIPEOUT) 380 .setWipeoutType(wipeoutStatus.getWipeoutType().ordinal()) 381 .build()); 382 } 383 } 384