Controller.java 10.8 KB
Newer Older
R
Romain Vimont 已提交
1 2
package com.genymobile.scrcpy;

3
import android.os.Build;
R
Romain Vimont 已提交
4 5 6 7 8 9 10
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;

import java.io.IOException;
B
brunoais 已提交
11 12 13
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
R
Romain Vimont 已提交
14

R
Romain Vimont 已提交
15
public class Controller {
R
Romain Vimont 已提交
16

17
    private static final int DEFAULT_DEVICE_ID = 0;
R
Romain Vimont 已提交
18

B
brunoais 已提交
19 20
    private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();

R
Romain Vimont 已提交
21
    private final Device device;
R
Romain Vimont 已提交
22
    private final DesktopConnection connection;
R
Romain Vimont 已提交
23
    private final DeviceMessageSender sender;
R
Romain Vimont 已提交
24 25 26

    private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);

R
Romain Vimont 已提交
27 28
    private long lastTouchDown;
    private final PointersState pointersState = new PointersState();
R
Romain Vimont 已提交
29 30
    private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
    private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
R
Romain Vimont 已提交
31

B
brunoais 已提交
32 33
    private boolean keepPowerModeOff;

R
Romain Vimont 已提交
34
    public Controller(Device device, DesktopConnection connection) {
R
Romain Vimont 已提交
35
        this.device = device;
R
Romain Vimont 已提交
36
        this.connection = connection;
37
        initPointers();
R
Romain Vimont 已提交
38
        sender = new DeviceMessageSender(connection);
R
Romain Vimont 已提交
39 40
    }

41
    private void initPointers() {
R
Romain Vimont 已提交
42 43 44 45 46 47
        for (int i = 0; i < PointersState.MAX_POINTERS; ++i) {
            MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
            props.toolType = MotionEvent.TOOL_TYPE_FINGER;

            MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
            coords.orientation = 0;
48
            coords.size = 0;
R
Romain Vimont 已提交
49

50 51
            pointerProperties[i] = props;
            pointerCoords[i] = coords;
R
Romain Vimont 已提交
52 53 54
        }
    }

R
Romain Vimont 已提交
55
    public void control() throws IOException {
R
Romain Vimont 已提交
56
        // on start, power on the device
57
        if (!Device.isScreenOn()) {
58
            device.injectKeycode(KeyEvent.KEYCODE_POWER);
59 60

            // dirty hack
61
            // After POWER is injected, the device is powered on asynchronously.
62 63 64 65 66 67 68
            // To turn the device screen off while mirroring, the client will send a message that
            // would be handled before the device is actually powered on, so its effect would
            // be "canceled" once the device is turned back on.
            // Adding this delay prevents to handle the message before the device is actually
            // powered on.
            SystemClock.sleep(500);
        }
R
Romain Vimont 已提交
69

R
Romain Vimont 已提交
70 71 72
        while (true) {
            handleEvent();
        }
R
Romain Vimont 已提交
73 74
    }

R
Romain Vimont 已提交
75
    public DeviceMessageSender getSender() {
R
Romain Vimont 已提交
76 77 78
        return sender;
    }

R
Romain Vimont 已提交
79
    private void handleEvent() throws IOException {
R
Romain Vimont 已提交
80 81 82
        ControlMessage msg = connection.receiveControlMessage();
        switch (msg.getType()) {
            case ControlMessage.TYPE_INJECT_KEYCODE:
E
e_vigurskiy 已提交
83
                if (device.supportsInputEvents()) {
X
xeropresence 已提交
84
                    injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
E
e_vigurskiy 已提交
85
                }
R
Romain Vimont 已提交
86
                break;
R
Romain Vimont 已提交
87
            case ControlMessage.TYPE_INJECT_TEXT:
E
e_vigurskiy 已提交
88 89 90
                if (device.supportsInputEvents()) {
                    injectText(msg.getText());
                }
R
Romain Vimont 已提交
91
                break;
R
Romain Vimont 已提交
92
            case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
E
e_vigurskiy 已提交
93 94 95
                if (device.supportsInputEvents()) {
                    injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());
                }
R
Romain Vimont 已提交
96
                break;
R
Romain Vimont 已提交
97
            case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
E
e_vigurskiy 已提交
98 99 100
                if (device.supportsInputEvents()) {
                    injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
                }
R
Romain Vimont 已提交
101
                break;
R
Romain Vimont 已提交
102
            case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
E
e_vigurskiy 已提交
103
                if (device.supportsInputEvents()) {
104
                    pressBackOrTurnScreenOn(msg.getAction());
E
e_vigurskiy 已提交
105
                }
106
                break;
R
Romain Vimont 已提交
107
            case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
108
                Device.expandNotificationPanel();
109
                break;
R
Romain Vimont 已提交
110
            case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
111
                Device.collapsePanels();
R
Romain Vimont 已提交
112
                break;
R
Romain Vimont 已提交
113
            case ControlMessage.TYPE_GET_CLIPBOARD:
114
                String clipboardText = Device.getClipboardText();
115 116 117
                if (clipboardText != null) {
                    sender.pushClipboardText(clipboardText);
                }
118
                break;
R
Romain Vimont 已提交
119
            case ControlMessage.TYPE_SET_CLIPBOARD:
120
                setClipboard(msg.getText(), msg.getPaste());
121
                break;
122
            case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
E
e_vigurskiy 已提交
123
                if (device.supportsInputEvents()) {
R
Romain Vimont 已提交
124
                    int mode = msg.getAction();
125
                    boolean setPowerModeOk = Device.setScreenPowerMode(mode);
R
Romain Vimont 已提交
126
                    if (setPowerModeOk) {
B
brunoais 已提交
127
                        keepPowerModeOff = mode == Device.POWER_MODE_OFF;
R
Romain Vimont 已提交
128 129
                        Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
                    }
E
e_vigurskiy 已提交
130
                }
131
                break;
R
Romain Vimont 已提交
132
            case ControlMessage.TYPE_ROTATE_DEVICE:
133
                Device.rotateDevice();
R
Romain Vimont 已提交
134
                break;
135 136
            default:
                // do nothing
R
Romain Vimont 已提交
137 138 139
        }
    }

X
xeropresence 已提交
140
    private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
B
brunoais 已提交
141 142 143
        if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
            schedulePowerModeOff();
        }
X
xeropresence 已提交
144
        return device.injectKeyEvent(action, keycode, repeat, metaState);
R
Romain Vimont 已提交
145 146
    }

R
Romain Vimont 已提交
147 148
    private boolean injectChar(char c) {
        String decomposed = KeyComposition.decompose(c);
R
Romain Vimont 已提交
149
        char[] chars = decomposed != null ? decomposed.toCharArray() : new char[]{c};
R
Romain Vimont 已提交
150
        KeyEvent[] events = charMap.getEvents(chars);
R
Romain Vimont 已提交
151
        if (events == null) {
R
Romain Vimont 已提交
152
            return false;
R
Romain Vimont 已提交
153 154
        }
        for (KeyEvent event : events) {
R
Romain Vimont 已提交
155
            if (!device.injectEvent(event)) {
R
Romain Vimont 已提交
156 157 158 159 160 161
                return false;
            }
        }
        return true;
    }

Y
Yu-Chen Lin 已提交
162 163
    private int injectText(String text) {
        int successCount = 0;
R
Romain Vimont 已提交
164
        for (char c : text.toCharArray()) {
R
Romain Vimont 已提交
165
            if (!injectChar(c)) {
166
                Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
Y
Yu-Chen Lin 已提交
167
                continue;
R
Romain Vimont 已提交
168
            }
Y
Yu-Chen Lin 已提交
169
            successCount++;
R
Romain Vimont 已提交
170
        }
Y
Yu-Chen Lin 已提交
171
        return successCount;
R
Romain Vimont 已提交
172 173
    }

174
    private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
R
Romain Vimont 已提交
175 176 177 178
        long now = SystemClock.uptimeMillis();

        Point point = device.getPhysicalPoint(position);
        if (point == null) {
R
Romain Vimont 已提交
179
            Ln.w("Ignore touch event, it was generated for a different device size");
R
Romain Vimont 已提交
180 181 182 183 184 185 186 187 188 189 190 191 192
            return false;
        }

        int pointerIndex = pointersState.getPointerIndex(pointerId);
        if (pointerIndex == -1) {
            Ln.w("Too many pointers for touch event");
            return false;
        }
        Pointer pointer = pointersState.get(pointerIndex);
        pointer.setPoint(point);
        pointer.setPressure(pressure);
        pointer.setUp(action == MotionEvent.ACTION_UP);

193
        int pointerCount = pointersState.update(pointerProperties, pointerCoords);
R
Romain Vimont 已提交
194 195 196 197 198 199 200 201 202 203 204 205 206 207

        if (pointerCount == 1) {
            if (action == MotionEvent.ACTION_DOWN) {
                lastTouchDown = now;
            }
        } else {
            // secondary pointers must use ACTION_POINTER_* ORed with the pointerIndex
            if (action == MotionEvent.ACTION_UP) {
                action = MotionEvent.ACTION_POINTER_UP | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
            } else if (action == MotionEvent.ACTION_DOWN) {
                action = MotionEvent.ACTION_POINTER_DOWN | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
            }
        }

208 209 210
        // Right-click and middle-click only work if the source is a mouse
        boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
        int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
211 212 213 214
        if (source != InputDevice.SOURCE_MOUSE) {
            // Buttons must not be set for touch events
            buttons = 0;
        }
215

R
Romain Vimont 已提交
216
        MotionEvent event = MotionEvent
217
                .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
218
                        0);
R
Romain Vimont 已提交
219
        return device.injectEvent(event);
R
Romain Vimont 已提交
220 221
    }

R
Romain Vimont 已提交
222
    private boolean injectScroll(Position position, int hScroll, int vScroll) {
R
Romain Vimont 已提交
223
        long now = SystemClock.uptimeMillis();
R
Romain Vimont 已提交
224
        Point point = device.getPhysicalPoint(position);
R
Romain Vimont 已提交
225
        if (point == null) {
R
Romain Vimont 已提交
226 227 228
            // ignore event
            return false;
        }
229

230
        MotionEvent.PointerProperties props = pointerProperties[0];
231 232
        props.id = 0;

233
        MotionEvent.PointerCoords coords = pointerCoords[0];
234 235 236 237 238
        coords.x = point.getX();
        coords.y = point.getY();
        coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
        coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);

R
Romain Vimont 已提交
239
        MotionEvent event = MotionEvent
240
                .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
241
                        InputDevice.SOURCE_TOUCHSCREEN, 0);
R
Romain Vimont 已提交
242
        return device.injectEvent(event);
R
Romain Vimont 已提交
243
    }
R
Romain Vimont 已提交
244

B
brunoais 已提交
245 246 247 248 249 250 251 252 253 254 255 256 257
    /**
     * Schedule a call to set power mode to off after a small delay.
     */
    private static void schedulePowerModeOff() {
        EXECUTOR.schedule(new Runnable() {
            @Override
            public void run() {
                Ln.i("Forcing screen off");
                Device.setScreenPowerMode(Device.POWER_MODE_OFF);
            }
        }, 200, TimeUnit.MILLISECONDS);
    }

258 259 260 261 262 263 264 265 266 267 268 269 270
    private boolean pressBackOrTurnScreenOn(int action) {
        if (Device.isScreenOn()) {
            return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0);
        }

        // Screen is off
        // Only press POWER on ACTION_DOWN
        if (action != KeyEvent.ACTION_DOWN) {
            // do nothing,
            return true;
        }

        if (keepPowerModeOff) {
B
brunoais 已提交
271 272
            schedulePowerModeOff();
        }
273
        return device.injectKeycode(KeyEvent.KEYCODE_POWER);
274
    }
275 276 277 278 279 280 281 282 283 284 285 286 287 288

    private boolean setClipboard(String text, boolean paste) {
        boolean ok = device.setClipboardText(text);
        if (ok) {
            Ln.i("Device clipboard set");
        }

        // On Android >= 7, also press the PASTE key if requested
        if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
            device.injectKeycode(KeyEvent.KEYCODE_PASTE);
        }

        return ok;
    }
R
Romain Vimont 已提交
289
}