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

Commit ee57097d authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add support for secondary display with display ID"

parents eafbb21f 58a3ba63
Loading
Loading
Loading
Loading
+245 −188
Original line number Diff line number Diff line
@@ -16,15 +16,20 @@

package com.android.commands.input;

import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;

import android.hardware.input.InputManager;
import android.os.SystemClock;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import com.android.internal.os.BaseCommand;

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

@@ -33,9 +38,11 @@ import java.util.Map;
 * desired character output.
 */

public class Input {
public class Input extends BaseCommand {
    private static final String TAG = "Input";
    private static final String INVALID_ARGUMENTS = "Error: Invalid arguments for command: ";
    private static final String INVALID_DISPLAY_ARGUMENTS =
            "Error: Invalid arguments for display ID.";

    private static final Map<String, Integer> SOURCES = new HashMap<String, Integer>() {{
        put("keyboard", InputDevice.SOURCE_KEYBOARD);
@@ -50,6 +57,7 @@ public class Input {
        put("joystick", InputDevice.SOURCE_JOYSTICK);
    }};

    private static final Map<String, InputCmd> COMMANDS = new HashMap<String, InputCmd>();

    /**
     * Command-line entry point.
@@ -60,97 +68,72 @@ public class Input {
        (new Input()).run(args);
    }

    private void run(String[] args) {
        if (args.length < 1) {
            showUsage();
            return;
    Input() {
        COMMANDS.put("text", new InputText());
        COMMANDS.put("keyevent", new InputKeyEvent());
        COMMANDS.put("tap", new InputTap());
        COMMANDS.put("swipe", new InputSwipe());
        COMMANDS.put("draganddrop", new InputDragAndDrop());
        COMMANDS.put("press", new InputPress());
        COMMANDS.put("roll", new InputRoll());
    }

        int index = 0;
        String command = args[index];
    @Override
    public void onRun() throws Exception {
        String arg = nextArgRequired();
        int inputSource = InputDevice.SOURCE_UNKNOWN;
        if (SOURCES.containsKey(command)) {
            inputSource = SOURCES.get(command);
            index++;
            command = args[index];
        }
        final int length = args.length - index;

        try {
            if (command.equals("text")) {
                if (length == 2) {
                    inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
                    sendText(inputSource, args[index+1]);
                    return;
                }
            } else if (command.equals("keyevent")) {
                if (length >= 2) {
                    final boolean longpress = "--longpress".equals(args[index + 1]);
                    final int start = longpress ? index + 2 : index + 1;
                    inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
                    if (args.length > start) {
                        for (int i = start; i < args.length; i++) {
                            int keyCode = KeyEvent.keyCodeFromString(args[i]);
                            sendKeyEvent(inputSource, keyCode, longpress);
                        }
                        return;
        // Get source (optional).
        if (SOURCES.containsKey(arg)) {
            inputSource = SOURCES.get(arg);
            arg = nextArgRequired();
        }

        // Get displayId (optional).
        int displayId = INVALID_DISPLAY;
        if ("-d".equals(arg)) {
            displayId = getDisplayId();
            arg = nextArgRequired();
        }
            } else if (command.equals("tap")) {
                if (length == 3) {
                    inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
                    sendTap(inputSource, Float.parseFloat(args[index+1]),
                            Float.parseFloat(args[index+2]));

        // Get command and run.
        InputCmd cmd = COMMANDS.get(arg);
        if (cmd != null) {
            try {
                cmd.run(inputSource, displayId);
                return;
            } catch (NumberFormatException ex) {
                throw new IllegalArgumentException(INVALID_ARGUMENTS + arg);
            }
            } else if (command.equals("swipe")) {
                int duration = -1;
                inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
                switch (length) {
                    case 6:
                        duration = Integer.parseInt(args[index+5]);
                    case 5:
                        sendSwipe(inputSource,
                                Float.parseFloat(args[index+1]), Float.parseFloat(args[index+2]),
                                Float.parseFloat(args[index+3]), Float.parseFloat(args[index+4]),
                                duration);
                        return;
        }
            } else if (command.equals("draganddrop")) {
                int duration = -1;
                inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
                switch (length) {
                    case 6:
                        duration = Integer.parseInt(args[index+5]);
                    case 5:
                        sendDragAndDrop(inputSource,
                                Float.parseFloat(args[index+1]), Float.parseFloat(args[index+2]),
                                Float.parseFloat(args[index+3]), Float.parseFloat(args[index+4]),
                                duration);
                        return;

        throw new IllegalArgumentException("Error: Unknown command: " + arg);
    }
            } else if (command.equals("press")) {
                inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
                if (length == 1) {
                    sendTap(inputSource, 0.0f, 0.0f);
                    return;

    private int getDisplayId() {
        String displayArg = nextArgRequired();
        if ("INVALID_DISPLAY".equalsIgnoreCase(displayArg)) {
            return INVALID_DISPLAY;
        } else if ("DEFAULT_DISPLAY".equalsIgnoreCase(displayArg)) {
            return DEFAULT_DISPLAY;
        } else {
            try {
                final int displayId = Integer.parseInt(displayArg);
                if (displayId == INVALID_DISPLAY) {
                    return INVALID_DISPLAY;
                }
            } else if (command.equals("roll")) {
                inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
                if (length == 3) {
                    sendMove(inputSource, Float.parseFloat(args[index+1]),
                            Float.parseFloat(args[index+2]));
                    return;
                return Math.max(displayId, 0);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException(INVALID_DISPLAY_ARGUMENTS);
            }
            } else {
                System.err.println("Error: Unknown command: " + command);
                showUsage();
                return;
        }
        } catch (NumberFormatException ex) {
    }
        System.err.println(INVALID_ARGUMENTS + command);
        showUsage();

    class InputText implements InputCmd {
        @Override
        public void run(int inputSource, int displayId) {
            inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
            sendText(inputSource, nextArgRequired(), displayId);
        }

        /**
@@ -159,10 +142,8 @@ public class Input {
         *
         * @param text is a string of characters you want to input to the device.
         */
    private void sendText(int source, String text) {

        StringBuffer buff = new StringBuffer(text);

        private void sendText(int source, final String text, int displayId) {
            final StringBuffer buff = new StringBuffer(text);
            boolean escapeFlag = false;
            for (int i = 0; i < buff.length(); i++) {
                if (escapeFlag) {
@@ -177,79 +158,136 @@ public class Input {
                }
            }

        char[] chars = buff.toString().toCharArray();

        KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
        KeyEvent[] events = kcm.getEvents(chars);
            final char[] chars = buff.toString().toCharArray();
            final KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
            final KeyEvent[] events = kcm.getEvents(chars);
            for (int i = 0; i < events.length; i++) {
                KeyEvent e = events[i];
                if (source != e.getSource()) {
                    e.setSource(source);
                }
                e.setDisplayId(displayId);
                injectKeyEvent(e);
            }
        }
    }

    private void sendKeyEvent(int inputSource, int keyCode, boolean longpress) {
        long now = SystemClock.uptimeMillis();
        injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
    class InputKeyEvent implements InputCmd {
        @Override
        public void run(int inputSource, int displayId) {
            String arg = nextArgRequired();
            final boolean longpress = "--longpress".equals(arg);
            if (longpress) {
            injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 1, 0,
                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_LONG_PRESS,
                    inputSource));
                arg = nextArgRequired();
            }
        injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));

            do {
                final int keycode = KeyEvent.keyCodeFromString(arg);
                sendKeyEvent(inputSource, keycode, longpress, displayId);
            } while ((arg = nextArg()) != null);
        }

    private void sendTap(int inputSource, float x, float y) {
        long now = SystemClock.uptimeMillis();
        injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x, y, 1.0f);
        injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x, y, 0.0f);
        private void sendKeyEvent(int inputSource, int keyCode, boolean longpress, int displayId) {
            final long now = SystemClock.uptimeMillis();
            int repeatCount = 0;

            KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, repeatCount,
                    0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0/*flags*/,
                    inputSource);
            event.setDisplayId(displayId);

            injectKeyEvent(event);
            if (longpress) {
                repeatCount++;
                injectKeyEvent(KeyEvent.changeTimeRepeat(event, now, repeatCount,
                        KeyEvent.FLAG_LONG_PRESS));
            }
            injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP));
        }
    }

    private void sendSwipe(int inputSource, float x1, float y1, float x2, float y2, int duration) {
        if (duration < 0) {
            duration = 300;
    class InputTap implements InputCmd {
        @Override
        public void run(int inputSource, int displayId) {
            inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
            sendTap(inputSource, Float.parseFloat(nextArgRequired()),
                    Float.parseFloat(nextArgRequired()), displayId);
        }
        long now = SystemClock.uptimeMillis();
        injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f);
        long startTime = now;
        long endTime = startTime + duration;
        while (now < endTime) {
            long elapsedTime = now - startTime;
            float alpha = (float) elapsedTime / duration;
            injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, lerp(x1, x2, alpha),
                    lerp(y1, y2, alpha), 1.0f);
            now = SystemClock.uptimeMillis();

        void sendTap(int inputSource, float x, float y, int displayId) {
            final long now = SystemClock.uptimeMillis();
            injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, now, x, y, 1.0f,
                    displayId);
            injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, now, x, y, 0.0f, displayId);
        }
        injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f);
    }

    private void sendDragAndDrop(int inputSource, float x1, float y1, float x2, float y2,
            int dragDuration) {
        if (dragDuration < 0) {
            dragDuration = 300;
    class InputPress extends InputTap {
        @Override
        public void run(int inputSource, int displayId) {
            inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
            sendTap(inputSource, 0.0f, 0.0f, displayId);
        }
        long now = SystemClock.uptimeMillis();
        injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f);
    }

    class InputSwipe implements InputCmd {
        @Override
        public void run(int inputSource, int displayId) {
            inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
            sendSwipe(inputSource, displayId, false);
        }

        void sendSwipe(int inputSource, int displayId, boolean isDragDrop) {
            // Parse two points and duration.
            final float x1 = Float.parseFloat(nextArgRequired());
            final float y1 = Float.parseFloat(nextArgRequired());
            final float x2 = Float.parseFloat(nextArgRequired());
            final float y2 = Float.parseFloat(nextArgRequired());
            String durationArg = nextArg();
            int duration = durationArg != null ? Integer.parseInt(durationArg) : -1;
            if (duration < 0) {
                duration = 300;
            }

            final long down = SystemClock.uptimeMillis();
            injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, down, down, x1, y1, 1.0f,
                    displayId);
            if (isDragDrop) {
                // long press until drag start.
                try {
                    Thread.sleep(ViewConfiguration.getLongPressTimeout());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        now = SystemClock.uptimeMillis();
        long startTime = now;
        long endTime = startTime + dragDuration;
            }
            long now = SystemClock.uptimeMillis();
            final long endTime = down + duration;
            while (now < endTime) {
            long elapsedTime = now - startTime;
            float alpha = (float) elapsedTime / dragDuration;
            injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, lerp(x1, x2, alpha),
                    lerp(y1, y2, alpha), 1.0f);
                final long elapsedTime = now - down;
                final float alpha = (float) elapsedTime / duration;
                injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, down, now,
                        lerp(x1, x2, alpha), lerp(y1, y2, alpha), 1.0f, displayId);
                now = SystemClock.uptimeMillis();
            }
        injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f);
            injectMotionEvent(inputSource, MotionEvent.ACTION_UP, down, now, x2, y2, 0.0f,
                    displayId);
        }
    }

    class InputDragAndDrop extends InputSwipe {
        @Override
        public void run(int inputSource, int displayId) {
            inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
            sendSwipe(inputSource, displayId, true);
        }
    }

    class InputRoll implements InputCmd {
        @Override
        public void run(int inputSource, int displayId) {
            inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
            sendMove(inputSource, Float.parseFloat(nextArgRequired()),
                    Float.parseFloat(nextArgRequired()), displayId);
        }

        /**
@@ -259,18 +297,27 @@ public class Input {
         * @param dx change in x coordinate due to move
         * @param dy change in y coordinate due to move
         */
    private void sendMove(int inputSource, float dx, float dy) {
        long now = SystemClock.uptimeMillis();
        injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, dx, dy, 0.0f);
        private void sendMove(int inputSource, float dx, float dy, int displayId) {
            final long now = SystemClock.uptimeMillis();
            injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, now, dx, dy, 0.0f,
                    displayId);
        }
    }

    /**
     * Abstract class for command
     * use nextArgRequired or nextArg to check next argument if necessary.
     */
    private interface InputCmd {
        void run(int inputSource, int displayId);
    }

    private void injectKeyEvent(KeyEvent event) {
        Log.i(TAG, "injectKeyEvent: " + event);
    private static void injectKeyEvent(KeyEvent event) {
        InputManager.getInstance().injectInputEvent(event,
                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
    }

    private int getInputDeviceId(int inputSource) {
    private static int getInputDeviceId(int inputSource) {
        final int DEFAULT_DEVICE_ID = 0;
        int[] devIds = InputDevice.getDeviceIds();
        for (int devId : devIds) {
@@ -287,22 +334,27 @@ public class Input {
     *
     * @param inputSource the InputDevice.SOURCE_* sending the input event
     * @param action the MotionEvent.ACTION_* for the event
     * @param downTime the value of the ACTION_DOWN event happened
     * @param when the value of SystemClock.uptimeMillis() at which the event happened
     * @param x x coordinate of event
     * @param y y coordinate of event
     * @param pressure pressure of event
     */
    private void injectMotionEvent(int inputSource, int action, long when, float x, float y, float pressure) {
    private static void injectMotionEvent(int inputSource, int action, long downTime, long when,
            float x, float y, float pressure, int displayId) {
        final float DEFAULT_SIZE = 1.0f;
        final int DEFAULT_META_STATE = 0;
        final float DEFAULT_PRECISION_X = 1.0f;
        final float DEFAULT_PRECISION_Y = 1.0f;
        final int DEFAULT_EDGE_FLAGS = 0;
        MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, DEFAULT_SIZE,
        MotionEvent event = MotionEvent.obtain(downTime, when, action, x, y, pressure, DEFAULT_SIZE,
                DEFAULT_META_STATE, DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y,
                getInputDeviceId(inputSource), DEFAULT_EDGE_FLAGS);
        event.setSource(inputSource);
        Log.i(TAG, "injectMotionEvent: " + event);
        if (displayId == INVALID_DISPLAY && (inputSource & InputDevice.SOURCE_CLASS_POINTER) != 0) {
            displayId = DEFAULT_DISPLAY;
        }
        event.setDisplayId(displayId);
        InputManager.getInstance().injectInputEvent(event,
                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
    }
@@ -315,24 +367,29 @@ public class Input {
        return inputSource == InputDevice.SOURCE_UNKNOWN ? defaultSource : inputSource;
    }

    private void showUsage() {
        System.err.println("Usage: input [<source>] <command> [<arg>...]");
        System.err.println();
        System.err.println("The sources are: ");
    @Override
    public void onShowUsage(PrintStream out) {
        out.println("Usage: input [<source>] [-d DISPLAY_ID] <command> [<arg>...]");
        out.println();
        out.println("The sources are: ");
        for (String src : SOURCES.keySet()) {
            System.err.println("      " + src);
        }
        System.err.println();
        System.err.println("The commands and default sources are:");
        System.err.println("      text <string> (Default: touchscreen)");
        System.err.println("      keyevent [--longpress] <key code number or name> ..."
            out.println("      " + src);
        }
        out.println();
        out.printf("-d: specify the display ID.\n"
                + "      (Default: %d for key event, %d for motion event if not specified.)",
                INVALID_DISPLAY, DEFAULT_DISPLAY);
        out.println();
        out.println("The commands and default sources are:");
        out.println("      text <string> (Default: touchscreen)");
        out.println("      keyevent [--longpress] <key code number or name> ..."
                + " (Default: keyboard)");
        System.err.println("      tap <x> <y> (Default: touchscreen)");
        System.err.println("      swipe <x1> <y1> <x2> <y2> [duration(ms)]"
        out.println("      tap <x> <y> (Default: touchscreen)");
        out.println("      swipe <x1> <y1> <x2> <y2> [duration(ms)]"
                + " (Default: touchscreen)");
        System.err.println("      draganddrop <x1> <y1> <x2> <y2> [duration(ms)]"
        out.println("      draganddrop <x1> <y1> <x2> <y2> [duration(ms)]"
                + " (Default: touchscreen)");
        System.err.println("      press (Default: trackball)");
        System.err.println("      roll <dx> <dy> (Default: trackball)");
        out.println("      press (Default: trackball)");
        out.println("      roll <dx> <dy> (Default: trackball)");
    }
}