/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.classifier; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_FLING_THRESHOLD_IN; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_SWIPE_THRESHOLD_IN; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_SCREEN_FRACTION_MAX_DISTANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VELOCITY_TO_DISTANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED; import static com.android.systemui.classifier.Classifier.SHADE_DRAG; import android.provider.DeviceConfig; import android.view.MotionEvent; import android.view.VelocityTracker; import com.android.systemui.util.DeviceConfigProxy; import java.util.List; import java.util.Locale; import javax.inject.Inject; /** * Ensure that the swipe + momentum covers a minimum distance. */ class DistanceClassifier extends FalsingClassifier { private static final float HORIZONTAL_FLING_THRESHOLD_DISTANCE_IN = 1; private static final float VERTICAL_FLING_THRESHOLD_DISTANCE_IN = 1.5f; private static final float HORIZONTAL_SWIPE_THRESHOLD_DISTANCE_IN = 3; private static final float VERTICAL_SWIPE_THRESHOLD_DISTANCE_IN = 3; private static final float VELOCITY_TO_DISTANCE = 30f; private static final float SCREEN_FRACTION_MAX_DISTANCE = 0.8f; private final float mVerticalFlingThresholdPx; private final float mHorizontalFlingThresholdPx; private final float mVerticalSwipeThresholdPx; private final float mHorizontalSwipeThresholdPx; private final float mVelocityToDistanceMultiplier; private boolean mDistanceDirty; private DistanceVectors mCachedDistance; @Inject DistanceClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) { super(dataProvider); mVelocityToDistanceMultiplier = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_DISTANCE_VELOCITY_TO_DISTANCE, VELOCITY_TO_DISTANCE); float horizontalFlingThresholdIn = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_FLING_THRESHOLD_IN, HORIZONTAL_FLING_THRESHOLD_DISTANCE_IN); float verticalFlingThresholdIn = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN, VERTICAL_FLING_THRESHOLD_DISTANCE_IN); float horizontalSwipeThresholdIn = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_SWIPE_THRESHOLD_IN, HORIZONTAL_SWIPE_THRESHOLD_DISTANCE_IN); float verticalSwipeThresholdIn = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN, VERTICAL_SWIPE_THRESHOLD_DISTANCE_IN); float screenFractionMaxDistance = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_DISTANCE_SCREEN_FRACTION_MAX_DISTANCE, SCREEN_FRACTION_MAX_DISTANCE); mHorizontalFlingThresholdPx = Math .min(getWidthPixels() * screenFractionMaxDistance, horizontalFlingThresholdIn * getXdpi()); mVerticalFlingThresholdPx = Math .min(getHeightPixels() * screenFractionMaxDistance, verticalFlingThresholdIn * getYdpi()); mHorizontalSwipeThresholdPx = Math .min(getWidthPixels() * screenFractionMaxDistance, horizontalSwipeThresholdIn * getXdpi()); mVerticalSwipeThresholdPx = Math .min(getHeightPixels() * screenFractionMaxDistance, verticalSwipeThresholdIn * getYdpi()); mDistanceDirty = true; } private DistanceVectors getDistances() { if (mDistanceDirty) { mCachedDistance = calculateDistances(); mDistanceDirty = false; } return mCachedDistance; } private DistanceVectors calculateDistances() { // This code assumes that there will be no missed DOWN or UP events. List motionEvents = getRecentMotionEvents(); if (motionEvents.size() < 3) { logDebug("Only " + motionEvents.size() + " motion events recorded."); return new DistanceVectors(0, 0, 0, 0); } VelocityTracker velocityTracker = VelocityTracker.obtain(); for (MotionEvent motionEvent : motionEvents) { velocityTracker.addMovement(motionEvent); } velocityTracker.computeCurrentVelocity(1); float vX = velocityTracker.getXVelocity(); float vY = velocityTracker.getYVelocity(); velocityTracker.recycle(); float dX = getLastMotionEvent().getX() - getFirstMotionEvent().getX(); float dY = getLastMotionEvent().getY() - getFirstMotionEvent().getY(); return new DistanceVectors(dX, dY, vX, vY); } @Override public void onTouchEvent(MotionEvent motionEvent) { mDistanceDirty = true; } @Override Result calculateFalsingResult( @Classifier.InteractionType int interactionType, double historyBelief, double historyConfidence) { if (interactionType == BRIGHTNESS_SLIDER || interactionType == MEDIA_SEEKBAR || interactionType == SHADE_DRAG || interactionType == QS_COLLAPSE || interactionType == Classifier.UDFPS_AUTHENTICATION || interactionType == Classifier.QS_SWIPE_SIDE || interactionType == QS_SWIPE_NESTED) { return Result.passed(0); } return !getPassedFlingThreshold() ? falsed(0.5, getReason()) : Result.passed(0.5); } String getReason() { DistanceVectors distanceVectors = getDistances(); return String.format( (Locale) null, "{distanceVectors=%s, isHorizontal=%s, velocityToDistanceMultiplier=%f, " + "horizontalFlingThreshold=%f, verticalFlingThreshold=%f, " + "horizontalSwipeThreshold=%f, verticalSwipeThreshold=%s}", distanceVectors, isHorizontal(), mVelocityToDistanceMultiplier, mHorizontalFlingThresholdPx, mVerticalFlingThresholdPx, mHorizontalSwipeThresholdPx, mVerticalSwipeThresholdPx); } Result isLongSwipe() { boolean longSwipe = getPassedDistanceThreshold(); logDebug("Is longSwipe? " + longSwipe); return longSwipe ? Result.passed(0.5) : falsed(0.5, getReason()); } private boolean getPassedDistanceThreshold() { DistanceVectors distanceVectors = getDistances(); if (isHorizontal()) { logDebug("Horizontal swipe distance: " + Math.abs(distanceVectors.mDx)); logDebug("Threshold: " + mHorizontalSwipeThresholdPx); return Math.abs(distanceVectors.mDx) >= mHorizontalSwipeThresholdPx; } logDebug("Vertical swipe distance: " + Math.abs(distanceVectors.mDy)); logDebug("Threshold: " + mVerticalSwipeThresholdPx); return Math.abs(distanceVectors.mDy) >= mVerticalSwipeThresholdPx; } private boolean getPassedFlingThreshold() { DistanceVectors distanceVectors = getDistances(); float dX = distanceVectors.mDx + distanceVectors.mVx * mVelocityToDistanceMultiplier; float dY = distanceVectors.mDy + distanceVectors.mVy * mVelocityToDistanceMultiplier; if (isHorizontal()) { logDebug("Horizontal swipe and fling distance: " + distanceVectors.mDx + ", " + distanceVectors.mVx * mVelocityToDistanceMultiplier); logDebug("Threshold: " + mHorizontalFlingThresholdPx); return Math.abs(dX) >= mHorizontalFlingThresholdPx; } logDebug("Vertical swipe and fling distance: " + distanceVectors.mDy + ", " + distanceVectors.mVy * mVelocityToDistanceMultiplier); logDebug("Threshold: " + mVerticalFlingThresholdPx); return Math.abs(dY) >= mVerticalFlingThresholdPx; } private class DistanceVectors { final float mDx; final float mDy; private final float mVx; private final float mVy; DistanceVectors(float dX, float dY, float vX, float vY) { this.mDx = dX; this.mDy = dY; this.mVx = vX; this.mVy = vY; } @Override public String toString() { return String.format((Locale) null, "{dx=%f, vx=%f, dy=%f, vy=%f}", mDx, mVx, mDy, mVy); } } }