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