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