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
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index be748da..6f5ad34 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1405,3 +1405,9 @@
     }
 }
 
+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
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 53d82d7..956c129 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -95,6 +95,7 @@
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mFalsingDataProvider.isUnfolded()).thenReturn(false);
+        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
                 mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
                 mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
@@ -193,6 +194,13 @@
     }
 
     @Test
+    public void testSkipNonTouchscreenDevices() {
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+    }
+
+    @Test
     public void testTrackpadGesture() {
         assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
         when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 49c6239..df4b048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -25,13 +26,20 @@
 import static org.mockito.Mockito.when;
 
 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.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -56,11 +64,15 @@
     private FoldStateListener mFoldStateListener;
     private final DockManagerFake mDockManager = new DockManagerFake();
     private DisplayMetrics mDisplayMetrics;
+    private IInputManager mIInputManager;
+    private InputManagerGlobal.TestSession inputManagerGlobalTestSession;
 
     @Before
     public void setup() {
         super.setup();
         MockitoAnnotations.initMocks(this);
+        mIInputManager = mock(IInputManager.Stub.class);
+        inputManagerGlobalTestSession = InputManagerGlobal.createTestSession(mIInputManager);
         mDisplayMetrics = new DisplayMetrics();
         mDisplayMetrics.xdpi = 100;
         mDisplayMetrics.ydpi = 100;
@@ -73,6 +85,7 @@
     public void tearDown() {
         super.tearDown();
         mDataProvider.onSessionEnd();
+        inputManagerGlobalTestSession.close();
     }
 
     @Test
@@ -378,6 +391,79 @@
     }
 
     @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
     public void test_UnfoldedState_Folded() {
         FalsingDataProvider falsingDataProvider = createWithFoldCapability(true);
         when(mFoldStateListener.getFolded()).thenReturn(true);
@@ -413,7 +499,7 @@
     }
 
     private FalsingDataProvider createWithFoldCapability(boolean foldable) {
-        return new FalsingDataProvider(
-                mDisplayMetrics, mBatteryController, mFoldStateListener, mDockManager, foldable);
+        return new FalsingDataProvider(mDisplayMetrics, mBatteryController, mFoldStateListener,
+                mDockManager, foldable);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index e1ba93c..83d4091 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -34,8 +34,6 @@
 import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
 import com.android.systemui.classifier.HistoryTracker.BeliefListener;
 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.statusbar.policy.KeyguardStateController;
 
@@ -396,6 +394,7 @@
                 || mDataProvider.isA11yAction()
                 || mDataProvider.isFromTrackpad()
                 || mDataProvider.isFromKeyboard()
+                || !mDataProvider.isTouchScreenSource()
                 || mDataProvider.isUnfolded();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 2eca02c..962ab99 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.util.Log;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -28,6 +29,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Flags;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -343,7 +345,9 @@
         // 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
         // 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.
             mPendingDownEvent = MotionEvent.obtain(ev);
             mAvoidGesture = false;
@@ -410,6 +414,22 @@
         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() {
         return mScreenOn
                 && (mState == StatusBarState.KEYGUARD)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 1501701..769976e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -20,11 +20,13 @@
 
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.util.DisplayMetrics;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 
+import com.android.systemui.Flags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -281,6 +283,9 @@
     }
 
     public boolean isFromTrackpad() {
+        if (Flags.nonTouchscreenDevicesBypassFalsing()) {
+            return false;
+        }
         if (mRecentMotionEvents.isEmpty()) {
             return false;
         }
@@ -290,6 +295,25 @@
                 || classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
     }
 
+    /**
+     * returns {@code true} if the device supports Touchscreen, {@code false} otherwise. Defaults to
+     * {@code true} if the device is {@code null}
+     */
+    public boolean isTouchScreenSource() {
+        if (!Flags.nonTouchscreenDevicesBypassFalsing()) {
+            return true;
+        }
+        if (mRecentMotionEvents.isEmpty()) {
+            return true;
+        }
+        InputDevice device = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getDevice();
+        if (device != null) {
+            return device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN);
+        } else {
+            return true;
+        }
+    }
+
     private void recalculateData() {
         if (!mDirty) {
             return;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index a1bea06..6377717 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -104,6 +104,7 @@
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mFalsingDataProvider.isUnfolded()).thenReturn(false);
+        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
                 mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
                 mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,