1 /* 2 * Copyright (C) 2016 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.incallui.answer.impl.classifier; 18 19 import android.content.Context; 20 import android.hardware.SensorEvent; 21 import android.util.DisplayMetrics; 22 import android.view.MotionEvent; 23 import com.android.dialer.common.ConfigProviderBindings; 24 25 /** An classifier trying to determine whether it is a human interacting with the phone or not. */ 26 class HumanInteractionClassifier extends Classifier { 27 28 private static final String CONFIG_ANSWER_FALSE_TOUCH_DETECTION_ENABLED = 29 "answer_false_touch_detection_enabled"; 30 31 private final StrokeClassifier[] mStrokeClassifiers; 32 private final GestureClassifier[] mGestureClassifiers; 33 private final HistoryEvaluator mHistoryEvaluator; 34 private final boolean mEnabled; 35 HumanInteractionClassifier(Context context)36 HumanInteractionClassifier(Context context) { 37 DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 38 39 // If the phone is rotated to landscape, the calculations would be wrong if xdpi and ydpi 40 // were to be used separately. Due negligible differences in xdpi and ydpi we can just 41 // take the average. 42 // Note that xdpi and ydpi are the physical pixels per inch and are not affected by scaling. 43 float dpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f; 44 mClassifierData = new ClassifierData(dpi, displayMetrics.heightPixels); 45 mHistoryEvaluator = new HistoryEvaluator(); 46 mEnabled = 47 ConfigProviderBindings.get(context) 48 .getBoolean(CONFIG_ANSWER_FALSE_TOUCH_DETECTION_ENABLED, true); 49 50 mStrokeClassifiers = 51 new StrokeClassifier[] { 52 new AnglesClassifier(mClassifierData), 53 new SpeedClassifier(mClassifierData), 54 new DurationCountClassifier(mClassifierData), 55 new EndPointRatioClassifier(mClassifierData), 56 new EndPointLengthClassifier(mClassifierData), 57 new AccelerationClassifier(mClassifierData), 58 new SpeedAnglesClassifier(mClassifierData), 59 new LengthCountClassifier(mClassifierData), 60 new DirectionClassifier(mClassifierData) 61 }; 62 63 mGestureClassifiers = 64 new GestureClassifier[] { 65 new PointerCountClassifier(mClassifierData), new ProximityClassifier(mClassifierData) 66 }; 67 } 68 69 @Override onTouchEvent(MotionEvent event)70 public void onTouchEvent(MotionEvent event) { 71 72 // If the user is dragging down the notification, they might want to drag it down 73 // enough to see the content, read it for a while and then lift the finger to open 74 // the notification. This kind of motion scores very bad in the Classifier so the 75 // MotionEvents which are close to the current position of the finger are not 76 // sent to the classifiers until the finger moves far enough. When the finger if lifted 77 // up, the last MotionEvent which was far enough from the finger is set as the final 78 // MotionEvent and sent to the Classifiers. 79 addTouchEvent(event); 80 } 81 addTouchEvent(MotionEvent event)82 private void addTouchEvent(MotionEvent event) { 83 mClassifierData.update(event); 84 85 for (StrokeClassifier c : mStrokeClassifiers) { 86 c.onTouchEvent(event); 87 } 88 89 for (GestureClassifier c : mGestureClassifiers) { 90 c.onTouchEvent(event); 91 } 92 93 int size = mClassifierData.getEndingStrokes().size(); 94 for (int i = 0; i < size; i++) { 95 Stroke stroke = mClassifierData.getEndingStrokes().get(i); 96 float evaluation = 0.0f; 97 for (StrokeClassifier c : mStrokeClassifiers) { 98 float e = c.getFalseTouchEvaluation(stroke); 99 evaluation += e; 100 } 101 102 mHistoryEvaluator.addStroke(evaluation); 103 } 104 105 int action = event.getActionMasked(); 106 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 107 float evaluation = 0.0f; 108 for (GestureClassifier c : mGestureClassifiers) { 109 float e = c.getFalseTouchEvaluation(); 110 evaluation += e; 111 } 112 mHistoryEvaluator.addGesture(evaluation); 113 } 114 115 mClassifierData.cleanUp(event); 116 } 117 118 @Override onSensorChanged(SensorEvent event)119 public void onSensorChanged(SensorEvent event) { 120 for (Classifier c : mStrokeClassifiers) { 121 c.onSensorChanged(event); 122 } 123 124 for (Classifier c : mGestureClassifiers) { 125 c.onSensorChanged(event); 126 } 127 } 128 isFalseTouch()129 boolean isFalseTouch() { 130 float evaluation = mHistoryEvaluator.getEvaluation(); 131 return evaluation >= 5.0f; 132 } 133 isEnabled()134 public boolean isEnabled() { 135 return mEnabled; 136 } 137 138 @Override getTag()139 public String getTag() { 140 return "HIC"; 141 } 142 } 143