1 /* 2 * Copyright (C) 2020 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.systemui.classifier; 18 19 import static com.android.systemui.classifier.FalsingModule.IS_FOLDABLE_DEVICE; 20 21 import android.hardware.devicestate.DeviceStateManager.FoldStateListener; 22 import android.util.DisplayMetrics; 23 import android.view.KeyEvent; 24 import android.view.MotionEvent; 25 import android.view.MotionEvent.PointerCoords; 26 import android.view.MotionEvent.PointerProperties; 27 28 import com.android.systemui.dagger.SysUISingleton; 29 import com.android.systemui.dock.DockManager; 30 import com.android.systemui.statusbar.policy.BatteryController; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 35 import javax.inject.Inject; 36 import javax.inject.Named; 37 38 /** 39 * Acts as a cache and utility class for FalsingClassifiers. 40 */ 41 @SysUISingleton 42 public class FalsingDataProvider { 43 44 private static final long MOTION_EVENT_AGE_MS = 1000; 45 private static final long KEY_EVENT_AGE_MS = 500; 46 private static final long DROP_EVENT_THRESHOLD_MS = 50; 47 private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI); 48 49 private final int mWidthPixels; 50 private final int mHeightPixels; 51 private BatteryController mBatteryController; 52 private final FoldStateListener mFoldStateListener; 53 private final DockManager mDockManager; 54 private boolean mIsFoldableDevice; 55 private final float mXdpi; 56 private final float mYdpi; 57 private final List<SessionListener> mSessionListeners = new ArrayList<>(); 58 private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>(); 59 private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>(); 60 61 private TimeLimitedInputEventBuffer<MotionEvent> mRecentMotionEvents = 62 new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS); 63 private final TimeLimitedInputEventBuffer<KeyEvent> mRecentKeyEvents = 64 new TimeLimitedInputEventBuffer<>(KEY_EVENT_AGE_MS); 65 private List<MotionEvent> mPriorMotionEvents = new ArrayList<>(); 66 67 private boolean mDirty = true; 68 69 private float mAngle = 0; 70 private MotionEvent mFirstRecentMotionEvent; 71 private MotionEvent mLastMotionEvent; 72 private boolean mDropLastEvent; 73 private boolean mJustUnlockedWithFace; 74 private boolean mA11YAction; 75 76 @Inject FalsingDataProvider( DisplayMetrics displayMetrics, BatteryController batteryController, FoldStateListener foldStateListener, DockManager dockManager, @Named(IS_FOLDABLE_DEVICE) boolean isFoldableDevice)77 public FalsingDataProvider( 78 DisplayMetrics displayMetrics, 79 BatteryController batteryController, 80 FoldStateListener foldStateListener, 81 DockManager dockManager, 82 @Named(IS_FOLDABLE_DEVICE) boolean isFoldableDevice) { 83 mXdpi = displayMetrics.xdpi; 84 mYdpi = displayMetrics.ydpi; 85 mWidthPixels = displayMetrics.widthPixels; 86 mHeightPixels = displayMetrics.heightPixels; 87 mBatteryController = batteryController; 88 mFoldStateListener = foldStateListener; 89 mDockManager = dockManager; 90 mIsFoldableDevice = isFoldableDevice; 91 92 FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi()); 93 FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels()); 94 } 95 onKeyEvent(KeyEvent keyEvent)96 void onKeyEvent(KeyEvent keyEvent) { 97 mRecentKeyEvents.add(keyEvent); 98 } 99 onMotionEvent(MotionEvent motionEvent)100 void onMotionEvent(MotionEvent motionEvent) { 101 List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent); 102 FalsingClassifier.logVerbose("Unpacked into: " + motionEvents.size()); 103 if (BrightLineFalsingManager.DEBUG) { 104 for (MotionEvent m : motionEvents) { 105 FalsingClassifier.logVerbose( 106 "x,y,t: " + m.getX() + "," + m.getY() + "," + m.getEventTime()); 107 } 108 } 109 110 if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { 111 // Ensure prior gesture was completed. May be a no-op. 112 completePriorGesture(); 113 } 114 115 // Drop the gesture closing event if it is close in time to a previous ACTION_MOVE event. 116 // The reason is that the closing ACTION_UP event of a swipe can be a bit offseted from the 117 // previous ACTION_MOVE event and when it happens, it makes some classifiers fail. 118 mDropLastEvent = shouldDropEvent(motionEvent); 119 120 if (!motionEvents.isEmpty() && !mRecentKeyEvents.isEmpty()) { 121 recycleAndClearRecentKeyEvents(); 122 } 123 124 mRecentMotionEvents.addAll(motionEvents); 125 126 FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size()); 127 128 mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent)); 129 130 // We explicitly do not "finalize" a gesture on UP or CANCEL events. 131 // We wait for the next gesture to start before marking the prior gesture as complete. This 132 // has multiple benefits. First, it makes it trivial to track the "current" or "recent" 133 // gesture, as it will always be found in mRecentMotionEvents. Second, and most importantly, 134 // it ensures that the current gesture doesn't get added to this HistoryTracker before it 135 // is analyzed. 136 137 mDirty = true; 138 } 139 onMotionEventComplete()140 void onMotionEventComplete() { 141 if (mRecentMotionEvents.isEmpty()) { 142 return; 143 } 144 int action = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getActionMasked(); 145 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 146 completePriorGesture(); 147 } 148 } 149 completePriorGesture()150 private void completePriorGesture() { 151 if (!mRecentMotionEvents.isEmpty()) { 152 mGestureFinalizedListeners.forEach(listener -> listener.onGestureFinalized( 153 mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime())); 154 155 mPriorMotionEvents = mRecentMotionEvents; 156 mRecentMotionEvents = new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS); 157 } 158 mDropLastEvent = false; 159 mA11YAction = false; 160 } 161 162 /** Returns screen width in pixels. */ getWidthPixels()163 public int getWidthPixels() { 164 return mWidthPixels; 165 } 166 167 /** Returns screen height in pixels. */ getHeightPixels()168 public int getHeightPixels() { 169 return mHeightPixels; 170 } 171 getXdpi()172 public float getXdpi() { 173 return mXdpi; 174 } 175 getYdpi()176 public float getYdpi() { 177 return mYdpi; 178 } 179 180 /** 181 * Get the {@link MotionEvent}s of the most recent gesture. 182 * 183 * Note that this list may not include the last recorded event. 184 * @see #mDropLastEvent 185 */ getRecentMotionEvents()186 public List<MotionEvent> getRecentMotionEvents() { 187 if (!mDropLastEvent || mRecentMotionEvents.isEmpty()) { 188 return mRecentMotionEvents; 189 } else { 190 return mRecentMotionEvents.subList(0, mRecentMotionEvents.size() - 1); 191 } 192 } 193 getPriorMotionEvents()194 public List<MotionEvent> getPriorMotionEvents() { 195 return mPriorMotionEvents; 196 } 197 198 /** 199 * Get the first recorded {@link MotionEvent} of the most recent gesture. 200 * 201 * Note that MotionEvents are not kept forever. As a gesture gets longer in duration, older 202 * MotionEvents may expire and be ejected. 203 */ getFirstRecentMotionEvent()204 public MotionEvent getFirstRecentMotionEvent() { 205 recalculateData(); 206 return mFirstRecentMotionEvent; 207 } 208 209 /** 210 * Get the last {@link MotionEvent} of the most recent gesture. 211 * 212 * Note that this may be the event prior to the last recorded event. 213 * @see #mDropLastEvent 214 */ getLastMotionEvent()215 public MotionEvent getLastMotionEvent() { 216 recalculateData(); 217 return mLastMotionEvent; 218 } 219 220 /** 221 * Returns the angle between the first and last point of the recent points. 222 * 223 * The angle will be in radians, always be between 0 and 2*PI, inclusive. 224 */ getAngle()225 public float getAngle() { 226 recalculateData(); 227 return mAngle; 228 } 229 230 /** Returns if the most recent gesture is more horizontal than vertical. */ isHorizontal()231 public boolean isHorizontal() { 232 recalculateData(); 233 if (mRecentMotionEvents.isEmpty()) { 234 return false; 235 } 236 237 return Math.abs(mFirstRecentMotionEvent.getX() - mLastMotionEvent.getX()) > Math 238 .abs(mFirstRecentMotionEvent.getY() - mLastMotionEvent.getY()); 239 } 240 241 /** 242 * Is the most recent gesture more right than left. 243 * 244 * This does not mean the gesture is mostly horizontal. Simply that it ended at least one pixel 245 * to the right of where it started. See also {@link #isHorizontal()}. 246 */ isRight()247 public boolean isRight() { 248 recalculateData(); 249 if (mRecentMotionEvents.isEmpty()) { 250 return false; 251 } 252 253 return mLastMotionEvent.getX() > mFirstRecentMotionEvent.getX(); 254 } 255 256 /** Returns if the most recent gesture is more vertical than horizontal. */ isVertical()257 public boolean isVertical() { 258 return !isHorizontal(); 259 } 260 261 /** 262 * Is the most recent gesture more up than down. 263 * 264 * This does not mean the gesture is mostly vertical. Simply that it ended at least one pixel 265 * higher than it started. See also {@link #isVertical()}. 266 */ isUp()267 public boolean isUp() { 268 recalculateData(); 269 if (mRecentMotionEvents.isEmpty()) { 270 return false; 271 } 272 273 return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY(); 274 } 275 276 /** 277 * If it's a specific set of keys as collected from {@link FalsingCollector} 278 */ isFromKeyboard()279 public boolean isFromKeyboard() { 280 return !mRecentKeyEvents.isEmpty(); 281 } 282 isFromTrackpad()283 public boolean isFromTrackpad() { 284 if (mRecentMotionEvents.isEmpty()) { 285 return false; 286 } 287 288 int classification = mRecentMotionEvents.get(0).getClassification(); 289 return classification == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE 290 || classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE; 291 } 292 recalculateData()293 private void recalculateData() { 294 if (!mDirty) { 295 return; 296 } 297 298 List<MotionEvent> recentMotionEvents = getRecentMotionEvents(); 299 if (recentMotionEvents.isEmpty()) { 300 mFirstRecentMotionEvent = null; 301 mLastMotionEvent = null; 302 } else { 303 mFirstRecentMotionEvent = recentMotionEvents.get(0); 304 mLastMotionEvent = recentMotionEvents.get(recentMotionEvents.size() - 1); 305 } 306 307 calculateAngleInternal(); 308 309 mDirty = false; 310 } 311 shouldDropEvent(MotionEvent event)312 private boolean shouldDropEvent(MotionEvent event) { 313 if (mRecentMotionEvents.size() < 3) return false; 314 315 MotionEvent lastEvent = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1); 316 boolean isCompletingGesture = event.getActionMasked() == MotionEvent.ACTION_UP 317 && lastEvent.getActionMasked() == MotionEvent.ACTION_MOVE; 318 boolean isRecentEvent = 319 event.getEventTime() - lastEvent.getEventTime() < DROP_EVENT_THRESHOLD_MS; 320 return isCompletingGesture && isRecentEvent; 321 } 322 323 private void calculateAngleInternal() { 324 if (mRecentMotionEvents.size() < 2) { 325 mAngle = Float.MAX_VALUE; 326 } else { 327 float lastX = mLastMotionEvent.getX() - mFirstRecentMotionEvent.getX(); 328 float lastY = mLastMotionEvent.getY() - mFirstRecentMotionEvent.getY(); 329 330 mAngle = (float) Math.atan2(lastY, lastX); 331 while (mAngle < 0) { 332 mAngle += THREE_HUNDRED_SIXTY_DEG; 333 } 334 while (mAngle > THREE_HUNDRED_SIXTY_DEG) { 335 mAngle -= THREE_HUNDRED_SIXTY_DEG; 336 } 337 } 338 } 339 340 private void recycleAndClearRecentKeyEvents() { 341 for (KeyEvent ev : mRecentKeyEvents) { 342 ev.recycle(); 343 } 344 345 mRecentKeyEvents.clear(); 346 } 347 348 private List<MotionEvent> unpackMotionEvent(MotionEvent motionEvent) { 349 List<MotionEvent> motionEvents = new ArrayList<>(); 350 List<PointerProperties> pointerPropertiesList = new ArrayList<>(); 351 int pointerCount = motionEvent.getPointerCount(); 352 for (int i = 0; i < pointerCount; i++) { 353 PointerProperties pointerProperties = new PointerProperties(); 354 motionEvent.getPointerProperties(i, pointerProperties); 355 pointerPropertiesList.add(pointerProperties); 356 } 357 PointerProperties[] pointerPropertiesArray = new PointerProperties[pointerPropertiesList 358 .size()]; 359 pointerPropertiesList.toArray(pointerPropertiesArray); 360 361 int historySize = motionEvent.getHistorySize(); 362 for (int i = 0; i < historySize; i++) { 363 List<PointerCoords> pointerCoordsList = new ArrayList<>(); 364 for (int j = 0; j < pointerCount; j++) { 365 PointerCoords pointerCoords = new PointerCoords(); 366 motionEvent.getHistoricalPointerCoords(j, i, pointerCoords); 367 pointerCoordsList.add(pointerCoords); 368 } 369 motionEvents.add(MotionEvent.obtain( 370 motionEvent.getDownTime(), 371 motionEvent.getHistoricalEventTime(i), 372 motionEvent.getAction(), 373 pointerCount, 374 pointerPropertiesArray, 375 pointerCoordsList.toArray(new PointerCoords[0]), 376 motionEvent.getMetaState(), 377 motionEvent.getButtonState(), 378 motionEvent.getXPrecision(), 379 motionEvent.getYPrecision(), 380 motionEvent.getDeviceId(), 381 motionEvent.getEdgeFlags(), 382 motionEvent.getSource(), 383 motionEvent.getDisplayId(), 384 motionEvent.getFlags(), 385 motionEvent.getClassification() 386 )); 387 } 388 389 motionEvents.add(MotionEvent.obtainNoHistory(motionEvent)); 390 391 return motionEvents; 392 } 393 394 /** Register a {@link SessionListener}. */ 395 public void addSessionListener(SessionListener listener) { 396 mSessionListeners.add(listener); 397 } 398 399 /** Unregister a {@link SessionListener}. */ 400 public void removeSessionListener(SessionListener listener) { 401 mSessionListeners.remove(listener); 402 } 403 404 /** Register a {@link MotionEventListener}. */ 405 public void addMotionEventListener(MotionEventListener listener) { 406 mMotionEventListeners.add(listener); 407 } 408 409 /** Unegister a {@link MotionEventListener}. */ 410 public void removeMotionEventListener(MotionEventListener listener) { 411 mMotionEventListeners.remove(listener); 412 } 413 414 /** Register a {@link GestureFinalizedListener}. */ 415 public void addGestureCompleteListener(GestureFinalizedListener listener) { 416 mGestureFinalizedListeners.add(listener); 417 } 418 419 /** Unregister a {@link GestureFinalizedListener}. */ 420 public void removeGestureCompleteListener(GestureFinalizedListener listener) { 421 mGestureFinalizedListeners.remove(listener); 422 } 423 424 /** Return whether last gesture was an A11y action. */ 425 public boolean isA11yAction() { 426 return mA11YAction; 427 } 428 429 /** Set whether last gesture was an A11y action. */ 430 public void onA11yAction() { 431 completePriorGesture(); 432 this.mA11YAction = true; 433 } 434 435 void onSessionStarted() { 436 mSessionListeners.forEach(SessionListener::onSessionStarted); 437 } 438 439 void onSessionEnd() { 440 for (MotionEvent ev : mRecentMotionEvents) { 441 ev.recycle(); 442 } 443 444 mRecentMotionEvents.clear(); 445 446 recycleAndClearRecentKeyEvents(); 447 448 mDirty = true; 449 450 mSessionListeners.forEach(SessionListener::onSessionEnded); 451 } 452 453 public boolean isJustUnlockedWithFace() { 454 return mJustUnlockedWithFace; 455 } 456 457 public void setJustUnlockedWithFace(boolean justUnlockedWithFace) { 458 mJustUnlockedWithFace = justUnlockedWithFace; 459 } 460 461 /** Returns true if phone is sitting in a dock or is wirelessly charging. */ 462 public boolean isDocked() { 463 return mBatteryController.isWirelessCharging() || mDockManager.isDocked(); 464 } 465 466 public boolean isUnfolded() { 467 return mIsFoldableDevice && Boolean.FALSE.equals(mFoldStateListener.getFolded()); 468 } 469 470 /** Implement to be alerted abotu the beginning and ending of falsing tracking. */ 471 public interface SessionListener { 472 /** Called when the lock screen is shown and falsing-tracking begins. */ 473 void onSessionStarted(); 474 475 /** Called when the lock screen exits and falsing-tracking ends. */ 476 void onSessionEnded(); 477 } 478 479 /** Callback for receiving {@link android.view.MotionEvent}s as they are reported. */ 480 public interface MotionEventListener { 481 /** */ 482 void onMotionEvent(MotionEvent ev); 483 } 484 485 /** Callback to be alerted when the current gesture ends. */ 486 public interface GestureFinalizedListener { 487 /** 488 * Called just before a new gesture starts. 489 * 490 * Any pending work on a prior gesture can be considered cemented in place. 491 */ 492 void onGestureFinalized(long completionTimeMs); 493 } 494 } 495