blob: 8b11ceb34ea091def56fe818604d9985439d7958 [file] [log] [blame]
/*
* Copyright (C) 2019 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.brightline;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import com.android.systemui.classifier.Classifier;
import java.util.ArrayList;
import java.util.List;
/**
* Acts as a cache and utility class for FalsingClassifiers.
*/
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 final float mXdpi;
private final float mYdpi;
private @Classifier.InteractionType int mInteractionType;
private final TimeLimitedMotionEventBuffer mRecentMotionEvents =
new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
private boolean mDirty = true;
private float mAngle = 0;
private MotionEvent mFirstActualMotionEvent;
private MotionEvent mFirstRecentMotionEvent;
private MotionEvent mLastMotionEvent;
public FalsingDataProvider(DisplayMetrics displayMetrics) {
mXdpi = displayMetrics.xdpi;
mYdpi = displayMetrics.ydpi;
mWidthPixels = displayMetrics.widthPixels;
mHeightPixels = displayMetrics.heightPixels;
FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi());
FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
}
void onMotionEvent(MotionEvent motionEvent) {
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
mFirstActualMotionEvent = 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) {
mRecentMotionEvents.clear();
}
mRecentMotionEvents.addAll(motionEvents);
FalsingClassifier.logDebug("Size: " + mRecentMotionEvents.size());
mDirty = true;
}
/** Returns screen width in pixels. */
int getWidthPixels() {
return mWidthPixels;
}
/** Returns screen height in pixels. */
int getHeightPixels() {
return mHeightPixels;
}
float getXdpi() {
return mXdpi;
}
float getYdpi() {
return mYdpi;
}
List<MotionEvent> getRecentMotionEvents() {
return mRecentMotionEvents;
}
/**
* interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
*/
final void setInteractionType(@Classifier.InteractionType int interactionType) {
this.mInteractionType = interactionType;
}
final int getInteractionType() {
return mInteractionType;
}
MotionEvent getFirstActualMotionEvent() {
return mFirstActualMotionEvent;
}
MotionEvent getFirstRecentMotionEvent() {
recalculateData();
return mFirstRecentMotionEvent;
}
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.
*/
float getAngle() {
recalculateData();
return mAngle;
}
boolean isHorizontal() {
recalculateData();
if (mRecentMotionEvents.isEmpty()) {
return false;
}
return Math.abs(mFirstRecentMotionEvent.getX() - mLastMotionEvent.getX()) > Math
.abs(mFirstRecentMotionEvent.getY() - mLastMotionEvent.getY());
}
boolean isRight() {
recalculateData();
if (mRecentMotionEvents.isEmpty()) {
return false;
}
return mLastMotionEvent.getX() > mFirstRecentMotionEvent.getX();
}
boolean isVertical() {
return !isHorizontal();
}
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;
}
void onSessionEnd() {
mFirstActualMotionEvent = null;
for (MotionEvent ev : mRecentMotionEvents) {
ev.recycle();
}
mRecentMotionEvents.clear();
mDirty = true;
}
}