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