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