blob: 0cc50cddbfc6c316ad2c1b34404f8d3934f55870 [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;
Selim Cinekf8c4add2017-06-08 09:54:58 -070023import android.os.Looper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070024import android.os.UserHandle;
25import android.provider.Settings;
Blazej Magnowski91be2f82015-09-24 16:41:58 -070026import android.util.DisplayMetrics;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070027import android.view.MotionEvent;
28
Adrian Roos633b6ba2017-09-05 16:11:10 +020029import com.android.systemui.R;
30
Blazej Magnowski91be2f82015-09-24 16:41:58 -070031import java.util.ArrayDeque;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070032
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070033/**
34 * An classifier trying to determine whether it is a human interacting with the phone or not.
35 */
36public class HumanInteractionClassifier extends Classifier {
37 private static final String HIC_ENABLE = "HIC_enable";
Blazej Magnowski91be2f82015-09-24 16:41:58 -070038 private static final float FINGER_DISTANCE = 0.1f;
Adrian Roos5c42df12016-01-27 08:48:42 -080039
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070040 private static HumanInteractionClassifier sInstance = null;
41
Selim Cinekf8c4add2017-06-08 09:54:58 -070042 private final Handler mHandler = new Handler(Looper.getMainLooper());
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070043 private final Context mContext;
44
Adrian Roos401caae2016-03-04 13:35:21 -080045 private final StrokeClassifier[] mStrokeClassifiers;
46 private final GestureClassifier[] mGestureClassifiers;
47 private final ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
48 private final HistoryEvaluator mHistoryEvaluator;
Blazej Magnowski91be2f82015-09-24 16:41:58 -070049 private final float mDpi;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070050
Blazej Magnowski52af6b62015-09-30 15:46:17 -070051 private boolean mEnableClassifier = false;
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070052 private int mCurrentType = Classifier.GENERIC;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070053
54 protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
55 @Override
56 public void onChange(boolean selfChange) {
57 updateConfiguration();
58 }
59 };
60
61 private HumanInteractionClassifier(Context context) {
62 mContext = context;
Blazej Magnowski91be2f82015-09-24 16:41:58 -070063 DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
64
65 // If the phone is rotated to landscape, the calculations would be wrong if xdpi and ydpi
66 // were to be used separately. Due negligible differences in xdpi and ydpi we can just
67 // take the average.
Adrian Roos56918502016-04-20 18:36:18 -070068 // Note that xdpi and ydpi are the physical pixels per inch and are not affected by scaling.
Blazej Magnowski91be2f82015-09-24 16:41:58 -070069 mDpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f;
70 mClassifierData = new ClassifierData(mDpi);
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070071 mHistoryEvaluator = new HistoryEvaluator();
72
Adrian Roos401caae2016-03-04 13:35:21 -080073 mStrokeClassifiers = new StrokeClassifier[]{
74 new AnglesClassifier(mClassifierData),
75 new SpeedClassifier(mClassifierData),
76 new DurationCountClassifier(mClassifierData),
77 new EndPointRatioClassifier(mClassifierData),
78 new EndPointLengthClassifier(mClassifierData),
79 new AccelerationClassifier(mClassifierData),
80 new SpeedAnglesClassifier(mClassifierData),
81 new LengthCountClassifier(mClassifierData),
82 new DirectionClassifier(mClassifierData),
83 };
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070084
Adrian Roos401caae2016-03-04 13:35:21 -080085 mGestureClassifiers = new GestureClassifier[] {
86 new PointerCountClassifier(mClassifierData),
87 new ProximityClassifier(mClassifierData)
88 };
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070089
90 mContext.getContentResolver().registerContentObserver(
91 Settings.Global.getUriFor(HIC_ENABLE), false,
92 mSettingsObserver,
93 UserHandle.USER_ALL);
94
95 updateConfiguration();
96 }
97
98 public static HumanInteractionClassifier getInstance(Context context) {
99 if (sInstance == null) {
100 sInstance = new HumanInteractionClassifier(context);
101 }
102 return sInstance;
103 }
104
105 private void updateConfiguration() {
Adrian Roos633b6ba2017-09-05 16:11:10 +0200106 boolean defaultValue = mContext.getResources().getBoolean(
107 R.bool.config_lockscreenAntiFalsingClassifierEnabled);
108
Adrian Roos5c42df12016-01-27 08:48:42 -0800109 mEnableClassifier = 0 != Settings.Global.getInt(
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700110 mContext.getContentResolver(),
Adrian Roos633b6ba2017-09-05 16:11:10 +0200111 HIC_ENABLE, defaultValue ? 1 : 0);
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700112 }
113
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700114 public void setType(int type) {
115 mCurrentType = type;
116 }
117
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700118 @Override
119 public void onTouchEvent(MotionEvent event) {
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700120 if (!mEnableClassifier) {
121 return;
122 }
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700123
Adrian Roosc5584ce2016-02-24 14:17:19 -0800124 // If the user is dragging down the notification, they might want to drag it down
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700125 // enough to see the content, read it for a while and then lift the finger to open
126 // the notification. This kind of motion scores very bad in the Classifier so the
127 // MotionEvents which are close to the current position of the finger are not
128 // sent to the classifiers until the finger moves far enough. When the finger if lifted
129 // up, the last MotionEvent which was far enough from the finger is set as the final
130 // MotionEvent and sent to the Classifiers.
Selim Cinek3d6ae232019-01-04 14:14:33 -0800131 if (mCurrentType == Classifier.NOTIFICATION_DRAG_DOWN
132 || mCurrentType == Classifier.PULSE_EXPAND) {
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700133 mBufferedEvents.add(MotionEvent.obtain(event));
134 Point pointEnd = new Point(event.getX() / mDpi, event.getY() / mDpi);
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700135
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700136 while (pointEnd.dist(new Point(mBufferedEvents.getFirst().getX() / mDpi,
137 mBufferedEvents.getFirst().getY() / mDpi)) > FINGER_DISTANCE) {
138 addTouchEvent(mBufferedEvents.getFirst());
139 mBufferedEvents.remove();
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700140 }
141
142 int action = event.getActionMasked();
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700143 if (action == MotionEvent.ACTION_UP) {
144 mBufferedEvents.getFirst().setAction(MotionEvent.ACTION_UP);
145 addTouchEvent(mBufferedEvents.getFirst());
146 mBufferedEvents.clear();
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700147 }
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700148 } else {
149 addTouchEvent(event);
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700150 }
151 }
152
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700153 private void addTouchEvent(MotionEvent event) {
154 mClassifierData.update(event);
155
Adrian Roos401caae2016-03-04 13:35:21 -0800156 for (StrokeClassifier c : mStrokeClassifiers) {
157 c.onTouchEvent(event);
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700158 }
159
Adrian Roos401caae2016-03-04 13:35:21 -0800160 for (GestureClassifier c : mGestureClassifiers) {
161 c.onTouchEvent(event);
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700162 }
163
164 int size = mClassifierData.getEndingStrokes().size();
165 for (int i = 0; i < size; i++) {
166 Stroke stroke = mClassifierData.getEndingStrokes().get(i);
167 float evaluation = 0.0f;
Adrian Roos401caae2016-03-04 13:35:21 -0800168 StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("stroke") : null;
169 for (StrokeClassifier c : mStrokeClassifiers) {
170 float e = c.getFalseTouchEvaluation(mCurrentType, stroke);
171 if (FalsingLog.ENABLED) {
172 String tag = c.getTag();
173 sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
174 }
175 evaluation += e;
176 }
177
178 if (FalsingLog.ENABLED) {
179 FalsingLog.i(" addTouchEvent", sb.toString());
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700180 }
181 mHistoryEvaluator.addStroke(evaluation);
182 }
183
184 int action = event.getActionMasked();
185 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
186 float evaluation = 0.0f;
Adrian Roos401caae2016-03-04 13:35:21 -0800187 StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("gesture") : null;
188 for (GestureClassifier c : mGestureClassifiers) {
189 float e = c.getFalseTouchEvaluation(mCurrentType);
190 if (FalsingLog.ENABLED) {
191 String tag = c.getTag();
192 sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
193 }
194 evaluation += e;
195 }
196 if (FalsingLog.ENABLED) {
197 FalsingLog.i(" addTouchEvent", sb.toString());
Blazej Magnowski91be2f82015-09-24 16:41:58 -0700198 }
199 mHistoryEvaluator.addGesture(evaluation);
200 setType(Classifier.GENERIC);
201 }
202
203 mClassifierData.cleanUp(event);
204 }
205
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700206 @Override
207 public void onSensorChanged(SensorEvent event) {
Adrian Roos401caae2016-03-04 13:35:21 -0800208 for (Classifier c : mStrokeClassifiers) {
209 c.onSensorChanged(event);
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700210 }
211
Adrian Roos401caae2016-03-04 13:35:21 -0800212 for (Classifier c : mGestureClassifiers) {
213 c.onSensorChanged(event);
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700214 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700215 }
216
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -0700217 public boolean isFalseTouch() {
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700218 if (mEnableClassifier) {
Adrian Roos401caae2016-03-04 13:35:21 -0800219 float evaluation = mHistoryEvaluator.getEvaluation();
220 boolean result = evaluation >= 5.0f;
221 if (FalsingLog.ENABLED) {
222 FalsingLog.i("isFalseTouch", new StringBuilder()
223 .append("eval=").append(evaluation).append(" result=")
224 .append(result ? 1 : 0).toString());
225 }
226 return result;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700227 }
228 return false;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700229 }
230
231 public boolean isEnabled() {
232 return mEnableClassifier;
233 }
Adrian Roos401caae2016-03-04 13:35:21 -0800234
235 @Override
236 public String getTag() {
237 return "HIC";
238 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700239}