From c9f383bbeddbb0d580b1a9731d41d064686c2d3d Mon Sep 17 00:00:00 2001 From: Arthur Hung Date: Wed, 4 Dec 2019 17:02:19 +0800 Subject: [PATCH] Support dynamic input mapping In addition to static input mapping with IDC or config files that was added in Q, there is a need to be able to change the mapping at runtime. This's different from focus being present to handle lifecycle/app switching/etc, the rest of the system will need to be adapted to work with no focus - Provide an API that could add/update the associations. - Provide an API that could remove the runtime associations. Bug: 136080860 Test: manual Change-Id: Ib80eea9a9c4b4326b0d48a9f496fa550dab2230e --- .../android/hardware/input/IInputManager.aidl | 7 ++ .../android/hardware/input/InputManager.java | 36 +++++++++ core/res/AndroidManifest.xml | 4 + .../server/input/InputManagerService.java | 77 +++++++++++++++++-- ...droid_server_input_InputManagerService.cpp | 8 ++ 5 files changed, 126 insertions(+), 6 deletions(-) diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 638d81b2f6350..6e1987c47d203 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -90,4 +90,11 @@ interface IInputManager { /** Create an input monitor for gestures. */ InputMonitor monitorGestureInput(String name, int displayId); + + // Add a runtime association between the input port and the display port. This overrides any + // static associations. + void addPortAssociation(in String inputPort, int displayPort); + // Remove the runtime association between the input port and the display port. Any existing + // static association for the cleared input port will be restored. + void removePortAssociation(in String inputPort); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 8d32db013fb3b..83f01a5dca35a 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -17,6 +17,7 @@ package android.hardware.input; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; @@ -963,6 +964,41 @@ public final class InputManager { } } + /** + * Add a runtime association between the input port and the display port. This overrides any + * static associations. + * @param inputPort The port of the input device. + * @param displayPort The physical port of the associated display. + *

+ * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}. + *

+ * @hide + */ + public void addPortAssociation(@NonNull String inputPort, int displayPort) { + try { + mIm.addPortAssociation(inputPort, displayPort); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Remove the runtime association between the input port and the display port. Any existing + * static association for the cleared input port will be restored. + * @param inputPort The port of the input device to be cleared. + *

+ * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}. + *

+ * @hide + */ + public void removePortAssociation(@NonNull String inputPort) { + try { + mIm.removePortAssociation(inputPort); + } catch (RemoteException ex) { + throw ex.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 ea70dcf28e690..a808ed8fcc811 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4756,6 +4756,10 @@ + + mStaticAssociations; + private final Object mAssociationsLock = new Object(); + @GuardedBy("mAssociationLock") + private final Map mRuntimeAssociations = new HashMap(); private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); @@ -240,6 +243,7 @@ public class InputManagerService extends IInputManager.Stub private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon); private static native void nativeSetPointerCapture(long ptr, boolean detached); private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId); + private static native void nativeNotifyPortAssociationsChanged(long ptr); // Input event injection constants defined in InputDispatcher.h. private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0; @@ -1723,6 +1727,49 @@ public class InputManagerService extends IInputManager.Stub nativeSetCustomPointerIcon(mPtr, icon); } + /** + * Add a runtime association between the input port and the display port. This overrides any + * static associations. + * @param inputPort The port of the input device. + * @param displayPort The physical port of the associated display. + */ + @Override // Binder call + public void addPortAssociation(@NonNull String inputPort, int displayPort) { + if (!checkCallingPermission( + android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT, + "addPortAssociation()")) { + throw new SecurityException( + "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission"); + } + + Objects.requireNonNull(inputPort); + synchronized (mAssociationsLock) { + mRuntimeAssociations.put(inputPort, displayPort); + } + nativeNotifyPortAssociationsChanged(mPtr); + } + + /** + * Remove the runtime association between the input port and the display port. Any existing + * static association for the cleared input port will be restored. + * @param inputPort The port of the input device to be cleared. + */ + @Override // Binder call + public void removePortAssociation(@NonNull String inputPort) { + if (!checkCallingPermission( + android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT, + "clearPortAssociations()")) { + throw new SecurityException( + "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission"); + } + + Objects.requireNonNull(inputPort); + synchronized (mAssociationsLock) { + mRuntimeAssociations.remove(inputPort); + } + nativeNotifyPortAssociationsChanged(mPtr); + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -1743,6 +1790,16 @@ public class InputManagerService extends IInputManager.Stub pw.println(" display: " + v); }); } + + synchronized (mAssociationsLock) { + if (!mRuntimeAssociations.isEmpty()) { + pw.println("Runtime Associations:"); + mRuntimeAssociations.forEach((k, v) -> { + pw.print(" port: " + k); + pw.println(" display: " + v); + }); + } + } } private boolean checkCallingPermission(String permission, String func) { @@ -1766,6 +1823,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void monitor() { synchronized (mInputFilterLock) { } + synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */} nativeMonitor(mPtr); } @@ -1930,7 +1988,7 @@ public class InputManagerService extends IInputManager.Stub * @return Flattened list */ private static List flatten(@NonNull Map map) { - List list = new ArrayList<>(map.size() * 2); + final List list = new ArrayList<>(map.size() * 2); map.forEach((k, v)-> { list.add(k); list.add(v.toString()); @@ -1943,11 +2001,11 @@ public class InputManagerService extends IInputManager.Stub * directory. */ private static Map loadStaticInputPortAssociations() { - File baseDir = Environment.getVendorDirectory(); - File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH); + final File baseDir = Environment.getVendorDirectory(); + final File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH); try { - InputStream stream = new FileInputStream(confFile); + final InputStream stream = new FileInputStream(confFile); return ConfigurationProcessor.processInputPortAssociations(stream); } catch (FileNotFoundException e) { // Most of the time, file will not exist, which is expected. @@ -1960,7 +2018,14 @@ public class InputManagerService extends IInputManager.Stub // Native callback private String[] getInputPortAssociations() { - List associationList = flatten(mStaticAssociations); + final Map associations = new HashMap<>(mStaticAssociations); + + // merge the runtime associations. + synchronized (mAssociationsLock) { + associations.putAll(mRuntimeAssociations); + } + + final List associationList = flatten(associations); return associationList.toArray(new String[0]); } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 2e8e5e7b706a4..c0891d7397889 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1757,6 +1757,12 @@ static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jclass /* clazz */, jlon return im->getInputManager()->getReader()->canDispatchToDisplay(deviceId, displayId); } +static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) { + NativeInputManager* im = reinterpret_cast(ptr); + im->getInputManager()->getReader()->requestRefreshConfiguration( + InputReaderConfiguration::CHANGE_DISPLAY_INFO); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -1842,6 +1848,8 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeSetCustomPointerIcon }, { "nativeCanDispatchToDisplay", "(JII)Z", (void*) nativeCanDispatchToDisplay }, + { "nativeNotifyPortAssociationsChanged", "(J)V", + (void*) nativeNotifyPortAssociationsChanged }, }; #define FIND_CLASS(var, className) \