blob: 45eb9ad3115a9ec40daf59de564bc22c9dbf5da6 [file] [log] [blame]
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -07001/*
2 * Copyright (C) 2015 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
17package com.android.systemui.classifier;
18
19import android.content.Context;
20import android.database.ContentObserver;
21import android.hardware.SensorEvent;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070022import android.os.Handler;
23import android.os.UserHandle;
24import android.provider.Settings;
Blazej Magnowski91be2f82015-09-24 16:41:58 -070025import android.util.DisplayMetrics;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070026import android.view.MotionEvent;
27
Blazej Magnowski91be2f82015-09-24 16:41:58 -070028import java.util.ArrayDeque;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070029import java.util.ArrayList;
30
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070031/**
32 * An classifier trying to determine whether it is a human interacting with the phone or not.
33 */
34public class HumanInteractionClassifier extends Classifier {
35 private static final String HIC_ENABLE = "HIC_enable";
Blazej Magnowski91be2f82015-09-24 16:41:58 -070036 private static final float FINGER_DISTANCE = 0.1f;
Adrian Roos5c42df12016-01-27 08:48:42 -080037
38 /** Default value for the HIC_ENABLE setting: 1 - enabled, 0 - disabled */
39 private static final int HIC_ENABLE_DEFAULT = 1;
40
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070041 private static HumanInteractionClassifier sInstance = null;
42
43 private final Handler mHandler = new Handler();
44 private final Context mContext;
45
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070046 private ArrayList<StrokeClassifier> mStrokeClassifiers = new ArrayList<>();
47 private ArrayList<GestureClassifier> mGestureClassifiers = new ArrayList<>();
Blazej Magnowski91be2f82015-09-24 16:41:58 -070048 private ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070049 private final int mStrokeClassifiersSize;
50 private final int mGestureClassifiersSize;
Blazej Magnowski91be2f82015-09-24 16:41:58 -070051 private final float mDpi;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070052
53 private HistoryEvaluator mHistoryEvaluator;
Blazej Magnowski52af6b62015-09-30 15:46:17 -070054 private boolean mEnableClassifier = false;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070055 private int mCurrentType = Classifier.GENERIC;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070056
57 protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
58 @Override
59 public void onChange(boolean selfChange) {
60 updateConfiguration();
61 }
62 };
63
64 private HumanInteractionClassifier(Context context) {
65 mContext = context;
Blazej Magnowski91be2f82015-09-24 16:41:58 -070066 DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
67
68 // If the phone is rotated to landscape, the calculations would be wrong if xdpi and ydpi
69 // were to be used separately. Due negligible differences in xdpi and ydpi we can just
70 // take the average.
71 mDpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f;
72 mClassifierData = new ClassifierData(mDpi);
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070073 mHistoryEvaluator = new HistoryEvaluator();
74
Blazej Magnowskia28de572015-09-25 16:41:27 -070075 mStrokeClassifiers.add(new AnglesClassifier(mClassifierData));
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070076 mStrokeClassifiers.add(new SpeedClassifier(mClassifierData));
77 mStrokeClassifiers.add(new DurationCountClassifier(mClassifierData));
78 mStrokeClassifiers.add(new EndPointRatioClassifier(mClassifierData));
79 mStrokeClassifiers.add(new EndPointLengthClassifier(mClassifierData));
80 mStrokeClassifiers.add(new AccelerationClassifier(mClassifierData));
Blazej Magnowskia28de572015-09-25 16:41:27 -070081 mStrokeClassifiers.add(new SpeedAnglesClassifier(mClassifierData));
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070082 mStrokeClassifiers.add(new LengthCountClassifier(mClassifierData));
Blazej Magnowski4498d9c2015-09-28 14:00:11 -070083 mStrokeClassifiers.add(new DirectionClassifier(mClassifierData));
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070084
85 mGestureClassifiers.add(new PointerCountClassifier(mClassifierData));
86 mGestureClassifiers.add(new ProximityClassifier(mClassifierData));
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070087
88 mStrokeClassifiersSize = mStrokeClassifiers.size();
89 mGestureClassifiersSize = mGestureClassifiers.size();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070090
91 mContext.getContentResolver().registerContentObserver(
92 Settings.Global.getUriFor(HIC_ENABLE), false,
93 mSettingsObserver,
94 UserHandle.USER_ALL);
95
96 updateConfiguration();
97 }
98
99 public static HumanInteractionClassifier getInstance(Context context) {
100 if (sInstance == null) {
101 sInstance = new HumanInteractionClassifier(context);
102 }
103 return sInstance;
104 }
105
106 private void updateConfiguration() {
Adrian Roos5c42df12016-01-27 08:48:42 -0800107 mEnableClassifier = 0 != Settings.Global.getInt(
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700108 mContext.getContentResolver(),
Adrian Roos5c42df12016-01-27 08:48:42 -0800109 HIC_ENABLE, HIC_ENABLE_DEFAULT);
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700110 }
111
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700112 public void setType(int type) {
113 mCurrentType = type;
114 }
115
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700116 @Override
117 public void onTouchEvent(MotionEvent event) {
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700118 if (!mEnableClassifier) {
119 return;
120 }
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700121
Adrian Roosc5584ce2016-02-24 14:17:19 -0800122 // If the user is dragging down the notification, they might want to drag it down
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700123 // enough to see the content, read it for a while and then lift the finger to open
124 // the notification. This kind of motion scores very bad in the Classifier so the
125 // MotionEvents which are close to the current position of the finger are not
126 // sent to the classifiers until the finger moves far enough. When the finger if lifted
127 // up, the last MotionEvent which was far enough from the finger is set as the final
128 // MotionEvent and sent to the Classifiers.
129 if (mCurrentType == Classifier.NOTIFICATION_DRAG_DOWN) {
130 mBufferedEvents.add(MotionEvent.obtain(event));
131 Point pointEnd = new Point(event.getX() / mDpi, event.getY() / mDpi);
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700132
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700133 while (pointEnd.dist(new Point(mBufferedEvents.getFirst().getX() / mDpi,
134 mBufferedEvents.getFirst().getY() / mDpi)) > FINGER_DISTANCE) {
135 addTouchEvent(mBufferedEvents.getFirst());
136 mBufferedEvents.remove();
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700137 }
138
139 int action = event.getActionMasked();
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700140 if (action == MotionEvent.ACTION_UP) {
141 mBufferedEvents.getFirst().setAction(MotionEvent.ACTION_UP);
142 addTouchEvent(mBufferedEvents.getFirst());
143 mBufferedEvents.clear();
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700144 }
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700145 } else {
146 addTouchEvent(event);
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700147 }
148 }
149
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700150 private void addTouchEvent(MotionEvent event) {
151 mClassifierData.update(event);
152
153 for (int i = 0; i < mStrokeClassifiersSize; i++) {
154 mStrokeClassifiers.get(i).onTouchEvent(event);
155 }
156
157 for (int i = 0; i < mGestureClassifiersSize; i++) {
158 mGestureClassifiers.get(i).onTouchEvent(event);
159 }
160
161 int size = mClassifierData.getEndingStrokes().size();
162 for (int i = 0; i < size; i++) {
163 Stroke stroke = mClassifierData.getEndingStrokes().get(i);
164 float evaluation = 0.0f;
165 for (int j = 0; j < mStrokeClassifiersSize; j++) {
166 evaluation += mStrokeClassifiers.get(j).getFalseTouchEvaluation(
167 mCurrentType, stroke);
168 }
169 mHistoryEvaluator.addStroke(evaluation);
170 }
171
172 int action = event.getActionMasked();
173 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
174 float evaluation = 0.0f;
175 for (int i = 0; i < mGestureClassifiersSize; i++) {
176 evaluation += mGestureClassifiers.get(i).getFalseTouchEvaluation(mCurrentType);
177 }
178 mHistoryEvaluator.addGesture(evaluation);
179 setType(Classifier.GENERIC);
180 }
181
182 mClassifierData.cleanUp(event);
183 }
184
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700185 @Override
186 public void onSensorChanged(SensorEvent event) {
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700187 for (int i = 0; i < mStrokeClassifiers.size(); i++) {
188 mStrokeClassifiers.get(i).onSensorChanged(event);
189 }
190
191 for (int i = 0; i < mGestureClassifiers.size(); i++) {
192 mGestureClassifiers.get(i).onSensorChanged(event);
193 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700194 }
195
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700196 public boolean isFalseTouch() {
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700197 if (mEnableClassifier) {
198 return mHistoryEvaluator.getEvaluation() >= 5.0f;
199 }
200 return false;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700201 }
202
203 public boolean isEnabled() {
204 return mEnableClassifier;
205 }
206}