Check virtual displays for ime dispatch

Bug: 184615313
Test: atest -a inputflinger_tests and atest -a libinput_tests
Change-Id: Ide4d7a0530e00eee3e2b7456d1d93df753b70afd
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 336fbf2..19fb845 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -117,6 +117,11 @@
     // static association for the cleared input port will be restored.
     void removePortAssociation(in String inputPort);
 
+    // Add a runtime association between the input device and display.
+    void addUniqueIdAssociation(in String inputDeviceName, in String displayUniqueId);
+    // Remove the runtime association between the input device and display.
+    void removeUniqueIdAssociation(in String inputDeviceName);
+
     InputSensorInfo[] getSensorList(int deviceId);
 
     boolean registerSensorListener(IInputSensorEventListener listener);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index b6d2eaf..648fda7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1293,7 +1293,7 @@
      * @param inputPort The port of the input device.
      * @param displayPort The physical port of the associated display.
      * <p>
-     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
@@ -1310,7 +1310,7 @@
      * static association for the cleared input port will be restored.
      * @param inputPort The port of the input device to be cleared.
      * <p>
-     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
@@ -1322,6 +1322,41 @@
         }
     }
 
+    /**
+     * Add a runtime association between the input device name and display, by unique id. Input
+     * device names are expected to be unique.
+     * @param inputDeviceName The name of the input device.
+     * @param displayUniqueId The unique id of the associated display.
+     * <p>
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
+     * </p>
+     * @hide
+     */
+    public void addUniqueIdAssociation(@NonNull String inputDeviceName,
+            @NonNull String displayUniqueId) {
+        try {
+            mIm.addUniqueIdAssociation(inputDeviceName, displayUniqueId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes a runtime association between the input device and display.
+     * @param inputDeviceName The name of the input device.
+     * <p>
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
+     * </p>
+     * @hide
+     */
+    public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+        try {
+            mIm.removeUniqueIdAssociation(inputDeviceName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private void populateInputDevicesLocked() {
         if (mInputDevicesChangedListener == null) {
             final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b1cc2ec..9fd413a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5632,7 +5632,7 @@
                 android:protectionLevel="signature|recents" />
     <!--  Allows the caller to change the associations between input devices and displays.
         Very dangerous! @hide -->
-    <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT"
+    <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY"
                 android:protectionLevel="signature" />
 
     <!-- Allows query of any normal app on the device, regardless of manifest declarations.
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 0a800e9..c51571a 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -270,6 +270,8 @@
     private final Object mAssociationsLock = new Object();
     @GuardedBy("mAssociationLock")
     private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<String, Integer>();
+    @GuardedBy("mAssociationLock")
+    private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
 
     private static native long nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
@@ -340,6 +342,7 @@
             boolean enabled);
     private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId);
     private static native void nativeNotifyPortAssociationsChanged(long ptr);
+    private static native void nativeChangeUniqueIdAssociation(long ptr);
     private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled);
     private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId);
     private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType);
@@ -2222,10 +2225,10 @@
     @Override // Binder call
     public void addPortAssociation(@NonNull String inputPort, int displayPort) {
         if (!checkCallingPermission(
-                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT,
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "addPortAssociation()")) {
             throw new SecurityException(
-                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission");
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
         }
 
         Objects.requireNonNull(inputPort);
@@ -2243,10 +2246,10 @@
     @Override // Binder call
     public void removePortAssociation(@NonNull String inputPort) {
         if (!checkCallingPermission(
-                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT,
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "clearPortAssociations()")) {
             throw new SecurityException(
-                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission");
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
         }
 
         Objects.requireNonNull(inputPort);
@@ -2256,6 +2259,49 @@
         nativeNotifyPortAssociationsChanged(mPtr);
     }
 
+    /**
+     * Add a runtime association between the input device name and the display unique id.
+     * @param inputDeviceName The name of the input device.
+     * @param displayUniqueId The unique id of the associated display.
+     */
+    @Override // Binder call
+    public void addUniqueIdAssociation(@NonNull String inputDeviceName,
+            @NonNull String displayUniqueId) {
+        if (!checkCallingPermission(
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+                "addNameAssociation()")) {
+            throw new SecurityException(
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
+        }
+
+        Objects.requireNonNull(inputDeviceName);
+        Objects.requireNonNull(displayUniqueId);
+        synchronized (mAssociationsLock) {
+            mUniqueIdAssociations.put(inputDeviceName, displayUniqueId);
+        }
+        nativeChangeUniqueIdAssociation(mPtr);
+    }
+
+    /**
+     * Remove the runtime association between the input device and the display.
+     * @param inputDeviceName The port of the input device to be cleared.
+     */
+    @Override // Binder call
+    public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+        if (!checkCallingPermission(
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+                "removeUniqueIdAssociation()")) {
+            throw new SecurityException(
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
+        }
+
+        Objects.requireNonNull(inputDeviceName);
+        synchronized (mAssociationsLock) {
+            mUniqueIdAssociations.remove(inputDeviceName);
+        }
+        nativeChangeUniqueIdAssociation(mPtr);
+    }
+
     @Override // Binder call
     public InputSensorInfo[] getSensorList(int deviceId) {
         return nativeGetSensorList(mPtr, deviceId);
@@ -2790,13 +2836,13 @@
      * key.
      * @return Flattened list
      */
-    private static List<String> flatten(@NonNull Map<String, Integer> map) {
+    private static <T> String[] flatten(@NonNull Map<String, T> map) {
         final List<String> list = new ArrayList<>(map.size() * 2);
         map.forEach((k, v)-> {
             list.add(k);
             list.add(v.toString());
         });
-        return list;
+        return list.toArray(new String[0]);
     }
 
     /**
@@ -2828,8 +2874,17 @@
             associations.putAll(mRuntimeAssociations);
         }
 
-        final List<String> associationList = flatten(associations);
-        return associationList.toArray(new String[0]);
+        return flatten(associations);
+    }
+
+    // Native callback
+    private String[] getInputUniqueIdAssociations() {
+        final Map<String, String> associations;
+        synchronized (mAssociationsLock) {
+            associations = new HashMap<>(mUniqueIdAssociations);
+        }
+
+        return flatten(associations);
     }
 
     /**
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3ee2b8d..cca62b9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -118,6 +118,7 @@
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
     jmethodID getInputPortAssociations;
+    jmethodID getInputUniqueIdAssociations;
     jmethodID getKeyRepeatTimeout;
     jmethodID getKeyRepeatDelay;
     jmethodID getHoverTapTimeout;
@@ -579,6 +580,21 @@
         }
         env->DeleteLocalRef(portAssociations);
     }
+    outConfig->uniqueIdAssociations.clear();
+    jobjectArray uniqueIdAssociations = jobjectArray(
+            env->CallObjectMethod(mServiceObj, gServiceClassInfo.getInputUniqueIdAssociations));
+    if (!checkAndClearExceptionFromCallback(env, "getInputUniqueIdAssociations") &&
+        uniqueIdAssociations) {
+        jsize length = env->GetArrayLength(uniqueIdAssociations);
+        for (jsize i = 0; i < length / 2; i++) {
+            std::string inputDeviceUniqueId =
+                    getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i);
+            std::string displayUniqueId =
+                    getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i + 1);
+            outConfig->uniqueIdAssociations.insert({inputDeviceUniqueId, displayUniqueId});
+        }
+        env->DeleteLocalRef(uniqueIdAssociations);
+    }
 
     jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
             gServiceClassInfo.getHoverTapTimeout);
@@ -2134,6 +2150,12 @@
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 }
 
+static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    im->getInputManager()->getReader()->requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
 static void nativeSetMotionClassifierEnabled(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
                                              jboolean enabled) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -2316,6 +2338,7 @@
          (void*)nativeSetCustomPointerIcon},
         {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay},
         {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged},
+        {"nativeChangeUniqueIdAssociation", "(J)V", (void*)nativeChangeUniqueIdAssociation},
         {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled},
         {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;",
          (void*)nativeGetSensorList},
@@ -2425,6 +2448,9 @@
     GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz,
             "getInputPortAssociations", "()[Ljava/lang/String;");
 
+    GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz,
+                  "getInputUniqueIdAssociations", "()[Ljava/lang/String;");
+
     GET_METHOD_ID(gServiceClassInfo.getKeyRepeatTimeout, clazz,
             "getKeyRepeatTimeout", "()I");