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 android.util.DisplayMetrics; 20 import android.view.MotionEvent; 21 import android.view.MotionEvent.PointerCoords; 22 import android.view.MotionEvent.PointerProperties; 23 24 import com.android.systemui.dagger.SysUISingleton; 25 import com.android.systemui.dock.DockManager; 26 import com.android.systemui.statusbar.policy.BatteryController; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 import javax.inject.Inject; 32 33 /** 34 * Acts as a cache and utility class for FalsingClassifiers. 35 */ 36 @SysUISingleton 37 public class FalsingDataProvider { 38 39 private static final long MOTION_EVENT_AGE_MS = 1000; 40 private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI); 41 42 private final int mWidthPixels; 43 private final int mHeightPixels; 44 private BatteryController mBatteryController; 45 private final DockManager mDockManager; 46 private final float mXdpi; 47 private final float mYdpi; 48 private final List<SessionListener> mSessionListeners = new ArrayList<>(); 49 private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>(); 50 private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>(); 51 52 private TimeLimitedMotionEventBuffer mRecentMotionEvents = 53 new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS); 54 private List<MotionEvent> mPriorMotionEvents = new ArrayList<>(); 55 56 private boolean mDirty = true; 57 58 private float mAngle = 0; 59 private MotionEvent mFirstRecentMotionEvent; 60 private MotionEvent mLastMotionEvent; 61 private boolean mJustUnlockedWithFace; 62 63 @Inject FalsingDataProvider( DisplayMetrics displayMetrics, BatteryController batteryController, DockManager dockManager)64 public FalsingDataProvider( 65 DisplayMetrics displayMetrics, 66 BatteryController batteryController, 67 DockManager dockManager) { 68 mXdpi = displayMetrics.xdpi; 69 mYdpi = displayMetrics.ydpi; 70 mWidthPixels = displayMetrics.widthPixels; 71 mHeightPixels = displayMetrics.heightPixels; 72 mBatteryController = batteryController; 73 mDockManager = dockManager; 74 75 FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi()); 76 FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels()); 77 } 78 onMotionEvent(MotionEvent motionEvent)79 void onMotionEvent(MotionEvent motionEvent) { 80 List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent); 81 FalsingClassifier.logDebug("Unpacked into: " + motionEvents.size()); 82 if (BrightLineFalsingManager.DEBUG) { 83 for (MotionEvent m : motionEvents) { 84 FalsingClassifier.logDebug( 85 "x,y,t: " + m.getX() + "," + m.getY() + "," + m.getEventTime()); 86 } 87 } 88 89 if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { 90 // Ensure prior gesture was completed. May be a no-op. 91 completePriorGesture(); 92 } 93 mRecentMotionEvents.addAll(motionEvents); 94 95 FalsingClassifier.logDebug("Size: " + mRecentMotionEvents.size()); 96 97 mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent)); 98 99 // We explicitly do not "finalize" a gesture on UP or CANCEL events. 100 // We wait for the next gesture to start before marking the prior gesture as complete. This 101 // has multiple benefits. First, it makes it trivial to track the "current" or "recent" 102 // gesture, as it will always be found in mRecentMotionEvents. Second, and most importantly, 103 // it ensures that the current gesture doesn't get added to this HistoryTracker before it 104 // is analyzed. 105 106 mDirty = true; 107 } 108 onMotionEventComplete()109 void onMotionEventComplete() { 110 if (mRecentMotionEvents.isEmpty()) { 111 return; 112 } 113 int action = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getActionMasked(); 114 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 115 completePriorGesture(); 116 } 117 } 118 completePriorGesture()119 private void completePriorGesture() { 120 if (!mRecentMotionEvents.isEmpty()) { 121 mGestureFinalizedListeners.forEach(listener -> listener.onGestureFinalized( 122 mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime())); 123 124 mPriorMotionEvents = mRecentMotionEvents; 125 mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS); 126 } 127 } 128 129 /** Returns screen width in pixels. */ getWidthPixels()130 public int getWidthPixels() { 131 return mWidthPixels; 132 } 133 134 /** Returns screen height in pixels. */ getHeightPixels()135 public int getHeightPixels() { 136 return mHeightPixels; 137 } 138 getXdpi()139 public float getXdpi() { 140 return mXdpi; 141 } 142 getYdpi()143 public float getYdpi() { 144 return mYdpi; 145 } 146 getRecentMotionEvents()147 public List<MotionEvent> getRecentMotionEvents() { 148 return mRecentMotionEvents; 149 } 150 getPriorMotionEvents()151 public List<MotionEvent> getPriorMotionEvents() { 152 return mPriorMotionEvents; 153 } 154 155 /** 156 * Get the first recorded {@link MotionEvent} of the most recent gesture. 157 * 158 * Note that MotionEvents are not kept forever. As a gesture gets longer in duration, older 159 * MotionEvents may expire and be ejected. 160 */ getFirstRecentMotionEvent()161 public MotionEvent getFirstRecentMotionEvent() { 162 recalculateData(); 163 return mFirstRecentMotionEvent; 164 } 165 166 /** Get the last recorded {@link MotionEvent}. */ getLastMotionEvent()167 public MotionEvent getLastMotionEvent() { 168 recalculateData(); 169 return mLastMotionEvent; 170 } 171 172 /** 173 * Returns the angle between the first and last point of the recent points. 174 * 175 * The angle will be in radians, always be between 0 and 2*PI, inclusive. 176 */ getAngle()177 public float getAngle() { 178 recalculateData(); 179 return mAngle; 180 } 181 182 /** Returns if the most recent gesture is more horizontal than vertical. */ isHorizontal()183 public boolean isHorizontal() { 184 recalculateData(); 185 if (mRecentMotionEvents.isEmpty()) { 186 return false; 187 } 188 189 return Math.abs(mFirstRecentMotionEvent.getX() - mLastMotionEvent.getX()) > Math 190 .abs(mFirstRecentMotionEvent.getY() - mLastMotionEvent.getY()); 191 } 192 193 /** 194 * Is the most recent gesture more right than left. 195 * 196 * This does not mean the gesture is mostly horizontal. Simply that it ended at least one pixel 197 * to the right of where it started. See also {@link #isHorizontal()}. 198 */ isRight()199 public boolean isRight() { 200 recalculateData(); 201 if (mRecentMotionEvents.isEmpty()) { 202 return false; 203 } 204 205 return mLastMotionEvent.getX() > mFirstRecentMotionEvent.getX(); 206 } 207 208 /** Returns if the most recent gesture is more vertical than horizontal. */ isVertical()209 public boolean isVertical() { 210 return !isHorizontal(); 211 } 212 213 /** 214 * Is the most recent gesture more up than down. 215 * 216 * This does not mean the gesture is mostly vertical. Simply that it ended at least one pixel 217 * higher than it started. See also {@link #isVertical()}. 218 */ isUp()219 public boolean isUp() { 220 recalculateData(); 221 if (mRecentMotionEvents.isEmpty()) { 222 return false; 223 } 224 225 return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY(); 226 } 227 recalculateData()228 private void recalculateData() { 229 if (!mDirty) { 230 return; 231 } 232 233 if (mRecentMotionEvents.isEmpty()) { 234 mFirstRecentMotionEvent = null; 235 mLastMotionEvent = null; 236 } else { 237 mFirstRecentMotionEvent = mRecentMotionEvents.get(0); 238 mLastMotionEvent = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1); 239 } 240 241 calculateAngleInternal(); 242 243 mDirty = false; 244 } 245 calculateAngleInternal()246 private void calculateAngleInternal() { 247 if (mRecentMotionEvents.size() < 2) { 248 mAngle = Float.MAX_VALUE; 249 } else { 250 float lastX = mLastMotionEvent.getX() - mFirstRecentMotionEvent.getX(); 251 float lastY = mLastMotionEvent.getY() - mFirstRecentMotionEvent.getY(); 252 253 mAngle = (float) Math.atan2(lastY, lastX); 254 while (mAngle < 0) { 255 mAngle += THREE_HUNDRED_SIXTY_DEG; 256 } 257 while (mAngle > THREE_HUNDRED_SIXTY_DEG) { 258 mAngle -= THREE_HUNDRED_SIXTY_DEG; 259 } 260 } 261 } 262 unpackMotionEvent(MotionEvent motionEvent)263 private List<MotionEvent> unpackMotionEvent(MotionEvent motionEvent) { 264 List<MotionEvent> motionEvents = new ArrayList<>(); 265 List<PointerProperties> pointerPropertiesList = new ArrayList<>(); 266 int pointerCount = motionEvent.getPointerCount(); 267 for (int i = 0; i < pointerCount; i++) { 268 PointerProperties pointerProperties = new PointerProperties(); 269 motionEvent.getPointerProperties(i, pointerProperties); 270 pointerPropertiesList.add(pointerProperties); 271 } 272 PointerProperties[] pointerPropertiesArray = new PointerProperties[pointerPropertiesList 273 .size()]; 274 pointerPropertiesList.toArray(pointerPropertiesArray); 275 276 int historySize = motionEvent.getHistorySize(); 277 for (int i = 0; i < historySize; i++) { 278 List<PointerCoords> pointerCoordsList = new ArrayList<>(); 279 for (int j = 0; j < pointerCount; j++) { 280 PointerCoords pointerCoords = new PointerCoords(); 281 motionEvent.getHistoricalPointerCoords(j, i, pointerCoords); 282 pointerCoordsList.add(pointerCoords); 283 } 284 motionEvents.add(MotionEvent.obtain( 285 motionEvent.getDownTime(), 286 motionEvent.getHistoricalEventTime(i), 287 motionEvent.getAction(), 288 pointerCount, 289 pointerPropertiesArray, 290 pointerCoordsList.toArray(new PointerCoords[0]), 291 motionEvent.getMetaState(), 292 motionEvent.getButtonState(), 293 motionEvent.getXPrecision(), 294 motionEvent.getYPrecision(), 295 motionEvent.getDeviceId(), 296 motionEvent.getEdgeFlags(), 297 motionEvent.getSource(), 298 motionEvent.getFlags() 299 )); 300 } 301 302 motionEvents.add(MotionEvent.obtainNoHistory(motionEvent)); 303 304 return motionEvents; 305 } 306 307 /** Register a {@link SessionListener}. */ addSessionListener(SessionListener listener)308 public void addSessionListener(SessionListener listener) { 309 mSessionListeners.add(listener); 310 } 311 312 /** Unregister a {@link SessionListener}. */ removeSessionListener(SessionListener listener)313 public void removeSessionListener(SessionListener listener) { 314 mSessionListeners.remove(listener); 315 } 316 317 /** Register a {@link MotionEventListener}. */ addMotionEventListener(MotionEventListener listener)318 public void addMotionEventListener(MotionEventListener listener) { 319 mMotionEventListeners.add(listener); 320 } 321 322 /** Unegister a {@link MotionEventListener}. */ removeMotionEventListener(MotionEventListener listener)323 public void removeMotionEventListener(MotionEventListener listener) { 324 mMotionEventListeners.remove(listener); 325 } 326 327 /** Register a {@link GestureFinalizedListener}. */ addGestureCompleteListener(GestureFinalizedListener listener)328 public void addGestureCompleteListener(GestureFinalizedListener listener) { 329 mGestureFinalizedListeners.add(listener); 330 } 331 332 /** Unregister a {@link GestureFinalizedListener}. */ removeGestureCompleteListener(GestureFinalizedListener listener)333 public void removeGestureCompleteListener(GestureFinalizedListener listener) { 334 mGestureFinalizedListeners.remove(listener); 335 } 336 onSessionStarted()337 void onSessionStarted() { 338 mSessionListeners.forEach(SessionListener::onSessionStarted); 339 } 340 onSessionEnd()341 void onSessionEnd() { 342 for (MotionEvent ev : mRecentMotionEvents) { 343 ev.recycle(); 344 } 345 346 mRecentMotionEvents.clear(); 347 348 mDirty = true; 349 350 mSessionListeners.forEach(SessionListener::onSessionEnded); 351 } 352 isJustUnlockedWithFace()353 public boolean isJustUnlockedWithFace() { 354 return mJustUnlockedWithFace; 355 } 356 setJustUnlockedWithFace(boolean justUnlockedWithFace)357 public void setJustUnlockedWithFace(boolean justUnlockedWithFace) { 358 mJustUnlockedWithFace = justUnlockedWithFace; 359 } 360 361 /** Returns true if phone is sitting in a dock or is wirelessly charging. */ isDocked()362 public boolean isDocked() { 363 return mBatteryController.isWirelessCharging() || mDockManager.isDocked(); 364 } 365 366 /** Implement to be alerted abotu the beginning and ending of falsing tracking. */ 367 public interface SessionListener { 368 /** Called when the lock screen is shown and falsing-tracking begins. */ onSessionStarted()369 void onSessionStarted(); 370 371 /** Called when the lock screen exits and falsing-tracking ends. */ onSessionEnded()372 void onSessionEnded(); 373 } 374 375 /** Callback for receiving {@link android.view.MotionEvent}s as they are reported. */ 376 public interface MotionEventListener { 377 /** */ onMotionEvent(MotionEvent ev)378 void onMotionEvent(MotionEvent ev); 379 } 380 381 /** Callback to be alerted when the current gesture ends. */ 382 public interface GestureFinalizedListener { 383 /** 384 * Called just before a new gesture starts. 385 * 386 * Any pending work on a prior gesture can be considered cemented in place. 387 */ onGestureFinalized(long completionTimeMs)388 void onGestureFinalized(long completionTimeMs); 389 } 390 } 391