• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.classifier;
18 
19 import android.content.Context;
20 import android.database.ContentObserver;
21 import android.hardware.SensorEvent;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.UserHandle;
25 import android.provider.Settings;
26 import android.util.DisplayMetrics;
27 import android.util.Log;
28 import android.view.MotionEvent;
29 
30 import com.android.systemui.R;
31 
32 import java.util.ArrayDeque;
33 import java.util.ArrayList;
34 
35 /**
36  * An classifier trying to determine whether it is a human interacting with the phone or not.
37  */
38 public class HumanInteractionClassifier extends Classifier {
39     private static final String HIC_ENABLE = "HIC_enable";
40     private static final float FINGER_DISTANCE = 0.1f;
41 
42     private static HumanInteractionClassifier sInstance = null;
43 
44     private final Handler mHandler = new Handler(Looper.getMainLooper());
45     private final Context mContext;
46 
47     private final StrokeClassifier[] mStrokeClassifiers;
48     private final GestureClassifier[] mGestureClassifiers;
49     private final ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
50     private final HistoryEvaluator mHistoryEvaluator;
51     private final float mDpi;
52 
53     private boolean mEnableClassifier = false;
54     private int mCurrentType = Classifier.GENERIC;
55 
56     protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
57         @Override
58         public void onChange(boolean selfChange) {
59             updateConfiguration();
60         }
61     };
62 
HumanInteractionClassifier(Context context)63     private HumanInteractionClassifier(Context context) {
64         mContext = context;
65         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.
70         // Note that xdpi and ydpi are the physical pixels per inch and are not affected by scaling.
71         mDpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f;
72         mClassifierData = new ClassifierData(mDpi);
73         mHistoryEvaluator = new HistoryEvaluator();
74 
75         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         };
86 
87         mGestureClassifiers = new GestureClassifier[] {
88                 new PointerCountClassifier(mClassifierData),
89                 new ProximityClassifier(mClassifierData)
90         };
91 
92         mContext.getContentResolver().registerContentObserver(
93                 Settings.Global.getUriFor(HIC_ENABLE), false,
94                 mSettingsObserver,
95                 UserHandle.USER_ALL);
96 
97         updateConfiguration();
98     }
99 
getInstance(Context context)100     public static HumanInteractionClassifier getInstance(Context context) {
101         if (sInstance == null) {
102             sInstance = new HumanInteractionClassifier(context);
103         }
104         return sInstance;
105     }
106 
updateConfiguration()107     private void updateConfiguration() {
108         boolean defaultValue = mContext.getResources().getBoolean(
109                 R.bool.config_lockscreenAntiFalsingClassifierEnabled);
110 
111         mEnableClassifier = 0 != Settings.Global.getInt(
112                 mContext.getContentResolver(),
113                 HIC_ENABLE, defaultValue ? 1 : 0);
114     }
115 
setType(int type)116     public void setType(int type) {
117         mCurrentType = type;
118     }
119 
120     @Override
onTouchEvent(MotionEvent event)121     public void onTouchEvent(MotionEvent event) {
122         if (!mEnableClassifier) {
123             return;
124         }
125 
126         // If the user is dragging down the notification, they might want to drag it down
127         // enough to see the content, read it for a while and then lift the finger to open
128         // the notification. This kind of motion scores very bad in the Classifier so the
129         // MotionEvents which are close to the current position of the finger are not
130         // sent to the classifiers until the finger moves far enough. When the finger if lifted
131         // up, the last MotionEvent which was far enough from the finger is set as the final
132         // MotionEvent and sent to the Classifiers.
133         if (mCurrentType == Classifier.NOTIFICATION_DRAG_DOWN) {
134             mBufferedEvents.add(MotionEvent.obtain(event));
135             Point pointEnd = new Point(event.getX() / mDpi, event.getY() / mDpi);
136 
137             while (pointEnd.dist(new Point(mBufferedEvents.getFirst().getX() / mDpi,
138                     mBufferedEvents.getFirst().getY() / mDpi)) > FINGER_DISTANCE) {
139                 addTouchEvent(mBufferedEvents.getFirst());
140                 mBufferedEvents.remove();
141             }
142 
143             int action = event.getActionMasked();
144             if (action == MotionEvent.ACTION_UP) {
145                 mBufferedEvents.getFirst().setAction(MotionEvent.ACTION_UP);
146                 addTouchEvent(mBufferedEvents.getFirst());
147                 mBufferedEvents.clear();
148             }
149         } else {
150             addTouchEvent(event);
151         }
152     }
153 
addTouchEvent(MotionEvent event)154     private void addTouchEvent(MotionEvent event) {
155         mClassifierData.update(event);
156 
157         for (StrokeClassifier c : mStrokeClassifiers) {
158             c.onTouchEvent(event);
159         }
160 
161         for (GestureClassifier c : mGestureClassifiers) {
162             c.onTouchEvent(event);
163         }
164 
165         int size = mClassifierData.getEndingStrokes().size();
166         for (int i = 0; i < size; i++) {
167             Stroke stroke = mClassifierData.getEndingStrokes().get(i);
168             float evaluation = 0.0f;
169             StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("stroke") : null;
170             for (StrokeClassifier c : mStrokeClassifiers) {
171                 float e = c.getFalseTouchEvaluation(mCurrentType, stroke);
172                 if (FalsingLog.ENABLED) {
173                     String tag = c.getTag();
174                     sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
175                 }
176                 evaluation += e;
177             }
178 
179             if (FalsingLog.ENABLED) {
180                 FalsingLog.i(" addTouchEvent", sb.toString());
181             }
182             mHistoryEvaluator.addStroke(evaluation);
183         }
184 
185         int action = event.getActionMasked();
186         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
187             float evaluation = 0.0f;
188             StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("gesture") : null;
189             for (GestureClassifier c : mGestureClassifiers) {
190                 float e = c.getFalseTouchEvaluation(mCurrentType);
191                 if (FalsingLog.ENABLED) {
192                     String tag = c.getTag();
193                     sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
194                 }
195                 evaluation += e;
196             }
197             if (FalsingLog.ENABLED) {
198                 FalsingLog.i(" addTouchEvent", sb.toString());
199             }
200             mHistoryEvaluator.addGesture(evaluation);
201             setType(Classifier.GENERIC);
202         }
203 
204         mClassifierData.cleanUp(event);
205     }
206 
207     @Override
onSensorChanged(SensorEvent event)208     public void onSensorChanged(SensorEvent event) {
209         for (Classifier c : mStrokeClassifiers) {
210             c.onSensorChanged(event);
211         }
212 
213         for (Classifier c : mGestureClassifiers) {
214             c.onSensorChanged(event);
215         }
216     }
217 
isFalseTouch()218     public boolean isFalseTouch() {
219         if (mEnableClassifier) {
220             float evaluation = mHistoryEvaluator.getEvaluation();
221             boolean result = evaluation >= 5.0f;
222             if (FalsingLog.ENABLED) {
223                 FalsingLog.i("isFalseTouch", new StringBuilder()
224                         .append("eval=").append(evaluation).append(" result=")
225                         .append(result ? 1 : 0).toString());
226             }
227             return result;
228         }
229         return false;
230     }
231 
isEnabled()232     public boolean isEnabled() {
233         return mEnableClassifier;
234     }
235 
236     @Override
getTag()237     public String getTag() {
238         return "HIC";
239     }
240 }
241