Fix NPE of handling ACTION_MOVE in StatusBarTouchController and added unit test

Fix: 282945183
Test: N/A
Change-Id: I96680f04a6946129b14c365e2300f408dfe8f0c3
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 395833f..6becf0f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -30,6 +30,8 @@
 import android.view.Window;
 import android.view.WindowManager;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -50,12 +52,13 @@
 
     private final Launcher mLauncher;
     private final SystemUiProxy mSystemUiProxy;
-    private final float mTouchSlop;
+    @VisibleForTesting final float mTouchSlop;
     private int mLastAction;
     private final SparseArray<PointF> mDownEvents;
 
     /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
-    private boolean mCanIntercept;
+    @VisibleForTesting
+    boolean mCanIntercept;
 
     public StatusBarTouchController(Launcher l) {
         mLauncher = l;
@@ -82,9 +85,9 @@
 
     @Override
     public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        int action = ev.getActionMasked();
-        int idx = ev.getActionIndex();
-        int pid = ev.getPointerId(idx);
+        final int action = ev.getActionMasked();
+        final int idx = ev.getActionIndex();
+        final int pid = ev.getPointerId(idx);
         if (action == ACTION_DOWN) {
             mCanIntercept = canInterceptTouch(ev);
             if (!mCanIntercept) {
@@ -92,14 +95,14 @@
             }
             mDownEvents.clear();
             mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
-        } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+        } else if (action == MotionEvent.ACTION_POINTER_DOWN) {
             // Check!! should only set it only when threshold is not entered.
             mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
         }
         if (!mCanIntercept) {
             return false;
         }
-        if (action == ACTION_MOVE) {
+        if (action == ACTION_MOVE && mDownEvents.contains(pid)) {
             float dy = ev.getY(idx) - mDownEvents.get(pid).y;
             float dx = ev.getX(idx) - mDownEvents.get(pid).x;
             // Currently input dispatcher will not do touch transfer if there are more than
@@ -126,7 +129,6 @@
             mLauncher.getStatsLogManager().logger()
                     .log(LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN);
             setWindowSlippery(false);
-            return true;
         }
         return true;
     }
@@ -140,7 +142,8 @@
      * Touches can slide out of the window but they cannot necessarily slide
      * back in (unless the other window with touch focus permits it).
      */
-    private void setWindowSlippery(boolean enable) {
+    @VisibleForTesting
+    void setWindowSlippery(boolean enable) {
         Window w = mLauncher.getWindow();
         WindowManager.LayoutParams wlp = w.getAttributes();
         if (enable) {
diff --git a/quickstep/tests/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchControllerTest.kt b/quickstep/tests/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchControllerTest.kt
new file mode 100644
index 0000000..b2f13c7
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchControllerTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 202 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.launcher3.uioverrides.touchcontrollers
+
+import android.view.MotionEvent
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.launcher3.Launcher
+import com.android.launcher3.ui.AbstractLauncherUiTest
+import com.android.launcher3.ui.TaplTestsLauncher3
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatusBarTouchControllerTest : AbstractLauncherUiTest() {
+    @Before
+    @Throws(Exception::class)
+    fun setup() {
+        TaplTestsLauncher3.initialize(this)
+    }
+
+    @Test
+    fun interceptActionDown_canIntercept() {
+        executeOnLauncher { launcher: Launcher? ->
+            val underTest = StatusBarTouchController(launcher)
+            assertFalse(underTest.mCanIntercept)
+            val downEvent = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+            underTest.onControllerInterceptTouchEvent(downEvent)
+
+            assertTrue(underTest.mCanIntercept)
+        }
+    }
+
+    @Test
+    fun interceptActionMove_handledAndSetSlippery() {
+        executeOnLauncher { launcher: Launcher ->
+            val underTest = StatusBarTouchController(launcher)
+            val downEvent = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+            underTest.onControllerInterceptTouchEvent(downEvent)
+            val w = launcher.window
+            assertEquals(0, w.attributes.flags and WindowManager.LayoutParams.FLAG_SLIPPERY)
+            val moveEvent =
+                MotionEvent.obtain(
+                    2,
+                    2,
+                    MotionEvent.ACTION_MOVE,
+                    underTest.mTouchSlop,
+                    underTest.mTouchSlop + 10,
+                    0
+                )
+
+            val handled = underTest.onControllerInterceptTouchEvent(moveEvent)
+
+            assertTrue(handled)
+            assertEquals(
+                WindowManager.LayoutParams.FLAG_SLIPPERY,
+                w.attributes.flags and WindowManager.LayoutParams.FLAG_SLIPPERY
+            )
+        }
+    }
+
+    @Test
+    fun interceptActionMove_not_handled() {
+        executeOnLauncher { launcher: Launcher? ->
+            val underTest = StatusBarTouchController(launcher)
+            val downEvent = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+            underTest.onControllerInterceptTouchEvent(downEvent)
+            val moveEvent =
+                MotionEvent.obtain(
+                    2,
+                    2,
+                    MotionEvent.ACTION_MOVE,
+                    underTest.mTouchSlop + 10,
+                    underTest.mTouchSlop,
+                    0
+                )
+
+            val handled = underTest.onControllerInterceptTouchEvent(moveEvent)
+
+            assertFalse(handled)
+        }
+    }
+
+    @Test
+    fun interceptActionMoveAsFirstGestureEvent_notCrashedNorHandled() {
+        executeOnLauncher { launcher: Launcher? ->
+            val underTest = StatusBarTouchController(launcher)
+            underTest.mCanIntercept = true
+            val moveEvent = MotionEvent.obtain(2, 2, MotionEvent.ACTION_MOVE, 10f, 10f, 0)
+
+            val handled = underTest.onControllerInterceptTouchEvent(moveEvent)
+
+            assertFalse(handled)
+        }
+    }
+
+    @Test
+    fun handleActionUp_setNotSlippery() {
+        executeOnLauncher { launcher: Launcher ->
+            val underTest = StatusBarTouchController(launcher)
+            underTest.mCanIntercept = true
+            underTest.setWindowSlippery(true)
+            val moveEvent = MotionEvent.obtain(2, 2, MotionEvent.ACTION_UP, 10f, 10f, 0)
+
+            val handled = underTest.onControllerTouchEvent(moveEvent)
+
+            assertTrue(handled)
+            assertEquals(
+                0,
+                launcher.window.attributes.flags and WindowManager.LayoutParams.FLAG_SLIPPERY
+            )
+        }
+    }
+
+    @Test
+    fun handleActionCancel_setNotSlippery() {
+        executeOnLauncher { launcher: Launcher ->
+            val underTest = StatusBarTouchController(launcher)
+            underTest.mCanIntercept = true
+            underTest.setWindowSlippery(true)
+            val moveEvent = MotionEvent.obtain(2, 2, MotionEvent.ACTION_CANCEL, 10f, 10f, 0)
+
+            val handled = underTest.onControllerTouchEvent(moveEvent)
+
+            assertTrue(handled)
+            assertEquals(
+                0,
+                launcher.window.attributes.flags and WindowManager.LayoutParams.FLAG_SLIPPERY
+            )
+        }
+    }
+}