• 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.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