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