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