Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c9f383bb authored by Arthur Hung's avatar Arthur Hung
Browse files

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
parent 1293e629
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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);
}
+36 −0
Original line number Diff line number Diff line
@@ -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.
     * <p>
     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
     * </p>
     * @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.
     * <p>
     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
     * </p>
     * @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();
+4 −0
Original line number Diff line number Diff line
@@ -4756,6 +4756,10 @@
    <!-- Allows input events to be monitored. Very dangerous!  @hide -->
    <permission android:name="android.permission.MONITOR_INPUT"
                android:protectionLevel="signature" />
    <!--  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"
                android:protectionLevel="signature" />

    <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
    <permission android:name="android.permission.QUERY_ALL_PACKAGES"
+71 −6
Original line number Diff line number Diff line
@@ -83,11 +83,11 @@ import android.view.ViewConfiguration;
import android.widget.Toast;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
@@ -186,6 +186,9 @@ public class InputManagerService extends IInputManager.Stub
    // The associations of input devices to displays by port. Maps from input device port (String)
    // to display id (int). Currently only accessed by InputReader.
    private final Map<String, Integer> mStaticAssociations;
    private final Object mAssociationsLock = new Object();
    @GuardedBy("mAssociationLock")
    private final Map<String, Integer> mRuntimeAssociations = new HashMap<String, Integer>();

    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<String> flatten(@NonNull Map<String, Integer> map) {
        List<String> list = new ArrayList<>(map.size() * 2);
        final List<String> 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<String, Integer> 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<String> associationList = flatten(mStaticAssociations);
        final Map<String, Integer> associations = new HashMap<>(mStaticAssociations);

        // merge the runtime associations.
        synchronized (mAssociationsLock) {
            associations.putAll(mRuntimeAssociations);
        }

        final List<String> associationList = flatten(associations);
        return associationList.toArray(new String[0]);
    }

+8 −0
Original line number Diff line number Diff line
@@ -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<NativeInputManager*>(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) \