Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 07fb7b73 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Add base class for new falsing manager and classifiers.

This adds no functional changes. It merely adds the framework
for a new FalsingManager.

Change-Id: I7f0e3b1363c847fa1eefa54bf7793508fefd1926
Test: manual.
Bug: 111394067
parent e64acbea
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -16,9 +16,13 @@

package com.android.systemui.classifier;

import android.annotation.IntDef;
import android.hardware.SensorEvent;
import android.view.MotionEvent;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * An abstract class for classifiers for touch and sensor events.
 */
@@ -34,6 +38,21 @@ public abstract class Classifier {
    public static final int BOUNCER_UNLOCK = 8;
    public static final int PULSE_EXPAND = 9;

    @IntDef({
            QUICK_SETTINGS,
            NOTIFICATION_DISMISS,
            NOTIFICATION_DRAG_DOWN,
            NOTIFICATION_DOUBLE_TAP,
            UNLOCK,
            LEFT_AFFORDANCE,
            RIGHT_AFFORDANCE,
            GENERIC,
            BOUNCER_UNLOCK,
            PULSE_EXPAND
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface InteractionType {}

    /**
     * Contains all the information about touch events from which the classifier can query
     */
+320 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.brightline;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.util.Log;
import android.view.MotionEvent;

import com.android.systemui.classifier.Classifier;
import com.android.systemui.plugins.FalsingManager;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * FalsingManager designed to make clear why a touch was rejected.
 */
public class BrightLineFalsingManager implements FalsingManager {

    static final boolean DEBUG = false;
    private static final String TAG = "FalsingManagerPlugin";

    private final SensorManager mSensorManager;
    private final FalsingDataProvider mDataProvider;
    private boolean mSessionStarted;

    private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor();

    private final List<FalsingClassifier> mClassifiers;

    private SensorEventListener mSensorEventListener = new SensorEventListener() {
        @Override
        public synchronized void onSensorChanged(SensorEvent event) {
            onSensorEvent(event);
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }
    };

    BrightLineFalsingManager(FalsingDataProvider falsingDataProvider, SensorManager sensorManager) {
        mDataProvider = falsingDataProvider;
        mSensorManager = sensorManager;
        mClassifiers = new ArrayList<>();
        // TODO: add classifiers here.
    }

    private void registerSensors() {
        Sensor s = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
        if (s != null) {
            // This can be expensive, and doesn't need to happen on the main thread.
            mBackgroundExecutor.submit(() -> {
                logDebug("registering sensor listener");
                mSensorManager.registerListener(
                        mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
            });
        }
    }


    private void unregisterSensors() {
        // This can be expensive, and doesn't need to happen on the main thread.
        mBackgroundExecutor.submit(() -> {
            logDebug("unregistering sensor listener");
            mSensorManager.unregisterListener(mSensorEventListener);
        });
    }

    private void sessionStart() {
        logDebug("Starting Session");
        mSessionStarted = true;
        registerSensors();
        mClassifiers.forEach(FalsingClassifier::onSessionStarted);
    }

    private void sessionEnd() {
        if (mSessionStarted) {
            logDebug("Ending Session");
            mSessionStarted = false;
            unregisterSensors();
            mDataProvider.onSessionEnd();
            mClassifiers.forEach(FalsingClassifier::onSessionEnded);
        }
    }

    private void updateInteractionType(@Classifier.InteractionType int type) {
        logDebug("InteractionType: " + type);
        mClassifiers.forEach((classifier) -> classifier.setInteractionType(type));
    }

    @Override
    public boolean isClassiferEnabled() {
        return true;
    }

    @Override
    public boolean isFalseTouch() {
        boolean r = mClassifiers.stream().anyMatch(falsingClassifier -> {
            boolean result = falsingClassifier.isFalseTouch();
            if (result) {
                logInfo(falsingClassifier.getClass().getName() + ": true");
            } else {
                logDebug(falsingClassifier.getClass().getName() + ": false");
            }
            return result;
        });

        logDebug("Is false touch? " + r);

        return r;
    }

    @Override
    public void onTouchEvent(MotionEvent motionEvent, int width, int height) {
        // TODO: some of these classifiers might allow us to abort early, meaning we don't have to
        // make these calls.
        mDataProvider.onMotionEvent(motionEvent);
        mClassifiers.forEach((classifier) -> classifier.onTouchEvent(motionEvent));
    }

    private void onSensorEvent(SensorEvent sensorEvent) {
        // TODO: some of these classifiers might allow us to abort early, meaning we don't have to
        // make these calls.
        mClassifiers.forEach((classifier) -> classifier.onSensorEvent(sensorEvent));
    }

    @Override
    public void onSucccessfulUnlock() {
    }

    @Override
    public void onNotificationActive() {
    }

    @Override
    public void setShowingAod(boolean showingAod) {
        if (showingAod) {
            sessionEnd();
        } else {
            sessionStart();
        }
    }

    @Override
    public void onNotificatonStartDraggingDown() {
        updateInteractionType(Classifier.NOTIFICATION_DRAG_DOWN);

    }

    @Override
    public boolean isUnlockingDisabled() {
        return false;
    }


    @Override
    public void onNotificatonStopDraggingDown() {
    }

    @Override
    public void setNotificationExpanded() {
    }

    @Override
    public void onQsDown() {
        updateInteractionType(Classifier.QUICK_SETTINGS);
    }

    @Override
    public void setQsExpanded(boolean b) {
    }

    @Override
    public boolean shouldEnforceBouncer() {
        return false;
    }

    @Override
    public void onTrackingStarted(boolean secure) {
        updateInteractionType(secure ? Classifier.BOUNCER_UNLOCK : Classifier.UNLOCK);
    }

    @Override
    public void onTrackingStopped() {
    }

    @Override
    public void onLeftAffordanceOn() {
    }

    @Override
    public void onCameraOn() {
    }

    @Override
    public void onAffordanceSwipingStarted(boolean rightCorner) {
        updateInteractionType(
                rightCorner ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
    }

    @Override
    public void onAffordanceSwipingAborted() {
    }

    @Override
    public void onStartExpandingFromPulse() {
        updateInteractionType(Classifier.PULSE_EXPAND);
    }

    @Override
    public void onExpansionFromPulseStopped() {
    }

    @Override
    public Uri reportRejectedTouch() {
        return null;
    }

    @Override
    public void onScreenOnFromTouch() {
        sessionStart();
    }

    @Override
    public boolean isReportingEnabled() {
        return false;
    }

    @Override
    public void onUnlockHintStarted() {
    }

    @Override
    public void onCameraHintStarted() {
    }

    @Override
    public void onLeftAffordanceHintStarted() {
    }

    @Override
    public void onScreenTurningOn() {
        sessionStart();
    }

    @Override
    public void onScreenOff() {
        sessionEnd();
    }


    @Override
    public void onNotificatonStopDismissing() {
    }

    @Override
    public void onNotificationDismissed() {
    }

    @Override
    public void onNotificatonStartDismissing() {
        updateInteractionType(Classifier.NOTIFICATION_DISMISS);
    }

    @Override
    public void onNotificationDoubleTap(boolean b, float v, float v1) {
    }

    @Override
    public void onBouncerShown() {
    }

    @Override
    public void onBouncerHidden() {
    }

    @Override
    public void dump(PrintWriter printWriter) {
    }

    static void logDebug(String msg) {
        logDebug(msg, null);
    }

    static void logDebug(String msg, Throwable throwable) {
        if (DEBUG) {
            Log.d(TAG, msg, throwable);
        }
    }

    static void logInfo(String msg) {
        Log.i(TAG, msg);
    }

    static void logError(String msg) {
        Log.e(TAG, msg);
    }
}
+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.brightline;

import android.hardware.SensorEvent;
import android.view.MotionEvent;

import com.android.systemui.classifier.Classifier;

import java.util.List;

/**
 * Base class for rules that determine False touches.
 */
abstract class FalsingClassifier {
    private final FalsingDataProvider mDataProvider;

    FalsingClassifier(FalsingDataProvider dataProvider) {
        this.mDataProvider = dataProvider;
    }

    List<MotionEvent> getRecentMotionEvents() {
        return mDataProvider.getRecentMotionEvents();
    }

    MotionEvent getFirstMotionEvent() {
        return mDataProvider.getFirstRecentMotionEvent();
    }

    MotionEvent getLastMotionEvent() {
        return mDataProvider.getLastMotionEvent();
    }

    boolean isHorizontal() {
        return mDataProvider.isHorizontal();
    }

    boolean isRight() {
        return mDataProvider.isRight();
    }

    boolean isVertical() {
        return mDataProvider.isVertical();
    }

    boolean isUp() {
        return mDataProvider.isUp();
    }

    float getAngle() {
        return mDataProvider.getAngle();
    }

    int getWidthPixels() {
        return mDataProvider.mWidthPixels;
    }

    int getHeightPixels() {
        return mDataProvider.mHeightPixels;
    }

    float getXdpi() {
        return mDataProvider.mXdpi;
    }

    float getYdpi() {
        return mDataProvider.mYdpi;
    }

    final @Classifier.InteractionType int getInteractionType() {
        return mDataProvider.getInteractionType();
    }

    final void setInteractionType(@Classifier.InteractionType int interactionType) {
        mDataProvider.setInteractionType(interactionType);
    }

    /**
     * Called whenever a MotionEvent occurs.
     *
     * Useful for classifiers that need to see every MotionEvent, but most can probably
     * use {@link #getRecentMotionEvents()} instead, which will return a list of MotionEvents.
     */
    void onTouchEvent(MotionEvent motionEvent) {};

    /**
     * Called whenever a SensorEvent occurs, specifically the ProximitySensor.
     */
    void onSensorEvent(SensorEvent sensorEvent) {};

    /**
     * The phone screen has turned on and we need to begin falsing detection.
     */
    void onSessionStarted() {};

    /**
     * The phone screen has turned off and falsing data can be discarded.
     */
    void onSessionEnded() {};

    /**
     * Returns true if the data captured so far looks like a false touch.
     */
    abstract boolean isFalseTouch();

    static void logDebug(String msg) {
        BrightLineFalsingManager.logDebug(msg);
    }

    static void logInfo(String msg) {
        BrightLineFalsingManager.logInfo(msg);
    }

    static void logError(String msg) {
        BrightLineFalsingManager.logError(msg);
    }
}
+218 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.brightline;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;

import com.android.systemui.classifier.Classifier;

import java.util.ArrayList;
import java.util.List;

/**
 * Acts as a cache and utility class for FalsingClassifiers.
 */
class FalsingDataProvider {

    private static final long MOTION_EVENT_AGE_MS = 1000;
    final int mWidthPixels;
    final int mHeightPixels;
    final float mXdpi;
    final float mYdpi;

    private @Classifier.InteractionType int mInteractionType;
    private final TimeLimitedMotionEventBuffer mRecentMotionEvents =
            new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);

    private boolean mDirty = true;

    private float mAngle = 0;
    private MotionEvent mFirstActualMotionEvent;
    private MotionEvent mFirstRecentMotionEvent;
    private MotionEvent mLastMotionEvent;

    FalsingDataProvider(Context context) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        mXdpi = displayMetrics.xdpi;
        mYdpi = displayMetrics.ydpi;
        mWidthPixels = displayMetrics.widthPixels;
        mHeightPixels = displayMetrics.heightPixels;

        FalsingClassifier.logInfo("xdpi, ydpi: " + mXdpi + ", " + mYdpi);
        FalsingClassifier.logInfo("width, height: " + mWidthPixels + ", " + mHeightPixels);
    }

    void onMotionEvent(MotionEvent motionEvent) {
        if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
            mFirstActualMotionEvent = motionEvent;
        }

        List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent);
        FalsingClassifier.logDebug("Unpacked into: " + motionEvents.size());
        if (BrightLineFalsingManager.DEBUG) {
            for (MotionEvent m : motionEvents) {
                FalsingClassifier.logDebug(
                        "x,y,t: " + m.getX() + "," + m.getY() + "," + m.getEventTime());
            }
        }

        if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
            mRecentMotionEvents.clear();
        }
        mRecentMotionEvents.addAll(motionEvents);

        FalsingClassifier.logDebug("Size: " + mRecentMotionEvents.size());

        mDirty = true;
    }

    List<MotionEvent> getRecentMotionEvents() {
        return mRecentMotionEvents;
    }

    /**
     * interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
     */
    final void setInteractionType(@Classifier.InteractionType int interactionType) {
        this.mInteractionType = interactionType;
    }

    final int getInteractionType() {
        return mInteractionType;
    }

    MotionEvent getFirstActualMotionEvent() {
        return mFirstActualMotionEvent;
    }

    MotionEvent getFirstRecentMotionEvent() {
        recalculateData();
        return mFirstRecentMotionEvent;
    }

    MotionEvent getLastMotionEvent() {
        recalculateData();
        return mLastMotionEvent;
    }

    float getAngle() {
        recalculateData();
        return mAngle;
    }

    boolean isHorizontal() {
        recalculateData();
        return Math.abs(mFirstRecentMotionEvent.getX() - mLastMotionEvent.getX()) > Math
                .abs(mFirstRecentMotionEvent.getY() - mLastMotionEvent.getY());
    }

    boolean isRight() {
        recalculateData();
        return mLastMotionEvent.getX() > mFirstRecentMotionEvent.getX();
    }

    boolean isVertical() {
        return !isHorizontal();
    }

    boolean isUp() {
        recalculateData();
        return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY();
    }

    private void recalculateData() {
        if (!mDirty) {
            return;
        }

        mFirstRecentMotionEvent = mRecentMotionEvents.get(0);
        mLastMotionEvent = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1);

        calculateAngleInternal();

        mDirty = false;
    }

    private void calculateAngleInternal() {
        if (mRecentMotionEvents.size() < 2) {
            mAngle = Float.MAX_VALUE;
        } else {
            float lastX = mLastMotionEvent.getX() - mFirstRecentMotionEvent.getX();
            float lastY = mLastMotionEvent.getY() - mFirstRecentMotionEvent.getY();

            mAngle = (float) Math.atan2(lastY, lastX);
        }
    }

    private List<MotionEvent> unpackMotionEvent(MotionEvent motionEvent) {
        List<MotionEvent> motionEvents = new ArrayList<>();
        List<PointerProperties> pointerPropertiesList = new ArrayList<>();
        int pointerCount = motionEvent.getPointerCount();
        for (int i = 0; i < pointerCount; i++) {
            PointerProperties pointerProperties = new PointerProperties();
            motionEvent.getPointerProperties(i, pointerProperties);
            pointerPropertiesList.add(pointerProperties);
        }
        PointerProperties[] pointerPropertiesArray = new PointerProperties[pointerPropertiesList
                .size()];
        pointerPropertiesList.toArray(pointerPropertiesArray);

        int historySize = motionEvent.getHistorySize();
        for (int i = 0; i < historySize; i++) {
            List<PointerCoords> pointerCoordsList = new ArrayList<>();
            for (int j = 0; j < pointerCount; j++) {
                PointerCoords pointerCoords = new PointerCoords();
                motionEvent.getHistoricalPointerCoords(j, i, pointerCoords);
                pointerCoordsList.add(pointerCoords);
            }
            motionEvents.add(MotionEvent.obtain(
                    motionEvent.getDownTime(),
                    motionEvent.getHistoricalEventTime(i),
                    motionEvent.getAction(),
                    pointerCount,
                    pointerPropertiesArray,
                    pointerCoordsList.toArray(new PointerCoords[0]),
                    motionEvent.getMetaState(),
                    motionEvent.getButtonState(),
                    motionEvent.getXPrecision(),
                    motionEvent.getYPrecision(),
                    motionEvent.getDeviceId(),
                    motionEvent.getEdgeFlags(),
                    motionEvent.getSource(),
                    motionEvent.getFlags()
            ));
        }

        motionEvents.add(MotionEvent.obtainNoHistory(motionEvent));

        return motionEvents;
    }

    void onSessionEnd() {
        mFirstActualMotionEvent = null;

        for (MotionEvent ev : mRecentMotionEvents) {
            ev.recycle();
        }

        mRecentMotionEvents.clear();
    }
}
+242 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading