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

Commit e1c367eb authored by Brad Hinegardner's avatar Brad Hinegardner
Browse files

Skip falsing on non-touchscreen device sources

Previously we were analyzing the motion event classification to
determine if the device was an accurate tool (stylus, touchpad, etc)
in order to skip falsing.

Instead, we should perform the inverse, and can make the determination
that if the device source does not support SOURCE_TOUCHSCREEN, then
falsing should not be run as there is minimal risk of accidental touches

Additionally, we should not delay processing of MotionEvents on these
types of input.

Bug: 319809270
Test: manual - tested on tablet with trackpad attached
Test: manual - tested on tablet with mouse attached
Test: atest FalsingDataProviderTest.java
Flag: com.android.systemui.non_touchscreen_devices_bypass_falsing
Change-Id: Ie73e5da63452d2b5e15ed830c0a94f03718137a5
parent 465d11f8
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -1405,3 +1405,9 @@ flag {
    }
    }
}
}


flag {
   name: "non_touchscreen_devices_bypass_falsing"
   namespace: "systemui"
   description: "Allow non-touchscreen devices to bypass falsing"
   bug: "319809270"
}
 No newline at end of file
+8 −0
Original line number Original line Diff line number Diff line
@@ -95,6 +95,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
        when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
        when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
        when(mKeyguardStateController.isShowing()).thenReturn(true);
        when(mKeyguardStateController.isShowing()).thenReturn(true);
        when(mFalsingDataProvider.isUnfolded()).thenReturn(false);
        when(mFalsingDataProvider.isUnfolded()).thenReturn(false);
        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(true);
        mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
        mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
                mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
                mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
                mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
                mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
@@ -192,6 +193,13 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
    }
    }


    @Test
    public void testSkipNonTouchscreenDevices() {
        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false);
        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
    }

    @Test
    @Test
    public void testTrackpadGesture() {
    public void testTrackpadGesture() {
        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+88 −2
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.classifier;


import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertThat;


import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.never;
@@ -25,13 +26,20 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;


import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerGlobal;
import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.util.DisplayMetrics;
import android.util.DisplayMetrics;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent;


import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.filters.SmallTest;


import com.android.systemui.Flags;
import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -56,11 +64,15 @@ public class FalsingDataProviderTest extends ClassifierTest {
    private FoldStateListener mFoldStateListener;
    private FoldStateListener mFoldStateListener;
    private final DockManagerFake mDockManager = new DockManagerFake();
    private final DockManagerFake mDockManager = new DockManagerFake();
    private DisplayMetrics mDisplayMetrics;
    private DisplayMetrics mDisplayMetrics;
    private IInputManager mIInputManager;
    private InputManagerGlobal.TestSession inputManagerGlobalTestSession;


    @Before
    @Before
    public void setup() {
    public void setup() {
        super.setup();
        super.setup();
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);
        mIInputManager = mock(IInputManager.Stub.class);
        inputManagerGlobalTestSession = InputManagerGlobal.createTestSession(mIInputManager);
        mDisplayMetrics = new DisplayMetrics();
        mDisplayMetrics = new DisplayMetrics();
        mDisplayMetrics.xdpi = 100;
        mDisplayMetrics.xdpi = 100;
        mDisplayMetrics.ydpi = 100;
        mDisplayMetrics.ydpi = 100;
@@ -73,6 +85,7 @@ public class FalsingDataProviderTest extends ClassifierTest {
    public void tearDown() {
    public void tearDown() {
        super.tearDown();
        super.tearDown();
        mDataProvider.onSessionEnd();
        mDataProvider.onSessionEnd();
        inputManagerGlobalTestSession.close();
    }
    }


    @Test
    @Test
@@ -377,6 +390,79 @@ public class FalsingDataProviderTest extends ClassifierTest {
        assertThat(mDataProvider.isA11yAction()).isTrue();
        assertThat(mDataProvider.isA11yAction()).isTrue();
    }
    }


    @Test
    @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
    public void test_isTouchscreenSource_flagOff_alwaysTrue() {
        assertThat(mDataProvider.isTouchScreenSource()).isTrue();
    }

    @Test
    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
    public void test_isTouchscreenSource_recentEventsEmpty_true() {
        //send no events into the data provider
        assertThat(mDataProvider.isTouchScreenSource()).isTrue();
    }

    @Test
    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
    public void test_isTouchscreenSource_latestDeviceTouchscreen_true() throws RemoteException {
        int deviceId = 999;

        InputDevice device = new InputDevice.Builder()
                .setSources(InputDevice.SOURCE_CLASS_TRACKBALL | InputDevice.SOURCE_TOUCHSCREEN)
                .setId(deviceId)
                .build();
        when(mIInputManager.getInputDeviceIds()).thenReturn(new int[]{deviceId});
        when(mIInputManager.getInputDevice(anyInt())).thenReturn(device);

        MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1,
                MotionEvent.PointerProperties.createArray(1),
                MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, deviceId, 0,
                InputDevice.SOURCE_CLASS_NONE, 0, 0, 0);

        mDataProvider.onMotionEvent(event);
        boolean result = mDataProvider.isTouchScreenSource();
        assertThat(result).isTrue();
    }

    @Test
    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
    public void test_isTouchscreenSource_latestDeviceNonTouchscreen_false() throws RemoteException {
        int deviceId = 9999;

        InputDevice device = new InputDevice.Builder()
                .setSources(InputDevice.SOURCE_CLASS_TRACKBALL)
                .setId(deviceId)
                .build();
        when(mIInputManager.getInputDeviceIds()).thenReturn(new int[]{deviceId});
        when(mIInputManager.getInputDevice(anyInt())).thenReturn(device);

        MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1,
                MotionEvent.PointerProperties.createArray(1),
                MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, deviceId, 0,
                InputDevice.SOURCE_CLASS_NONE, 0, 0, 0);

        mDataProvider.onMotionEvent(event);
        boolean result = mDataProvider.isTouchScreenSource();
        assertThat(result).isFalse();
    }

    @Test
    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
    public void test_isTouchscreenSource_latestDeviceNull_true() {
        // Do not mock InputManager for this test
        inputManagerGlobalTestSession.close();

        int nonExistentDeviceId = 9997;
        MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1,
                MotionEvent.PointerProperties.createArray(1),
                MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, nonExistentDeviceId, 0,
                InputDevice.SOURCE_CLASS_NONE, 0, 0, 0);

        mDataProvider.onMotionEvent(event);
        assertThat(mDataProvider.isTouchScreenSource()).isTrue();
    }

    @Test
    @Test
    public void test_UnfoldedState_Folded() {
    public void test_UnfoldedState_Folded() {
        FalsingDataProvider falsingDataProvider = createWithFoldCapability(true);
        FalsingDataProvider falsingDataProvider = createWithFoldCapability(true);
@@ -413,7 +499,7 @@ public class FalsingDataProviderTest extends ClassifierTest {
    }
    }


    private FalsingDataProvider createWithFoldCapability(boolean foldable) {
    private FalsingDataProvider createWithFoldCapability(boolean foldable) {
        return new FalsingDataProvider(
        return new FalsingDataProvider(mDisplayMetrics, mBatteryController, mFoldStateListener,
                mDisplayMetrics, mBatteryController, mFoldStateListener, mDockManager, foldable);
                mDockManager, foldable);
    }
    }
}
}
+1 −2
Original line number Original line Diff line number Diff line
@@ -34,8 +34,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
import com.android.systemui.classifier.HistoryTracker.BeliefListener;
import com.android.systemui.classifier.HistoryTracker.BeliefListener;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardStateController;


@@ -396,6 +394,7 @@ public class BrightLineFalsingManager implements FalsingManager {
                || mDataProvider.isA11yAction()
                || mDataProvider.isA11yAction()
                || mDataProvider.isFromTrackpad()
                || mDataProvider.isFromTrackpad()
                || mDataProvider.isFromKeyboard()
                || mDataProvider.isFromKeyboard()
                || !mDataProvider.isTouchScreenSource()
                || mDataProvider.isUnfolded();
                || mDataProvider.isUnfolded();
    }
    }


+21 −1
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.android.systemui.dock.DockManager.DockEventListener;
import android.hardware.SensorManager;
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.BiometricSourceType;
import android.util.Log;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent;


@@ -28,6 +29,7 @@ import androidx.annotation.VisibleForTesting;


import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Flags;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.Main;
@@ -343,7 +345,9 @@ class FalsingCollectorImpl implements FalsingCollector {
        // will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
        // will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
        // avoidGesture must be called immediately following the MotionEvent.ACTION_DOWN, before
        // avoidGesture must be called immediately following the MotionEvent.ACTION_DOWN, before
        // any other events are processed, otherwise the whole gesture will be recorded.
        // any other events are processed, otherwise the whole gesture will be recorded.
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
        //
        // We should only delay processing of these events for touchscreen sources
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN && isTouchscreenSource(ev)) {
            // Make a copy of ev, since it will be recycled after we exit this method.
            // Make a copy of ev, since it will be recycled after we exit this method.
            mPendingDownEvent = MotionEvent.obtain(ev);
            mPendingDownEvent = MotionEvent.obtain(ev);
            mAvoidGesture = false;
            mAvoidGesture = false;
@@ -410,6 +414,22 @@ class FalsingCollectorImpl implements FalsingCollector {
        mFalsingDataProvider.onA11yAction();
        mFalsingDataProvider.onA11yAction();
    }
    }


    /**
     * returns {@code true} if the device supports Touchscreen, {@code false} otherwise. Defaults to
     * {@code true} if the device is {@code null}
     */
    private boolean isTouchscreenSource(MotionEvent ev) {
        if (!Flags.nonTouchscreenDevicesBypassFalsing()) {
            return true;
        }
        InputDevice device = ev.getDevice();
        if (device != null) {
            return device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN);
        } else {
            return true;
        }
    }

    private boolean shouldSessionBeActive() {
    private boolean shouldSessionBeActive() {
        return mScreenOn
        return mScreenOn
                && (mState == StatusBarState.KEYGUARD)
                && (mState == StatusBarState.KEYGUARD)
Loading