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.signals;
18 
19 import android.adservices.common.AdTechIdentifier;
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.enrollment.EnrollmentDao;
26 import com.android.adservices.data.signals.EncodedPayloadDao;
27 import com.android.adservices.data.signals.EncoderLogicHandler;
28 import com.android.adservices.data.signals.ProtectedSignalsDao;
29 import com.android.adservices.data.signals.ProtectedSignalsDatabase;
30 import com.android.adservices.service.Flags;
31 import com.android.adservices.service.FlagsFactory;
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.time.Clock;
35 import java.time.Instant;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Objects;
39 import java.util.Set;
40 
41 /** Utility class to perform Protected Signals maintenance tasks. */
42 public class SignalsMaintenanceTasksWorker {
43 
44     private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
45     @NonNull private final ProtectedSignalsDao mProtectedSignalsDao;
46     @NonNull private final EnrollmentDao mEnrollmentDao;
47     @NonNull private final EncodedPayloadDao mEncodedPayloadDao;
48     @NonNull private final Flags mFlags;
49     @NonNull private final Clock mClock;
50     @NonNull private final PackageManager mPackageManager;
51     @NonNull private final EncoderLogicHandler mEncoderLogicHandler;
52 
53     @VisibleForTesting
SignalsMaintenanceTasksWorker( @onNull Flags flags, @NonNull ProtectedSignalsDao protectedSignalsDao, @NonNull EncoderLogicHandler encoderLogicHandler, @NonNull EncodedPayloadDao encodedPayloadDao, @NonNull EnrollmentDao enrollmentDao, @NonNull Clock clock, @NonNull PackageManager packageManager)54     public SignalsMaintenanceTasksWorker(
55             @NonNull Flags flags,
56             @NonNull ProtectedSignalsDao protectedSignalsDao,
57             @NonNull EncoderLogicHandler encoderLogicHandler,
58             @NonNull EncodedPayloadDao encodedPayloadDao,
59             @NonNull EnrollmentDao enrollmentDao,
60             @NonNull Clock clock,
61             @NonNull PackageManager packageManager) {
62         Objects.requireNonNull(flags);
63         Objects.requireNonNull(protectedSignalsDao);
64         Objects.requireNonNull(enrollmentDao);
65         Objects.requireNonNull(clock);
66         Objects.requireNonNull(packageManager);
67 
68         mFlags = flags;
69         mProtectedSignalsDao = protectedSignalsDao;
70         mEnrollmentDao = enrollmentDao;
71         mEncodedPayloadDao = encodedPayloadDao;
72         mEncoderLogicHandler = encoderLogicHandler;
73         mClock = clock;
74         mPackageManager = packageManager;
75     }
76 
SignalsMaintenanceTasksWorker(@onNull Context context)77     private SignalsMaintenanceTasksWorker(@NonNull Context context) {
78         Objects.requireNonNull(context);
79         mFlags = FlagsFactory.getFlags();
80         mProtectedSignalsDao = ProtectedSignalsDatabase.getInstance().protectedSignalsDao();
81         mEnrollmentDao = EnrollmentDao.getInstance();
82         mEncoderLogicHandler = new EncoderLogicHandler(context);
83         mEncodedPayloadDao = ProtectedSignalsDatabase.getInstance().getEncodedPayloadDao();
84         mClock = Clock.systemUTC();
85         mPackageManager = context.getPackageManager();
86     }
87 
88     /** Creates a new instance of {@link SignalsMaintenanceTasksWorker}. */
create(@onNull Context context)89     public static SignalsMaintenanceTasksWorker create(@NonNull Context context) {
90         Objects.requireNonNull(context);
91         return new SignalsMaintenanceTasksWorker(context);
92     }
93 
94     /**
95      * Clears invalid signals from the protected signals table. Not flagged since the job will only
96      * run if protected signals is enabled.
97      *
98      * <ul>
99      *   <li>Expired signals
100      *   <li>Disallowed buyer signals
101      *   <li>Disallowed source app signals
102      *   <li>Uninstalled source app signals
103      *       <p>Also clears data for disallowed buyers and expired encoding related information such
104      *       as:
105      *       <ul>
106      *         <li>Encoder end-point
107      *         <li>Encoding logic
108      *         <li>Encoded Signals payload
109      *       </ul>
110      */
clearInvalidProtectedSignalsData()111     public void clearInvalidProtectedSignalsData() {
112         Instant now = mClock.instant();
113         Instant expirationInstant = now.minusSeconds(ProtectedSignal.EXPIRATION_SECONDS);
114         clearInvalidSignals(expirationInstant, now);
115         clearInvalidEncoders(expirationInstant);
116         clearInvalidEncodedPayloads(expirationInstant);
117     }
118 
119     @VisibleForTesting
clearInvalidSignals(Instant expirationInstant, Instant now)120     void clearInvalidSignals(Instant expirationInstant, Instant now) {
121 
122         sLogger.v("Clearing expired signals older than %s", expirationInstant);
123         int numExpiredSignals =
124                 mProtectedSignalsDao.deleteExpiredSignalsAndUpdateSignalsUpdateMetadata(
125                         expirationInstant, now);
126         sLogger.v("Cleared %d expired signals", numExpiredSignals);
127 
128         // Read from flags directly, since this maintenance task worker is attached to a background
129         //  job with unknown lifetime
130         if (mFlags.getDisableFledgeEnrollmentCheck()) {
131             sLogger.v(
132                     "FLEDGE enrollment check disabled; skipping disallowed buyer signal"
133                             + " maintenance");
134         } else {
135             sLogger.v("Clearing signals for disallowed buyer ad techs");
136             int numDisallowedBuyerEvents =
137                     mProtectedSignalsDao.deleteDisallowedBuyerSignals(mEnrollmentDao);
138             sLogger.v("Cleared %d signals for disallowed buyer ad techs", numDisallowedBuyerEvents);
139         }
140 
141         sLogger.v("Clearing signals for disallowed source apps");
142         int numDisallowedSourceAppSignals =
143                 mProtectedSignalsDao.deleteAllDisallowedPackageSignalsAndUpdateSignalUpdateMetadata(
144                         mPackageManager, mFlags, now);
145         sLogger.v("Cleared %d signals for disallowed source apps", numDisallowedSourceAppSignals);
146     }
147 
148     @VisibleForTesting
clearInvalidEncoders(Instant expirationInstant)149     void clearInvalidEncoders(Instant expirationInstant) {
150 
151         Set<AdTechIdentifier> buyersWithConsentRevoked = new HashSet<>();
152         if (mFlags.getDisableFledgeEnrollmentCheck()) {
153             sLogger.v(
154                     "FLEDGE enrollment check disabled; skipping disallowed buyer encoding logic"
155                             + " maintenance");
156         } else {
157             sLogger.v("Gathering buyers with consent revoked");
158             List<AdTechIdentifier> registeredBuyers = mEncoderLogicHandler.getBuyersWithEncoders();
159             buyersWithConsentRevoked = getBuyersWithRevokedConsent(new HashSet<>(registeredBuyers));
160         }
161 
162         sLogger.v("Clearing expired encoders older than %s", expirationInstant);
163         Set<AdTechIdentifier> buyersWithStaleEncoders =
164                 new HashSet<>(mEncoderLogicHandler.getBuyersWithStaleEncoders(expirationInstant));
165 
166         // Union of two sets with revoked buyers and buyers with stale encoders
167         buyersWithStaleEncoders.addAll(buyersWithConsentRevoked);
168         mEncoderLogicHandler.deleteEncodersForBuyers(buyersWithStaleEncoders);
169     }
170 
171     @VisibleForTesting
clearInvalidEncodedPayloads(Instant expirationInstant)172     void clearInvalidEncodedPayloads(Instant expirationInstant) {
173 
174         if (mFlags.getDisableFledgeEnrollmentCheck()) {
175             sLogger.v(
176                     "FLEDGE enrollment check disabled; skipping disallowed buyer encoded payload"
177                             + " maintenance");
178         } else {
179             sLogger.v("Gathering buyers with consent revoked");
180             List<AdTechIdentifier> buyersWithEncodedPayloads =
181                     mEncodedPayloadDao.getAllBuyersWithEncodedPayloads();
182             Set<AdTechIdentifier> buyersWithConsentRevoked =
183                     getBuyersWithRevokedConsent(new HashSet<>(buyersWithEncodedPayloads));
184             for (AdTechIdentifier revokedBuyer : buyersWithConsentRevoked) {
185                 mEncodedPayloadDao.deleteEncodedPayload(revokedBuyer);
186             }
187         }
188 
189         sLogger.v("Clearing expired encodings older than %s", expirationInstant);
190         mEncodedPayloadDao.deleteEncodedPayloadsBeforeTime(expirationInstant);
191     }
192 
getBuyersWithRevokedConsent(Set<AdTechIdentifier> buyers)193     private Set<AdTechIdentifier> getBuyersWithRevokedConsent(Set<AdTechIdentifier> buyers) {
194         Set<AdTechIdentifier> enrolledAdTechs = mEnrollmentDao.getAllFledgeEnrolledAdTechs();
195         buyers.removeAll(enrolledAdTechs);
196         return buyers;
197     }
198 }
199