blob: 45eb9ad3115a9ec40daf59de564bc22c9dbf5da6 [file] [log] [blame]
/*
* Copyright (C) 2015 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.content.Context;
import android.database.ContentObserver;
import android.hardware.SensorEvent;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import java.util.ArrayDeque;
import java.util.ArrayList;
/**
* An classifier trying to determine whether it is a human interacting with the phone or not.
*/
public class HumanInteractionClassifier extends Classifier {
private static final String HIC_ENABLE = "HIC_enable";
private static final float FINGER_DISTANCE = 0.1f;
/** Default value for the HIC_ENABLE setting: 1 - enabled, 0 - disabled */
private static final int HIC_ENABLE_DEFAULT = 1;
private static HumanInteractionClassifier sInstance = null;
private final Handler mHandler = new Handler();
private final Context mContext;
private ArrayList<StrokeClassifier> mStrokeClassifiers = new ArrayList<>();
private ArrayList<GestureClassifier> mGestureClassifiers = new ArrayList<>();
private ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
private final int mStrokeClassifiersSize;
private final int mGestureClassifiersSize;
private final float mDpi;
private HistoryEvaluator mHistoryEvaluator;
private boolean mEnableClassifier = false;
private int mCurrentType = Classifier.GENERIC;
protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
updateConfiguration();
}
};
private HumanInteractionClassifier(Context context) {
mContext = context;
DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
// If the phone is rotated to landscape, the calculations would be wrong if xdpi and ydpi
// were to be used separately. Due negligible differences in xdpi and ydpi we can just
// take the average.
mDpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f;
mClassifierData = new ClassifierData(mDpi);
mHistoryEvaluator = new HistoryEvaluator();
mStrokeClassifiers.add(new AnglesClassifier(mClassifierData));
mStrokeClassifiers.add(new SpeedClassifier(mClassifierData));
mStrokeClassifiers.add(new DurationCountClassifier(mClassifierData));
mStrokeClassifiers.add(new EndPointRatioClassifier(mClassifierData));
mStrokeClassifiers.add(new EndPointLengthClassifier(mClassifierData));
mStrokeClassifiers.add(new AccelerationClassifier(mClassifierData));
mStrokeClassifiers.add(new SpeedAnglesClassifier(mClassifierData));
mStrokeClassifiers.add(new LengthCountClassifier(mClassifierData));
mStrokeClassifiers.add(new DirectionClassifier(mClassifierData));
mGestureClassifiers.add(new PointerCountClassifier(mClassifierData));
mGestureClassifiers.add(new ProximityClassifier(mClassifierData));
mStrokeClassifiersSize = mStrokeClassifiers.size();
mGestureClassifiersSize = mGestureClassifiers.size();
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(HIC_ENABLE), false,
mSettingsObserver,
UserHandle.USER_ALL);
updateConfiguration();
}
public static HumanInteractionClassifier getInstance(Context context) {
if (sInstance == null) {
sInstance = new HumanInteractionClassifier(context);
}
return sInstance;
}
private void updateConfiguration() {
mEnableClassifier = 0 != Settings.Global.getInt(
mContext.getContentResolver(),
HIC_ENABLE, HIC_ENABLE_DEFAULT);
}
public void setType(int type) {
mCurrentType = type;
}
@Override
public void onTouchEvent(MotionEvent event) {
if (!mEnableClassifier) {
return;
}
// If the user is dragging down the notification, they might want to drag it down
// enough to see the content, read it for a while and then lift the finger to open
// the notification. This kind of motion scores very bad in the Classifier so the
// MotionEvents which are close to the current position of the finger are not
// sent to the classifiers until the finger moves far enough. When the finger if lifted
// up, the last MotionEvent which was far enough from the finger is set as the final
// MotionEvent and sent to the Classifiers.
if (mCurrentType == Classifier.NOTIFICATION_DRAG_DOWN) {
mBufferedEvents.add(MotionEvent.obtain(event));
Point pointEnd = new Point(event.getX() / mDpi, event.getY() / mDpi);
while (pointEnd.dist(new Point(mBufferedEvents.getFirst().getX() / mDpi,
mBufferedEvents.getFirst().getY() / mDpi)) > FINGER_DISTANCE) {
addTouchEvent(mBufferedEvents.getFirst());
mBufferedEvents.remove();
}
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_UP) {
mBufferedEvents.getFirst().setAction(MotionEvent.ACTION_UP);
addTouchEvent(mBufferedEvents.getFirst());
mBufferedEvents.clear();
}
} else {
addTouchEvent(event);
}
}
private void addTouchEvent(MotionEvent event) {
mClassifierData.update(event);
for (int i = 0; i < mStrokeClassifiersSize; i++) {
mStrokeClassifiers.get(i).onTouchEvent(event);
}
for (int i = 0; i < mGestureClassifiersSize; i++) {
mGestureClassifiers.get(i).onTouchEvent(event);
}
int size = mClassifierData.getEndingStrokes().size();
for (int i = 0; i < size; i++) {
Stroke stroke = mClassifierData.getEndingStrokes().get(i);
float evaluation = 0.0f;
for (int j = 0; j < mStrokeClassifiersSize; j++) {
evaluation += mStrokeClassifiers.get(j).getFalseTouchEvaluation(
mCurrentType, stroke);
}
mHistoryEvaluator.addStroke(evaluation);
}
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
float evaluation = 0.0f;
for (int i = 0; i < mGestureClassifiersSize; i++) {
evaluation += mGestureClassifiers.get(i).getFalseTouchEvaluation(mCurrentType);
}
mHistoryEvaluator.addGesture(evaluation);
setType(Classifier.GENERIC);
}
mClassifierData.cleanUp(event);
}
@Override
public void onSensorChanged(SensorEvent event) {
for (int i = 0; i < mStrokeClassifiers.size(); i++) {
mStrokeClassifiers.get(i).onSensorChanged(event);
}
for (int i = 0; i < mGestureClassifiers.size(); i++) {
mGestureClassifiers.get(i).onSensorChanged(event);
}
}
public boolean isFalseTouch() {
if (mEnableClassifier) {
return mHistoryEvaluator.getEvaluation() >= 5.0f;
}
return false;
}
public boolean isEnabled() {
return mEnableClassifier;
}
}