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