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.measurement.inputverification;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.hardware.input.InputManager;
22 import android.view.InputEvent;
23 import android.view.KeyEvent;
24 import android.view.MotionEvent;
25 import android.view.VerifiedInputEvent;
26 
27 import com.android.adservices.service.Flags;
28 import com.android.adservices.service.FlagsFactory;
29 import com.android.adservices.service.measurement.Source;
30 import com.android.adservices.service.stats.AdServicesLogger;
31 import com.android.adservices.service.stats.AdServicesLoggerImpl;
32 import com.android.adservices.service.stats.MeasurementClickVerificationStats;
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import com.google.auto.value.AutoValue;
36 import com.google.common.cache.CacheBuilder;
37 import com.google.common.cache.CacheLoader;
38 import com.google.common.cache.LoadingCache;
39 
40 import java.util.concurrent.TimeUnit;
41 
42 /** Class for handling navigation event verification. */
43 public class ClickVerifier {
44     @NonNull private final InputManager mInputManager;
45     @NonNull private final Flags mFlags;
46 
47     @NonNull private final AdServicesLogger mAdServicesLogger;
48 
49     @NonNull private LoadingCache<VerifiedInputEvent, Long> mVerifiedInputEventsPreviouslyUsed;
50     @NonNull private LoadingCache<MotionEventWrapper, Long> mUnverifiedMotionEventsPreviouslyUsed;
51     @NonNull private LoadingCache<KeyEventWrapper, Long> mUnverifiedKeyEventsPreviouslyUsed;
52 
ClickVerifier(Context context)53     public ClickVerifier(Context context) {
54         mInputManager = context.getSystemService(InputManager.class);
55         mFlags = FlagsFactory.getFlags();
56         mAdServicesLogger = AdServicesLoggerImpl.getInstance();
57         mVerifiedInputEventsPreviouslyUsed =
58                 CacheBuilder.newBuilder()
59                         .expireAfterWrite(
60                                 mFlags.getMeasurementRegistrationInputEventValidWindowMs(),
61                                 TimeUnit.MILLISECONDS)
62                         .build(
63                                 new CacheLoader<>() {
64                                     @NonNull
65                                     @Override
66                                     public Long load(VerifiedInputEvent key) {
67                                         return 0L;
68                                     }
69                                 });
70 
71         mUnverifiedMotionEventsPreviouslyUsed =
72                 CacheBuilder.newBuilder()
73                         .expireAfterWrite(
74                                 mFlags.getMeasurementRegistrationInputEventValidWindowMs(),
75                                 TimeUnit.MILLISECONDS)
76                         .build(
77                                 new CacheLoader<>() {
78                                     @NonNull
79                                     @Override
80                                     public Long load(MotionEventWrapper key) {
81                                         return 0L;
82                                     }
83                                 });
84 
85         mUnverifiedKeyEventsPreviouslyUsed =
86                 CacheBuilder.newBuilder()
87                         .expireAfterWrite(
88                                 mFlags.getMeasurementRegistrationInputEventValidWindowMs(),
89                                 TimeUnit.MILLISECONDS)
90                         .build(
91                                 new CacheLoader<>() {
92                                     @NonNull
93                                     @Override
94                                     public Long load(KeyEventWrapper key) {
95                                         return 0L;
96                                     }
97                                 });
98     }
99 
100     @VisibleForTesting
ClickVerifier( @onNull InputManager inputManager, @NonNull Flags flags, @NonNull AdServicesLogger adServicesLogger)101     ClickVerifier(
102             @NonNull InputManager inputManager,
103             @NonNull Flags flags,
104             @NonNull AdServicesLogger adServicesLogger) {
105         mInputManager = inputManager;
106         mFlags = flags;
107         mAdServicesLogger = adServicesLogger;
108         mVerifiedInputEventsPreviouslyUsed =
109                 CacheBuilder.newBuilder()
110                         .expireAfterWrite(
111                                 mFlags.getMeasurementRegistrationInputEventValidWindowMs(),
112                                 TimeUnit.MILLISECONDS)
113                         .build(
114                                 new CacheLoader<>() {
115                                     @NonNull
116                                     @Override
117                                     public Long load(VerifiedInputEvent key) {
118                                         return 0L;
119                                     }
120                                 });
121 
122         mUnverifiedMotionEventsPreviouslyUsed =
123                 CacheBuilder.newBuilder()
124                         .expireAfterWrite(
125                                 mFlags.getMeasurementRegistrationInputEventValidWindowMs(),
126                                 TimeUnit.MILLISECONDS)
127                         .build(
128                                 new CacheLoader<>() {
129                                     @NonNull
130                                     @Override
131                                     public Long load(MotionEventWrapper key) {
132                                         return 0L;
133                                     }
134                                 });
135 
136         mUnverifiedKeyEventsPreviouslyUsed =
137                 CacheBuilder.newBuilder()
138                         .expireAfterWrite(
139                                 mFlags.getMeasurementRegistrationInputEventValidWindowMs(),
140                                 TimeUnit.MILLISECONDS)
141                         .build(
142                                 new CacheLoader<>() {
143                                     @NonNull
144                                     @Override
145                                     public Long load(KeyEventWrapper key) {
146                                         return 0L;
147                                     }
148                                 });
149     }
150 
151     /**
152      * Checks if the {@link InputEvent} passed with a click registration can be verified. In order
153      * for an InputEvent to be verified:
154      *
155      * <p>1. The event time of the InputEvent has to be within {@link
156      * com.android.adservices.service.PhFlags#MEASUREMENT_REGISTRATION_INPUT_EVENT_VALID_WINDOW_MS }
157      * of the API call.
158      *
159      * <p>2. The InputEvent has to be verified by the system {@link InputManager}.
160      *
161      * <p>3. The InputEvent must be used less than {@link
162      * com.android.adservices.service.PhFlags#MEASUREMENT_MAX_SOURCES_PER_CLICK} times previously.
163      *
164      * @param event The InputEvent passed with the registration call.
165      * @param registerTimestamp The time of the registration call.
166      * @return Whether the InputEvent can be verified.
167      */
isInputEventVerifiable( @onNull InputEvent event, long registerTimestamp, String sourceRegistrant)168     public boolean isInputEventVerifiable(
169             @NonNull InputEvent event, long registerTimestamp, String sourceRegistrant) {
170         boolean isInputEventVerified = true;
171         MeasurementClickVerificationStats.Builder clickVerificationStatsBuilder =
172                 MeasurementClickVerificationStats.builder();
173         clickVerificationStatsBuilder.setInputEventPresent(true);
174 
175         if (!isInputEventVerifiableBySystem(event, clickVerificationStatsBuilder)) {
176             isInputEventVerified = false;
177         }
178 
179         if (!isInputEventWithinValidTimeRange(
180                 registerTimestamp, event, clickVerificationStatsBuilder)) {
181             isInputEventVerified = false;
182         }
183 
184         if (!isInputEventUnderUsageLimit(event, clickVerificationStatsBuilder)) {
185             isInputEventVerified = false;
186         }
187 
188         clickVerificationStatsBuilder.setSourceType(
189                 (isInputEventVerified ? Source.SourceType.NAVIGATION : Source.SourceType.EVENT)
190                         .getIntValue());
191         clickVerificationStatsBuilder.setSourceRegistrant(sourceRegistrant);
192 
193         logClickVerificationStats(clickVerificationStatsBuilder, mAdServicesLogger);
194 
195         return isInputEventVerified;
196     }
197 
198     /** Checks whether the InputEvent can be verified by the system. */
199     @VisibleForTesting
isInputEventVerifiableBySystem( InputEvent event, MeasurementClickVerificationStats.Builder stats)200     boolean isInputEventVerifiableBySystem(
201             InputEvent event, MeasurementClickVerificationStats.Builder stats) {
202         boolean isVerifiedBySystem = mInputManager.verifyInputEvent(event) != null;
203 
204         stats.setSystemClickVerificationEnabled(mFlags.getMeasurementIsClickVerifiedByInputEvent());
205         stats.setSystemClickVerificationSuccessful(isVerifiedBySystem);
206         return !mFlags.getMeasurementIsClickVerifiedByInputEvent() || isVerifiedBySystem;
207     }
208 
209     /**
210      * Checks whether the timestamp on the InputEvent and the time of the API call are within the
211      * accepted range defined at {@link
212      * com.android.adservices.service.PhFlags#MEASUREMENT_REGISTRATION_INPUT_EVENT_VALID_WINDOW_MS}
213      */
214     @VisibleForTesting
isInputEventWithinValidTimeRange( long registerTimestamp, InputEvent event, MeasurementClickVerificationStats.Builder stats)215     boolean isInputEventWithinValidTimeRange(
216             long registerTimestamp,
217             InputEvent event,
218             MeasurementClickVerificationStats.Builder stats) {
219         long inputEventDelay = registerTimestamp - event.getEventTime();
220         stats.setInputEventDelayMillis(inputEventDelay);
221         stats.setValidDelayWindowMillis(mFlags.getMeasurementRegistrationInputEventValidWindowMs());
222         return inputEventDelay <= mFlags.getMeasurementRegistrationInputEventValidWindowMs();
223     }
224 
225     /**
226      * Checks whether the provided InputEvent has been used fewer times than the limit defined at
227      * {@link com.android.adservices.service.PhFlags#MEASUREMENT_MAX_SOURCES_PER_CLICK}
228      */
229     @VisibleForTesting
isInputEventUnderUsageLimit( InputEvent event, MeasurementClickVerificationStats.Builder stats)230     boolean isInputEventUnderUsageLimit(
231             InputEvent event, MeasurementClickVerificationStats.Builder stats) {
232         stats.setClickDeduplicationEnabled(mFlags.getMeasurementIsClickDeduplicationEnabled());
233         stats.setClickDeduplicationEnforced(mFlags.getMeasurementIsClickDeduplicationEnforced());
234         stats.setMaxSourcesPerClick(mFlags.getMeasurementMaxSourcesPerClick());
235 
236         if (!mFlags.getMeasurementIsClickDeduplicationEnabled()) {
237             stats.setCurrentRegistrationUnderClickDeduplicationLimit(/* value */ true);
238             return true;
239         }
240 
241         long numTimesPreviouslyUsed = 0;
242         VerifiedInputEvent verifiedInputEvent = mInputManager.verifyInputEvent(event);
243         if (verifiedInputEvent != null) {
244             // VerifiedInputEvents will be equal even if the InputEvents passed to the InputManager
245             // is not.
246             numTimesPreviouslyUsed =
247                     mVerifiedInputEventsPreviouslyUsed.getUnchecked(verifiedInputEvent);
248             mVerifiedInputEventsPreviouslyUsed.put(verifiedInputEvent, numTimesPreviouslyUsed + 1);
249         } else {
250             // A copy of an InputEvent object won't be equal to the original. If we can't get a
251             // VerifiedInputEvent, we have to use a wrapper class and compare the coordinates and
252             // the down time for a MotionEvent or the key code and the downtime for a KeyEvent.
253             if (event instanceof MotionEvent) {
254                 MotionEvent motionEvent = (MotionEvent) event;
255                 MotionEventWrapper unverifiedMotionEvent =
256                         MotionEventWrapper.builder()
257                                 .setRawX(motionEvent.getRawX())
258                                 .setRawY(motionEvent.getRawY())
259                                 .setDownTime(motionEvent.getDownTime())
260                                 .setAction(motionEvent.getAction())
261                                 .build();
262                 numTimesPreviouslyUsed =
263                         mUnverifiedMotionEventsPreviouslyUsed.getUnchecked(unverifiedMotionEvent);
264                 mUnverifiedMotionEventsPreviouslyUsed.put(
265                         unverifiedMotionEvent, numTimesPreviouslyUsed + 1);
266             } else if (event instanceof KeyEvent) {
267                 KeyEvent keyEvent = (KeyEvent) event;
268                 KeyEventWrapper unverifiedKeyEvent =
269                         KeyEventWrapper.builder()
270                                 .setKeyCode(keyEvent.getKeyCode())
271                                 .setDownTime(keyEvent.getDownTime())
272                                 .setAction(keyEvent.getAction())
273                                 .build();
274                 numTimesPreviouslyUsed =
275                         mUnverifiedKeyEventsPreviouslyUsed.getUnchecked(unverifiedKeyEvent);
276                 mUnverifiedKeyEventsPreviouslyUsed.put(
277                         unverifiedKeyEvent, numTimesPreviouslyUsed + 1);
278             }
279         }
280 
281         stats.setCurrentRegistrationUnderClickDeduplicationLimit(
282                 (numTimesPreviouslyUsed < mFlags.getMeasurementMaxSourcesPerClick()));
283 
284         return !mFlags.getMeasurementIsClickDeduplicationEnforced()
285                 || (numTimesPreviouslyUsed < mFlags.getMeasurementMaxSourcesPerClick());
286     }
287 
288     private void logClickVerificationStats(
289             MeasurementClickVerificationStats.Builder stats, AdServicesLogger adServicesLogger) {
290         adServicesLogger.logMeasurementClickVerificationStats(stats.build());
291     }
292 
293     @AutoValue
294     abstract static class MotionEventWrapper {
295         abstract float rawX();
296 
297         abstract float rawY();
298 
299         abstract long downTime();
300 
301         abstract int action();
302 
303         static Builder builder() {
304             return new AutoValue_ClickVerifier_MotionEventWrapper.Builder();
305         }
306 
307         @AutoValue.Builder
308         abstract static class Builder {
309             abstract Builder setRawX(float value);
310 
311             abstract Builder setRawY(float value);
312 
313             abstract Builder setDownTime(long value);
314 
315             abstract Builder setAction(int value);
316 
317             abstract MotionEventWrapper build();
318         }
319     }
320 
321     @AutoValue
322     abstract static class KeyEventWrapper {
323         abstract int keyCode();
324 
325         abstract long downTime();
326 
327         abstract int action();
328 
329         static Builder builder() {
330             return new AutoValue_ClickVerifier_KeyEventWrapper.Builder();
331         }
332 
333         @AutoValue.Builder
334         abstract static class Builder {
335             abstract Builder setKeyCode(int value);
336 
337             abstract Builder setDownTime(long value);
338 
339             abstract Builder setAction(int value);
340 
341             abstract KeyEventWrapper build();
342         }
343     }
344 }
345