blob: 5e35d76f24f033d405d4c944b4983bc46919e694 [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;
Adrian Roos401caae2016-03-04 13:35:21 -080026import android.util.Log;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070027import android.view.MotionEvent;
28
Blazej Magnowski91be2f82015-09-24 16:41:58 -070029import java.util.ArrayDeque;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070030import java.util.ArrayList;
31
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070032/**
33 * An classifier trying to determine whether it is a human interacting with the phone or not.
34 */
35public class HumanInteractionClassifier extends Classifier {
36 private static final String HIC_ENABLE = "HIC_enable";
Blazej Magnowski91be2f82015-09-24 16:41:58 -070037 private static final float FINGER_DISTANCE = 0.1f;
Adrian Roos5c42df12016-01-27 08:48:42 -080038
39 /** Default value for the HIC_ENABLE setting: 1 - enabled, 0 - disabled */
40 private static final int HIC_ENABLE_DEFAULT = 1;
41
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070042 private static HumanInteractionClassifier sInstance = null;
43
44 private final Handler mHandler = new Handler();
45 private final Context mContext;
46
Adrian Roos401caae2016-03-04 13:35:21 -080047 private final StrokeClassifier[] mStrokeClassifiers;
48 private final GestureClassifier[] mGestureClassifiers;
49 private final ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
50 private final HistoryEvaluator mHistoryEvaluator;
Blazej Magnowski91be2f82015-09-24 16:41:58 -070051 private final float mDpi;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070052
Blazej Magnowski52af6b62015-09-30 15:46:17 -070053 private boolean mEnableClassifier = false;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070054 private int mCurrentType = Classifier.GENERIC;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070055
56 protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
57 @Override
58 public void onChange(boolean selfChange) {
59 updateConfiguration();
60 }
61 };
62
63 private HumanInteractionClassifier(Context context) {
64 mContext = context;
Blazej Magnowski91be2f82015-09-24 16:41:58 -070065 DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
66
67 // If the phone is rotated to landscape, the calculations would be wrong if xdpi and ydpi
68 // were to be used separately. Due negligible differences in xdpi and ydpi we can just
69 // take the average.
Adrian Roos401caae2016-03-04 13:35:21 -080070 // TODO: make this respect DPI changes.
Blazej Magnowski91be2f82015-09-24 16:41:58 -070071 mDpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f;
72 mClassifierData = new ClassifierData(mDpi);
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070073 mHistoryEvaluator = new HistoryEvaluator();
74
Adrian Roos401caae2016-03-04 13:35:21 -080075 mStrokeClassifiers = new StrokeClassifier[]{
76 new AnglesClassifier(mClassifierData),
77 new SpeedClassifier(mClassifierData),
78 new DurationCountClassifier(mClassifierData),
79 new EndPointRatioClassifier(mClassifierData),
80 new EndPointLengthClassifier(mClassifierData),
81 new AccelerationClassifier(mClassifierData),
82 new SpeedAnglesClassifier(mClassifierData),
83 new LengthCountClassifier(mClassifierData),
84 new DirectionClassifier(mClassifierData),
85 };
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070086
Adrian Roos401caae2016-03-04 13:35:21 -080087 mGestureClassifiers = new GestureClassifier[] {
88 new PointerCountClassifier(mClassifierData),
89 new ProximityClassifier(mClassifierData)
90 };
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070091
92 mContext.getContentResolver().registerContentObserver(
93 Settings.Global.getUriFor(HIC_ENABLE), false,
94 mSettingsObserver,
95 UserHandle.USER_ALL);
96
97 updateConfiguration();
98 }
99
100 public static HumanInteractionClassifier getInstance(Context context) {
101 if (sInstance == null) {
102 sInstance = new HumanInteractionClassifier(context);
103 }
104 return sInstance;
105 }
106
107 private void updateConfiguration() {
Adrian Roos5c42df12016-01-27 08:48:42 -0800108 mEnableClassifier = 0 != Settings.Global.getInt(
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700109 mContext.getContentResolver(),
Adrian Roos5c42df12016-01-27 08:48:42 -0800110 HIC_ENABLE, HIC_ENABLE_DEFAULT);
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700111 }
112
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700113 public void setType(int type) {
114 mCurrentType = type;
115 }
116
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700117 @Override
118 public void onTouchEvent(MotionEvent event) {
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700119 if (!mEnableClassifier) {
120 return;
121 }
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700122
Adrian Roosc5584ce2016-02-24 14:17:19 -0800123 // If the user is dragging down the notification, they might want to drag it down
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700124 // enough to see the content, read it for a while and then lift the finger to open
125 // the notification. This kind of motion scores very bad in the Classifier so the
126 // MotionEvents which are close to the current position of the finger are not
127 // sent to the classifiers until the finger moves far enough. When the finger if lifted
128 // up, the last MotionEvent which was far enough from the finger is set as the final
129 // MotionEvent and sent to the Classifiers.
130 if (mCurrentType == Classifier.NOTIFICATION_DRAG_DOWN) {
131 mBufferedEvents.add(MotionEvent.obtain(event));
132 Point pointEnd = new Point(event.getX() / mDpi, event.getY() / mDpi);
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700133
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700134 while (pointEnd.dist(new Point(mBufferedEvents.getFirst().getX() / mDpi,
135 mBufferedEvents.getFirst().getY() / mDpi)) > FINGER_DISTANCE) {
136 addTouchEvent(mBufferedEvents.getFirst());
137 mBufferedEvents.remove();
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700138 }
139
140 int action = event.getActionMasked();
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700141 if (action == MotionEvent.ACTION_UP) {
142 mBufferedEvents.getFirst().setAction(MotionEvent.ACTION_UP);
143 addTouchEvent(mBufferedEvents.getFirst());
144 mBufferedEvents.clear();
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700145 }
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700146 } else {
147 addTouchEvent(event);
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700148 }
149 }
150
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700151 private void addTouchEvent(MotionEvent event) {
152 mClassifierData.update(event);
153
Adrian Roos401caae2016-03-04 13:35:21 -0800154 for (StrokeClassifier c : mStrokeClassifiers) {
155 c.onTouchEvent(event);
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700156 }
157
Adrian Roos401caae2016-03-04 13:35:21 -0800158 for (GestureClassifier c : mGestureClassifiers) {
159 c.onTouchEvent(event);
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700160 }
161
162 int size = mClassifierData.getEndingStrokes().size();
163 for (int i = 0; i < size; i++) {
164 Stroke stroke = mClassifierData.getEndingStrokes().get(i);
165 float evaluation = 0.0f;
Adrian Roos401caae2016-03-04 13:35:21 -0800166 StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("stroke") : null;
167 for (StrokeClassifier c : mStrokeClassifiers) {
168 float e = c.getFalseTouchEvaluation(mCurrentType, stroke);
169 if (FalsingLog.ENABLED) {
170 String tag = c.getTag();
171 sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
172 }
173 evaluation += e;
174 }
175
176 if (FalsingLog.ENABLED) {
177 FalsingLog.i(" addTouchEvent", sb.toString());
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700178 }
179 mHistoryEvaluator.addStroke(evaluation);
180 }
181
182 int action = event.getActionMasked();
183 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
184 float evaluation = 0.0f;
Adrian Roos401caae2016-03-04 13:35:21 -0800185 StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("gesture") : null;
186 for (GestureClassifier c : mGestureClassifiers) {
187 float e = c.getFalseTouchEvaluation(mCurrentType);
188 if (FalsingLog.ENABLED) {
189 String tag = c.getTag();
190 sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
191 }
192 evaluation += e;
193 }
194 if (FalsingLog.ENABLED) {
195 FalsingLog.i(" addTouchEvent", sb.toString());
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700196 }
197 mHistoryEvaluator.addGesture(evaluation);
198 setType(Classifier.GENERIC);
199 }
200
201 mClassifierData.cleanUp(event);
202 }
203
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700204 @Override
205 public void onSensorChanged(SensorEvent event) {
Adrian Roos401caae2016-03-04 13:35:21 -0800206 for (Classifier c : mStrokeClassifiers) {
207 c.onSensorChanged(event);
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700208 }
209
Adrian Roos401caae2016-03-04 13:35:21 -0800210 for (Classifier c : mGestureClassifiers) {
211 c.onSensorChanged(event);
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700212 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700213 }
214
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700215 public boolean isFalseTouch() {
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700216 if (mEnableClassifier) {
Adrian Roos401caae2016-03-04 13:35:21 -0800217 float evaluation = mHistoryEvaluator.getEvaluation();
218 boolean result = evaluation >= 5.0f;
219 if (FalsingLog.ENABLED) {
220 FalsingLog.i("isFalseTouch", new StringBuilder()
221 .append("eval=").append(evaluation).append(" result=")
222 .append(result ? 1 : 0).toString());
223 }
224 return result;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700225 }
226 return false;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700227 }
228
229 public boolean isEnabled() {
230 return mEnableClassifier;
231 }
Adrian Roos401caae2016-03-04 13:35:21 -0800232
233 @Override
234 public String getTag() {
235 return "HIC";
236 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700237}