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