Moving trackpad device tracking to a separate class

This would eventually lead to breaking apart TIS into smaller classes,
which can be injected using dagger.
This is needed to migrate DisplayController to dagger

Bug: 361850561
Test: Presubmit
Flag: EXEMPT dagger refactor
Change-Id: Ic15386136bf1705e76c08536cc0790f51e19ab73
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index a04ff2e..620e2b7 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -217,7 +217,7 @@
 
     @Override
     protected boolean isLauncherInitialized() {
-        return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
+        return super.isLauncherInitialized() && SystemUiProxy.INSTANCE.get(mContext).isActive();
     }
 
     private void enableBlockingTimeout(
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index f5cc518..9870ecf 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -45,13 +45,11 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Region;
-import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.util.ArraySet;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputDevice;
@@ -59,7 +57,6 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.BinderThread;
-import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -98,6 +95,7 @@
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActiveGestureLog.CompoundString;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
+import com.android.quickstep.util.ActiveTrackpadList;
 import com.android.quickstep.util.ContextualSearchInvoker;
 import com.android.quickstep.util.ContextualSearchStateManager;
 import com.android.quickstep.views.RecentsViewContainer;
@@ -124,7 +122,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -188,7 +185,6 @@
                 tis.initInputMonitor("TISBinder#onInitialize()");
                 tis.preloadOverview(true /* fromInit */);
             }));
-            sIsInitialized = true;
         }
 
         @BinderThread
@@ -504,72 +500,8 @@
         }
     }
 
-    private final InputManager.InputDeviceListener mInputDeviceListener =
-            new InputManager.InputDeviceListener() {
-                @Override
-                public void onInputDeviceAdded(int deviceId) {
-                    if (isTrackpadDevice(deviceId)) {
-                        // This updates internal TIS state so it needs to also run on the main
-                        // thread.
-                        MAIN_EXECUTOR.execute(() -> {
-                            boolean wasEmpty = mTrackpadsConnected.isEmpty();
-                            mTrackpadsConnected.add(deviceId);
-                            if (wasEmpty) {
-                                update();
-                            }
-                        });
-                    }
-                }
-
-                @Override
-                public void onInputDeviceChanged(int deviceId) {
-                }
-
-                @Override
-                public void onInputDeviceRemoved(int deviceId) {
-                    // This updates internal TIS state so it needs to also run on the main
-                    // thread.
-                    MAIN_EXECUTOR.execute(() -> {
-                        mTrackpadsConnected.remove(deviceId);
-                        if (mTrackpadsConnected.isEmpty()) {
-                            update();
-                        }
-                    });
-                }
-
-                @MainThread
-                private void update() {
-                    if (mInputMonitorCompat != null && !mTrackpadsConnected.isEmpty()) {
-                        // Don't destroy and reinitialize input monitor due to trackpad
-                        // connecting when it's already set up.
-                        return;
-                    }
-                    initInputMonitor("onTrackpadConnected()");
-                }
-
-                private boolean isTrackpadDevice(int deviceId) {
-                    // This is a blocking binder call that should run on a bg thread.
-                    InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
-                    if (inputDevice == null) {
-                        return false;
-                    }
-                    return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
-                            | InputDevice.SOURCE_TOUCHPAD);
-                }
-            };
-
-    private static boolean sConnected = false;
-    private static boolean sIsInitialized = false;
     private RotationTouchHelper mRotationTouchHelper;
 
-    public static boolean isConnected() {
-        return sConnected;
-    }
-
-    public static boolean isInitialized() {
-        return sIsInitialized;
-    }
-
     private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
             this::createLauncherSwipeHandler;
     private final AbsSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
@@ -618,8 +550,7 @@
     private TaskbarManager mTaskbarManager;
     private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
     private AllAppsActionManager mAllAppsActionManager;
-    private InputManager mInputManager;
-    private final Set<Integer> mTrackpadsConnected = new ArraySet<>();
+    private ActiveTrackpadList mTrackpadsConnected;
 
     private NavigationMode mGestureStartNavMode = null;
 
@@ -638,13 +569,15 @@
         mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         mAllAppsActionManager = new AllAppsActionManager(
                 this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
-        mInputManager = getSystemService(InputManager.class);
-        mInputManager.registerInputDeviceListener(mInputDeviceListener,
-                UI_HELPER_EXECUTOR.getHandler());
-        int [] inputDevices = mInputManager.getInputDeviceIds();
-        for (int inputDeviceId : inputDevices) {
-            mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
-        }
+        mTrackpadsConnected = new ActiveTrackpadList(this, () -> {
+            if (mInputMonitorCompat != null && !mTrackpadsConnected.isEmpty()) {
+                // Don't destroy and reinitialize input monitor due to trackpad
+                // connecting when it's already set up.
+                return;
+            }
+            initInputMonitor("onTrackpadConnected()");
+        });
+
         mDesktopVisibilityController = new DesktopVisibilityController(this);
         mTaskbarManager = new TaskbarManager(
                 this, mAllAppsActionManager, mNavCallbacks, mDesktopVisibilityController);
@@ -656,8 +589,6 @@
         // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
         LockedUserState.get(this).runOnUserUnlocked(mUserUnlockedRunnable);
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
-        sConnected = true;
-
         ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
     }
 
@@ -793,7 +724,6 @@
     public void onDestroy() {
         Log.d(TAG, "onDestroy: user=" + getUserId()
                 + " instance=" + System.identityHashCode(this));
-        sIsInitialized = false;
         if (LockedUserState.get(this).isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
             mOverviewComponentObserver.setHomeDisabled(false);
@@ -805,16 +735,13 @@
 
         mAllAppsActionManager.onDestroy();
 
-        mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
-        mTrackpadsConnected.clear();
-
+        mTrackpadsConnected.destroy();
         mTaskbarManager.destroy();
         if (mDesktopAppLaunchTransitionManager != null) {
             mDesktopAppLaunchTransitionManager.unregisterTransitions();
         }
         mDesktopAppLaunchTransitionManager = null;
         mDesktopVisibilityController.onDestroy();
-        sConnected = false;
 
         LockedUserState.get(this).removeOnUserUnlockedRunnable(mUserUnlockedRunnable);
         ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
diff --git a/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
new file mode 100644
index 0000000..63bd03d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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.quickstep.util
+
+import android.content.Context
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.IntSet
+
+/** Utility class to maintain a list of actively connected trackpad devices */
+class ActiveTrackpadList(ctx: Context, private val updateCallback: Runnable) :
+    IntSet(), InputManager.InputDeviceListener {
+
+    private val inputManager = ctx.getSystemService(InputManager::class.java)!!
+
+    init {
+        inputManager.registerInputDeviceListener(this, Executors.UI_HELPER_EXECUTOR.handler)
+        inputManager.inputDeviceIds.forEach { deviceId -> onInputDeviceAdded(deviceId) }
+    }
+
+    override fun onInputDeviceAdded(deviceId: Int) {
+        if (isTrackpadDevice(deviceId)) {
+            // This updates internal TIS state so it needs to also run on the main
+            // thread.
+            Executors.MAIN_EXECUTOR.execute {
+                val wasEmpty = isEmpty
+                add(deviceId)
+                if (wasEmpty) update()
+            }
+        }
+    }
+
+    override fun onInputDeviceChanged(deviceId: Int) {}
+
+    override fun onInputDeviceRemoved(deviceId: Int) {
+        // This updates internal TIS state so it needs to also run on the main thread.
+        Executors.MAIN_EXECUTOR.execute {
+            remove(deviceId)
+            if (isEmpty) update()
+        }
+    }
+
+    private fun update() {
+        updateCallback.run()
+    }
+
+    fun destroy() {
+        inputManager.unregisterInputDeviceListener(this)
+        clear()
+    }
+
+    /** This is a blocking binder call that should run on a bg thread. */
+    private fun isTrackpadDevice(deviceId: Int) =
+        inputManager.getInputDevice(deviceId)?.sources ==
+            (InputDevice.SOURCE_MOUSE or InputDevice.SOURCE_TOUCHPAD)
+}