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 android.adservices.common.KeyedFrequencyCap;
20 import android.annotation.NonNull;
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 
24 import com.android.adservices.LoggerFactory;
25 import com.android.adservices.data.adselection.AdSelectionDatabase;
26 import com.android.adservices.data.adselection.AdSelectionDebugReportDao;
27 import com.android.adservices.data.adselection.AdSelectionDebugReportingDatabase;
28 import com.android.adservices.data.adselection.AdSelectionEntryDao;
29 import com.android.adservices.data.adselection.AdSelectionServerDatabase;
30 import com.android.adservices.data.adselection.EncryptionContextDao;
31 import com.android.adservices.data.adselection.FrequencyCapDao;
32 import com.android.adservices.data.adselection.SharedStorageDatabase;
33 import com.android.adservices.data.enrollment.EnrollmentDao;
34 import com.android.adservices.data.kanon.KAnonDatabase;
35 import com.android.adservices.data.kanon.KAnonMessageDao;
36 import com.android.adservices.service.Flags;
37 import com.android.adservices.service.FlagsFactory;
38 import com.android.adservices.service.stats.AdServicesLogger;
39 import com.android.adservices.service.stats.InteractionReportingTableClearedStats;
40 import com.android.adservices.service.stats.StatsdAdServicesLogger;
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import java.time.Clock;
44 import java.time.Instant;
45 import java.util.Objects;
46 
47 /** Utility class to perform Fledge maintenance tasks */
48 public class FledgeMaintenanceTasksWorker {
49     private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
50     @NonNull private final AdSelectionEntryDao mAdSelectionEntryDao;
51     @NonNull private final AdSelectionDebugReportDao mAdSelectionDebugReportDao;
52     @NonNull private final FrequencyCapDao mFrequencyCapDao;
53     @NonNull private final EnrollmentDao mEnrollmentDao;
54     @NonNull private final EncryptionContextDao mEncryptionContextDao;
55     @NonNull private final Flags mFlags;
56     @NonNull private final Clock mClock;
57     @NonNull private final KAnonMessageDao mKAnonMessageDao;
58 
59     @NonNull private final AdServicesLogger mAdServicesLogger;
60 
61     @VisibleForTesting
FledgeMaintenanceTasksWorker( @onNull Flags flags, @NonNull AdSelectionEntryDao adSelectionEntryDao, @NonNull FrequencyCapDao frequencyCapDao, @NonNull EnrollmentDao enrollmentDao, @NonNull EncryptionContextDao encryptionContextDao, @NonNull AdSelectionDebugReportDao adSelectionDebugReportDao, @NonNull Clock clock, @NonNull AdServicesLogger adServicesLogger, @NonNull KAnonMessageDao kAnonMessageDao)62     public FledgeMaintenanceTasksWorker(
63             @NonNull Flags flags,
64             @NonNull AdSelectionEntryDao adSelectionEntryDao,
65             @NonNull FrequencyCapDao frequencyCapDao,
66             @NonNull EnrollmentDao enrollmentDao,
67             @NonNull EncryptionContextDao encryptionContextDao,
68             @NonNull AdSelectionDebugReportDao adSelectionDebugReportDao,
69             @NonNull Clock clock,
70             @NonNull AdServicesLogger adServicesLogger,
71             @NonNull KAnonMessageDao kAnonMessageDao) {
72         Objects.requireNonNull(flags);
73         Objects.requireNonNull(adSelectionEntryDao);
74         Objects.requireNonNull(frequencyCapDao);
75         Objects.requireNonNull(enrollmentDao);
76         Objects.requireNonNull(clock);
77         Objects.requireNonNull(encryptionContextDao);
78         Objects.requireNonNull(adSelectionDebugReportDao);
79         Objects.requireNonNull(adServicesLogger);
80         Objects.requireNonNull(kAnonMessageDao);
81 
82         mFlags = flags;
83         mAdSelectionEntryDao = adSelectionEntryDao;
84         mFrequencyCapDao = frequencyCapDao;
85         mEnrollmentDao = enrollmentDao;
86         mEncryptionContextDao = encryptionContextDao;
87         mClock = clock;
88         mAdSelectionDebugReportDao = adSelectionDebugReportDao;
89         mAdServicesLogger = adServicesLogger;
90         mKAnonMessageDao = kAnonMessageDao;
91     }
92 
FledgeMaintenanceTasksWorker(@onNull Context context)93     private FledgeMaintenanceTasksWorker(@NonNull Context context) {
94         Objects.requireNonNull(context);
95         mFlags = FlagsFactory.getFlags();
96         mAdSelectionEntryDao = AdSelectionDatabase.getInstance(context).adSelectionEntryDao();
97         mFrequencyCapDao = SharedStorageDatabase.getInstance(context).frequencyCapDao();
98         mEnrollmentDao = EnrollmentDao.getInstance();
99         mEncryptionContextDao =
100                 AdSelectionServerDatabase.getInstance(context).encryptionContextDao();
101         mClock = Clock.systemUTC();
102         mAdSelectionDebugReportDao =
103                 AdSelectionDebugReportingDatabase.getInstance(context)
104                         .getAdSelectionDebugReportDao();
105         mAdServicesLogger = StatsdAdServicesLogger.getInstance();
106         mKAnonMessageDao = KAnonDatabase.getInstance(context).kAnonMessageDao();
107     }
108 
109     /** Creates a new instance of {@link FledgeMaintenanceTasksWorker}. */
create(@onNull Context context)110     public static FledgeMaintenanceTasksWorker create(@NonNull Context context) {
111         Objects.requireNonNull(context);
112         return new FledgeMaintenanceTasksWorker(context);
113     }
114 
115     /**
116      * Clears all entries in the {@code ad_selection} table that are older than {@code
117      * expirationTime}. Then, clears all expired entries in the {@code buyer_decision_logic} as well
118      * as the {@code registered_ad_interactions} table.
119      */
clearExpiredAdSelectionData()120     public void clearExpiredAdSelectionData() {
121         // Read from flags directly, since this maintenance task worker is attached to a background
122         //  job with unknown lifetime
123         Instant expirationTime =
124                 mClock.instant().minusSeconds(mFlags.getAdSelectionExpirationWindowS());
125 
126         sLogger.v("Clearing expired Ad Selection data");
127         mAdSelectionEntryDao.removeExpiredAdSelection(expirationTime);
128 
129         sLogger.v("Clearing expired Buyer Decision Logic data ");
130         mAdSelectionEntryDao.removeExpiredBuyerDecisionLogic();
131 
132         if (mFlags.getFledgeBeaconReportingMetricsEnabled()) {
133             long numUrisBeforeClearing = mAdSelectionEntryDao.getTotalNumRegisteredAdInteractions();
134 
135             sLogger.v("Clearing expired Registered Ad Interaction data ");
136             mAdSelectionEntryDao.removeExpiredRegisteredAdInteractions();
137 
138             long numUrisAfterClearing = mAdSelectionEntryDao.getTotalNumRegisteredAdInteractions();
139 
140             mAdServicesLogger.logInteractionReportingTableClearedStats(
141                     InteractionReportingTableClearedStats.builder()
142                             .setNumUrisCleared((int) (numUrisAfterClearing - numUrisBeforeClearing))
143                             .build());
144         } else {
145             sLogger.v("Clearing expired Registered Ad Interaction data ");
146             mAdSelectionEntryDao.removeExpiredRegisteredAdInteractions();
147         }
148 
149         if (mFlags.getFledgeAuctionServerEnabled()
150                 || mFlags.getFledgeOnDeviceAuctionShouldUseUnifiedTables()) {
151             sLogger.v("Clearing expired Ad Selection Initialization data");
152             mAdSelectionEntryDao.removeExpiredAdSelectionInitializations(expirationTime);
153         }
154 
155         if (mFlags.getFledgeOnDeviceAuctionShouldUseUnifiedTables()) {
156             sLogger.v("Clearing expired Registered Ad Interaction data from unified table ");
157             mAdSelectionEntryDao.removeExpiredRegisteredAdInteractionsFromUnifiedTable();
158         }
159 
160         if (mFlags.getFledgeAuctionServerEnabled()) {
161             sLogger.v("Clearing expired Encryption Context");
162             mEncryptionContextDao.removeExpiredEncryptionContext(expirationTime);
163         }
164 
165         if (mFlags.getFledgeEventLevelDebugReportingEnabled()) {
166             sLogger.v("Clearing expired debug reports ");
167             mAdSelectionDebugReportDao.deleteDebugReportsBeforeTime(expirationTime);
168         }
169     }
170 
171     /**
172      * Clears invalid histogram data from the frequency cap database if the ad filtering feature is
173      * enabled.
174      *
175      * <p>Invalid histogram data includes:
176      *
177      * <ul>
178      *   <li>Expired histogram data
179      *   <li>Disallowed buyer histogram data
180      *   <li>Disallowed source app histogram data
181      *   <li>Uninstalled source app histogram data
182      * </ul>
183      */
clearInvalidFrequencyCapHistogramData(@onNull PackageManager packageManager)184     public void clearInvalidFrequencyCapHistogramData(@NonNull PackageManager packageManager) {
185         Objects.requireNonNull(packageManager);
186 
187         // Read from flags directly, since this maintenance task worker is attached to a background
188         //  job with unknown lifetime
189         if (!mFlags.getFledgeFrequencyCapFilteringEnabled()) {
190             sLogger.v(
191                     "Frequency cap filtering disabled; skipping Frequency Cap histogram"
192                             + " maintenance");
193             return;
194         }
195 
196         Instant expirationInstant =
197                 mClock.instant().minusSeconds(KeyedFrequencyCap.MAX_INTERVAL.getSeconds());
198 
199         sLogger.v(
200                 "Clearing expired Frequency Cap histogram events older than %s", expirationInstant);
201         int numExpiredEvents = mFrequencyCapDao.deleteAllExpiredHistogramData(expirationInstant);
202         sLogger.v("Cleared %d expired Frequency Cap histogram events", numExpiredEvents);
203 
204         // Read from flags directly, since this maintenance task worker is attached to a background
205         //  job with unknown lifetime
206         if (mFlags.getDisableFledgeEnrollmentCheck()) {
207             sLogger.v(
208                     "FLEDGE enrollment check disabled; skipping disallowed buyer Frequency Cap "
209                             + "histogram maintenance");
210         } else {
211             sLogger.v("Clearing Frequency Cap histogram events for disallowed buyer ad techs");
212             int numDisallowedBuyerEvents =
213                     mFrequencyCapDao.deleteAllDisallowedBuyerHistogramData(mEnrollmentDao);
214             sLogger.v(
215                     "Cleared %d Frequency Cap histogram events for disallowed buyer ad techs",
216                     numDisallowedBuyerEvents);
217         }
218 
219         // Read from flags directly, since this maintenance task worker is attached to a background
220         //  job with unknown lifetime
221         sLogger.v("Clearing Frequency Cap histogram events for disallowed source apps");
222         int numDisallowedSourceAppEvents =
223                 mFrequencyCapDao.deleteAllDisallowedSourceAppHistogramData(packageManager, mFlags);
224         sLogger.v(
225                 "Cleared %d Frequency Cap histogram events for disallowed source apps",
226                 numDisallowedSourceAppEvents);
227     }
228 
229     /** Deletes the expired {@link com.android.adservices.service.kanon.KAnonMessageEntity} */
clearExpiredKAnonMessageEntities()230     public void clearExpiredKAnonMessageEntities() {
231         if (mFlags.getFledgeKAnonSignJoinFeatureEnabled()) {
232             sLogger.v("Clearing expired KAnon message entities");
233             mKAnonMessageDao.removeExpiredEntities(mClock.instant());
234             sLogger.v("Cleared expired KAnon message entities");
235         }
236     }
237 }
238