mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-08-03 06:39:39 +00:00
add_debian_package_build
This commit is contained in:
parent
44a8bcb152
commit
58d0ec681d
15 changed files with 827 additions and 12 deletions
1
.pc/.quilt_patches
Normal file
1
.pc/.quilt_patches
Normal file
|
@ -0,0 +1 @@
|
||||||
|
debian/patches
|
1
.pc/.quilt_series
Normal file
1
.pc/.quilt_series
Normal file
|
@ -0,0 +1 @@
|
||||||
|
series
|
1
.pc/.version
Normal file
1
.pc/.version
Normal file
|
@ -0,0 +1 @@
|
||||||
|
2
|
0
.pc/0001-No-Android-Q.patch/.timestamp
Normal file
0
.pc/0001-No-Android-Q.patch/.timestamp
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.KeyCharacterMap;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class Controller {
|
||||||
|
|
||||||
|
private static final int DEVICE_ID_VIRTUAL = -1;
|
||||||
|
|
||||||
|
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
|
private final Device device;
|
||||||
|
private final DesktopConnection connection;
|
||||||
|
private final DeviceMessageSender sender;
|
||||||
|
|
||||||
|
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
||||||
|
|
||||||
|
private long lastTouchDown;
|
||||||
|
private final PointersState pointersState = new PointersState();
|
||||||
|
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
||||||
|
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
||||||
|
|
||||||
|
private boolean keepPowerModeOff;
|
||||||
|
|
||||||
|
public Controller(Device device, DesktopConnection connection) {
|
||||||
|
this.device = device;
|
||||||
|
this.connection = connection;
|
||||||
|
initPointers();
|
||||||
|
sender = new DeviceMessageSender(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPointers() {
|
||||||
|
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;
|
||||||
|
coords.size = 1;
|
||||||
|
|
||||||
|
pointerProperties[i] = props;
|
||||||
|
pointerCoords[i] = coords;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void control() throws IOException {
|
||||||
|
// on start, power on the device
|
||||||
|
if (!device.isScreenOn()) {
|
||||||
|
device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
|
|
||||||
|
// dirty hack
|
||||||
|
// After POWER is injected, the device is powered on asynchronously.
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
handleEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceMessageSender getSender() {
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEvent() throws IOException {
|
||||||
|
ControlMessage msg = connection.receiveControlMessage();
|
||||||
|
switch (msg.getType()) {
|
||||||
|
case ControlMessage.TYPE_INJECT_KEYCODE:
|
||||||
|
if (device.supportsInputEvents()) {
|
||||||
|
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_INJECT_TEXT:
|
||||||
|
if (device.supportsInputEvents()) {
|
||||||
|
injectText(msg.getText());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
|
||||||
|
if (device.supportsInputEvents()) {
|
||||||
|
injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
|
if (device.supportsInputEvents()) {
|
||||||
|
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
if (device.supportsInputEvents()) {
|
||||||
|
pressBackOrTurnScreenOn();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
|
device.expandNotificationPanel();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
|
device.collapsePanels();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
|
String clipboardText = device.getClipboardText();
|
||||||
|
if (clipboardText != null) {
|
||||||
|
sender.pushClipboardText(clipboardText);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
|
setClipboard(msg.getText(), msg.getPaste());
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
|
if (device.supportsInputEvents()) {
|
||||||
|
int mode = msg.getAction();
|
||||||
|
boolean setPowerModeOk = Device.setScreenPowerMode(mode);
|
||||||
|
if (setPowerModeOk) {
|
||||||
|
keepPowerModeOff = mode == Device.POWER_MODE_OFF;
|
||||||
|
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
|
device.rotateDevice();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
|
||||||
|
if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
|
||||||
|
schedulePowerModeOff();
|
||||||
|
}
|
||||||
|
return device.injectKeyEvent(action, keycode, repeat, metaState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean injectChar(char c) {
|
||||||
|
String decomposed = KeyComposition.decompose(c);
|
||||||
|
char[] chars = decomposed != null ? decomposed.toCharArray() : new char[]{c};
|
||||||
|
KeyEvent[] events = charMap.getEvents(chars);
|
||||||
|
if (events == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (KeyEvent event : events) {
|
||||||
|
if (!device.injectEvent(event)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int injectText(String text) {
|
||||||
|
int successCount = 0;
|
||||||
|
for (char c : text.toCharArray()) {
|
||||||
|
if (!injectChar(c)) {
|
||||||
|
Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
return successCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
|
Point point = device.getPhysicalPoint(position);
|
||||||
|
if (point == null) {
|
||||||
|
Ln.w("Ignore touch event, it was generated for a different device size");
|
||||||
|
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);
|
||||||
|
|
||||||
|
int pointerCount = pointersState.update(pointerProperties, pointerCoords);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent event = MotionEvent
|
||||||
|
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||||
|
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||||
|
return device.injectEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean injectScroll(Position position, int hScroll, int vScroll) {
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
Point point = device.getPhysicalPoint(position);
|
||||||
|
if (point == null) {
|
||||||
|
// ignore event
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.PointerProperties props = pointerProperties[0];
|
||||||
|
props.id = 0;
|
||||||
|
|
||||||
|
MotionEvent.PointerCoords coords = pointerCoords[0];
|
||||||
|
coords.x = point.getX();
|
||||||
|
coords.y = point.getY();
|
||||||
|
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
|
||||||
|
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||||
|
|
||||||
|
MotionEvent event = MotionEvent
|
||||||
|
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||||
|
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||||
|
return device.injectEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean pressBackOrTurnScreenOn() {
|
||||||
|
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
||||||
|
if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) {
|
||||||
|
schedulePowerModeOff();
|
||||||
|
}
|
||||||
|
return device.injectKeycode(keycode);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,276 @@
|
||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
||||||
|
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
||||||
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
|
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||||
|
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
||||||
|
import com.genymobile.scrcpy.wrappers.WindowManager;
|
||||||
|
|
||||||
|
import android.content.IOnPrimaryClipChangedListener;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.view.IRotationWatcher;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.InputEvent;
|
||||||
|
import android.view.KeyCharacterMap;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public final class Device {
|
||||||
|
|
||||||
|
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
||||||
|
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
||||||
|
|
||||||
|
public interface RotationListener {
|
||||||
|
void onRotationChanged(int rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ClipboardListener {
|
||||||
|
void onClipboardTextChanged(String text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ServiceManager serviceManager = new ServiceManager();
|
||||||
|
|
||||||
|
private ScreenInfo screenInfo;
|
||||||
|
private RotationListener rotationListener;
|
||||||
|
private ClipboardListener clipboardListener;
|
||||||
|
private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logical display identifier
|
||||||
|
*/
|
||||||
|
private final int displayId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The surface flinger layer stack associated with this logical display
|
||||||
|
*/
|
||||||
|
private final int layerStack;
|
||||||
|
|
||||||
|
private final boolean supportsInputEvents;
|
||||||
|
|
||||||
|
public Device(Options options) {
|
||||||
|
displayId = options.getDisplayId();
|
||||||
|
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||||
|
if (displayInfo == null) {
|
||||||
|
int[] displayIds = serviceManager.getDisplayManager().getDisplayIds();
|
||||||
|
throw new InvalidDisplayIdException(displayId, displayIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayInfoFlags = displayInfo.getFlags();
|
||||||
|
|
||||||
|
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
|
||||||
|
layerStack = displayInfo.getLayerStack();
|
||||||
|
|
||||||
|
serviceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||||
|
@Override
|
||||||
|
public void onRotationChanged(int rotation) {
|
||||||
|
synchronized (Device.this) {
|
||||||
|
screenInfo = screenInfo.withDeviceRotation(rotation);
|
||||||
|
|
||||||
|
// notify
|
||||||
|
if (rotationListener != null) {
|
||||||
|
rotationListener.onRotationChanged(rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, displayId);
|
||||||
|
|
||||||
|
if (options.getControl()) {
|
||||||
|
// If control is enabled, synchronize Android clipboard to the computer automatically
|
||||||
|
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||||
|
if (clipboardManager != null) {
|
||||||
|
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
||||||
|
@Override
|
||||||
|
public void dispatchPrimaryClipChanged() {
|
||||||
|
if (isSettingClipboard.get()) {
|
||||||
|
// This is a notification for the change we are currently applying, ignore it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (Device.this) {
|
||||||
|
if (clipboardListener != null) {
|
||||||
|
String text = getClipboardText();
|
||||||
|
if (text != null) {
|
||||||
|
clipboardListener.onClipboardTextChanged(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Ln.w("No clipboard manager, copy-paste between device and computer will not work");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
|
||||||
|
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
|
||||||
|
}
|
||||||
|
|
||||||
|
// main display or any display on Android >= Q
|
||||||
|
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
|
||||||
|
if (!supportsInputEvents) {
|
||||||
|
Ln.w("Input events are not supported for secondary displays before Android 10");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ScreenInfo getScreenInfo() {
|
||||||
|
return screenInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLayerStack() {
|
||||||
|
return layerStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point getPhysicalPoint(Position position) {
|
||||||
|
// it hides the field on purpose, to read it with a lock
|
||||||
|
@SuppressWarnings("checkstyle:HiddenField")
|
||||||
|
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
|
||||||
|
|
||||||
|
// ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation
|
||||||
|
Size unlockedVideoSize = screenInfo.getUnlockedVideoSize();
|
||||||
|
|
||||||
|
int reverseVideoRotation = screenInfo.getReverseVideoRotation();
|
||||||
|
// reverse the video rotation to apply the events
|
||||||
|
Position devicePosition = position.rotate(reverseVideoRotation);
|
||||||
|
|
||||||
|
Size clientVideoSize = devicePosition.getScreenSize();
|
||||||
|
if (!unlockedVideoSize.equals(clientVideoSize)) {
|
||||||
|
// The client sends a click relative to a video with wrong dimensions,
|
||||||
|
// the device may have been rotated since the event was generated, so ignore the event
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Rect contentRect = screenInfo.getContentRect();
|
||||||
|
Point point = devicePosition.getPoint();
|
||||||
|
int convertedX = contentRect.left + point.getX() * contentRect.width() / unlockedVideoSize.getWidth();
|
||||||
|
int convertedY = contentRect.top + point.getY() * contentRect.height() / unlockedVideoSize.getHeight();
|
||||||
|
return new Point(convertedX, convertedY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDeviceName() {
|
||||||
|
return Build.MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsInputEvents() {
|
||||||
|
return supportsInputEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean injectEvent(InputEvent inputEvent, int mode) {
|
||||||
|
if (!supportsInputEvents()) {
|
||||||
|
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean injectEvent(InputEvent event) {
|
||||||
|
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
||||||
|
InputDevice.SOURCE_KEYBOARD);
|
||||||
|
return injectEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean injectKeycode(int keyCode) {
|
||||||
|
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isScreenOn() {
|
||||||
|
return serviceManager.getPowerManager().isScreenOn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setRotationListener(RotationListener rotationListener) {
|
||||||
|
this.rotationListener = rotationListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setClipboardListener(ClipboardListener clipboardListener) {
|
||||||
|
this.clipboardListener = clipboardListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expandNotificationPanel() {
|
||||||
|
serviceManager.getStatusBarManager().expandNotificationsPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void collapsePanels() {
|
||||||
|
serviceManager.getStatusBarManager().collapsePanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClipboardText() {
|
||||||
|
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||||
|
if (clipboardManager == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CharSequence s = clipboardManager.getText();
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setClipboardText(String text) {
|
||||||
|
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||||
|
if (clipboardManager == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentClipboard = getClipboardText();
|
||||||
|
if (currentClipboard != null && currentClipboard.equals(text)) {
|
||||||
|
// The clipboard already contains the requested text.
|
||||||
|
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
|
||||||
|
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
|
||||||
|
// problem, do not explicitly set the clipboard text if it already contains the expected content.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSettingClipboard.set(true);
|
||||||
|
boolean ok = clipboardManager.setText(text);
|
||||||
|
isSettingClipboard.set(false);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mode one of the {@code POWER_MODE_*} constants
|
||||||
|
*/
|
||||||
|
public static boolean setScreenPowerMode(int mode) {
|
||||||
|
IBinder d = SurfaceControl.getBuiltInDisplay();
|
||||||
|
if (d == null) {
|
||||||
|
Ln.e("Could not get built-in display");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return SurfaceControl.setDisplayPowerMode(d, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
||||||
|
*/
|
||||||
|
public void rotateDevice() {
|
||||||
|
WindowManager wm = serviceManager.getWindowManager();
|
||||||
|
|
||||||
|
boolean accelerometerRotation = !wm.isRotationFrozen();
|
||||||
|
|
||||||
|
int currentRotation = wm.getRotation();
|
||||||
|
int newRotation = (currentRotation & 1) ^ 1; // 0->1, 1->0, 2->1, 3->0
|
||||||
|
String newRotationString = newRotation == 0 ? "portrait" : "landscape";
|
||||||
|
|
||||||
|
Ln.i("Device rotation requested: " + newRotationString);
|
||||||
|
wm.freezeRotation(newRotation);
|
||||||
|
|
||||||
|
// restore auto-rotate if necessary
|
||||||
|
if (accelerometerRotation) {
|
||||||
|
wm.thawRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentProvider createSettingsProvider() {
|
||||||
|
return serviceManager.getActivityManager().createSettingsProvider();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.IOnPrimaryClipChangedListener;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IInterface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class ClipboardManager {
|
||||||
|
private final IInterface manager;
|
||||||
|
private Method getPrimaryClipMethod;
|
||||||
|
private Method setPrimaryClipMethod;
|
||||||
|
private Method addPrimaryClipChangedListener;
|
||||||
|
|
||||||
|
public ClipboardManager(IInterface manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
|
||||||
|
if (getPrimaryClipMethod == null) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
||||||
|
} else {
|
||||||
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getPrimaryClipMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
|
||||||
|
if (setPrimaryClipMethod == null) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
||||||
|
} else {
|
||||||
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setPrimaryClipMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData)
|
||||||
|
throws InvocationTargetException, IllegalAccessException {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME);
|
||||||
|
} else {
|
||||||
|
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getText() {
|
||||||
|
try {
|
||||||
|
Method method = getGetPrimaryClipMethod();
|
||||||
|
ClipData clipData = getPrimaryClip(method, manager);
|
||||||
|
if (clipData == null || clipData.getItemCount() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return clipData.getItemAt(0).getText();
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not invoke method", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setText(CharSequence text) {
|
||||||
|
try {
|
||||||
|
Method method = getSetPrimaryClipMethod();
|
||||||
|
ClipData clipData = ClipData.newPlainText(null, text);
|
||||||
|
setPrimaryClip(method, manager, clipData);
|
||||||
|
return true;
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not invoke method", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener)
|
||||||
|
throws InvocationTargetException, IllegalAccessException {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME);
|
||||||
|
} else {
|
||||||
|
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
|
||||||
|
if (addPrimaryClipChangedListener == null) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
addPrimaryClipChangedListener = manager.getClass()
|
||||||
|
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
|
||||||
|
} else {
|
||||||
|
addPrimaryClipChangedListener = manager.getClass()
|
||||||
|
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addPrimaryClipChangedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
|
||||||
|
try {
|
||||||
|
Method method = getAddPrimaryClipChangedListener();
|
||||||
|
addPrimaryClipChangedListener(method, manager, listener);
|
||||||
|
return true;
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not invoke method", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
|
public final class SurfaceControl {
|
||||||
|
|
||||||
|
private static final Class<?> CLASS;
|
||||||
|
|
||||||
|
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
|
||||||
|
public static final int POWER_MODE_OFF = 0;
|
||||||
|
public static final int POWER_MODE_NORMAL = 2;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
CLASS = Class.forName("android.view.SurfaceControl");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method getBuiltInDisplayMethod;
|
||||||
|
private static Method setDisplayPowerModeMethod;
|
||||||
|
|
||||||
|
private SurfaceControl() {
|
||||||
|
// only static methods
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openTransaction() {
|
||||||
|
try {
|
||||||
|
CLASS.getMethod("openTransaction").invoke(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeTransaction() {
|
||||||
|
try {
|
||||||
|
CLASS.getMethod("closeTransaction").invoke(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) {
|
||||||
|
try {
|
||||||
|
CLASS.getMethod("setDisplayProjection", IBinder.class, int.class, Rect.class, Rect.class)
|
||||||
|
.invoke(null, displayToken, orientation, layerStackRect, displayRect);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
|
||||||
|
try {
|
||||||
|
CLASS.getMethod("setDisplayLayerStack", IBinder.class, int.class).invoke(null, displayToken, layerStack);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDisplaySurface(IBinder displayToken, Surface surface) {
|
||||||
|
try {
|
||||||
|
CLASS.getMethod("setDisplaySurface", IBinder.class, Surface.class).invoke(null, displayToken, surface);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IBinder createDisplay(String name, boolean secure) {
|
||||||
|
try {
|
||||||
|
return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException {
|
||||||
|
if (getBuiltInDisplayMethod == null) {
|
||||||
|
// the method signature has changed in Android Q
|
||||||
|
// <https://github.com/Genymobile/scrcpy/issues/586>
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
|
||||||
|
} else {
|
||||||
|
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getBuiltInDisplayMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IBinder getBuiltInDisplay() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Method method = getGetBuiltInDisplayMethod();
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
// call getBuiltInDisplay(0)
|
||||||
|
return (IBinder) method.invoke(null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// call getInternalDisplayToken()
|
||||||
|
return (IBinder) method.invoke(null);
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not invoke method", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method getSetDisplayPowerModeMethod() throws NoSuchMethodException {
|
||||||
|
if (setDisplayPowerModeMethod == null) {
|
||||||
|
setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
|
||||||
|
}
|
||||||
|
return setDisplayPowerModeMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean setDisplayPowerMode(IBinder displayToken, int mode) {
|
||||||
|
try {
|
||||||
|
Method method = getSetDisplayPowerModeMethod();
|
||||||
|
method.invoke(null, displayToken, mode);
|
||||||
|
return true;
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not invoke method", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void destroyDisplay(IBinder displayToken) {
|
||||||
|
try {
|
||||||
|
CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
.pc/applied-patches
Normal file
1
.pc/applied-patches
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0001-No-Android-Q.patch
|
2
debian/control
vendored
2
debian/control
vendored
|
@ -3,7 +3,7 @@ Section: net
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Yangfl <mmyangfl@gmail.com>
|
Maintainer: Yangfl <mmyangfl@gmail.com>
|
||||||
Build-Depends:
|
Build-Depends:
|
||||||
debhelper-compat (= 13),
|
debhelper-compat (= 13), quilt
|
||||||
Build-Depends-Arch:
|
Build-Depends-Arch:
|
||||||
meson,
|
meson,
|
||||||
libavcodec-dev,
|
libavcodec-dev,
|
||||||
|
|
3
debian/rules
vendored
3
debian/rules
vendored
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/make -f
|
#!/usr/bin/make -f
|
||||||
#export DH_VERBOSE = 1
|
#export DH_VERBOSE = 1
|
||||||
|
|
||||||
|
export QUILT_PATCHES=$(CURDIR)debian/patches
|
||||||
|
|
||||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||||
|
|
||||||
|
@ -33,8 +34,10 @@ clean:
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
dh_auto_clean
|
dh_auto_clean
|
||||||
rm -rf build_manual
|
rm -rf build_manual
|
||||||
|
quilt pop -a
|
||||||
|
|
||||||
override_dh_auto_configure-arch:
|
override_dh_auto_configure-arch:
|
||||||
|
quilt push -a
|
||||||
dh_auto_configure -a -- -Dcompile_server=false
|
dh_auto_configure -a -- -Dcompile_server=false
|
||||||
|
|
||||||
override_dh_auto_build-indep:
|
override_dh_auto_build-indep:
|
||||||
|
|
|
@ -262,8 +262,8 @@ public class Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
// On Android >= 7, also press the PASTE key if requested
|
// On Android >= 7, also press the PASTE key if requested
|
||||||
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
if (paste && Build.VERSION.SDK_INT >= 24 && device.supportsInputEvents()) {
|
||||||
device.injectKeycode(KeyEvent.KEYCODE_PASTE);
|
device.injectKeycode(279);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
|
|
|
@ -110,7 +110,7 @@ public final class Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
// main display or any display on Android >= Q
|
// main display or any display on Android >= Q
|
||||||
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
|
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= 29;
|
||||||
if (!supportsInputEvents) {
|
if (!supportsInputEvents) {
|
||||||
Ln.w("Input events are not supported for secondary displays before Android 10");
|
Ln.w("Input events are not supported for secondary displays before Android 10");
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class ClipboardManager {
|
||||||
|
|
||||||
private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
|
private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
|
||||||
if (getPrimaryClipMethod == null) {
|
if (getPrimaryClipMethod == null) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
||||||
} else {
|
} else {
|
||||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
|
||||||
|
@ -33,7 +33,7 @@ public class ClipboardManager {
|
||||||
|
|
||||||
private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
|
private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
|
||||||
if (setPrimaryClipMethod == null) {
|
if (setPrimaryClipMethod == null) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
||||||
} else {
|
} else {
|
||||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
|
||||||
|
@ -43,7 +43,7 @@ public class ClipboardManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException {
|
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME);
|
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME);
|
||||||
}
|
}
|
||||||
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
||||||
|
@ -51,7 +51,7 @@ public class ClipboardManager {
|
||||||
|
|
||||||
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData)
|
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData)
|
||||||
throws InvocationTargetException, IllegalAccessException {
|
throws InvocationTargetException, IllegalAccessException {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME);
|
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME);
|
||||||
} else {
|
} else {
|
||||||
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
||||||
|
@ -86,7 +86,7 @@ public class ClipboardManager {
|
||||||
|
|
||||||
private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener)
|
private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener)
|
||||||
throws InvocationTargetException, IllegalAccessException {
|
throws InvocationTargetException, IllegalAccessException {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME);
|
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME);
|
||||||
} else {
|
} else {
|
||||||
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
|
||||||
|
@ -95,7 +95,7 @@ public class ClipboardManager {
|
||||||
|
|
||||||
private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
|
private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
|
||||||
if (addPrimaryClipChangedListener == null) {
|
if (addPrimaryClipChangedListener == null) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
addPrimaryClipChangedListener = manager.getClass()
|
addPrimaryClipChangedListener = manager.getClass()
|
||||||
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
|
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -88,7 +88,7 @@ public final class SurfaceControl {
|
||||||
if (getBuiltInDisplayMethod == null) {
|
if (getBuiltInDisplayMethod == null) {
|
||||||
// the method signature has changed in Android Q
|
// the method signature has changed in Android Q
|
||||||
// <https://github.com/Genymobile/scrcpy/issues/586>
|
// <https://github.com/Genymobile/scrcpy/issues/586>
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
|
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
|
||||||
} else {
|
} else {
|
||||||
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
|
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
|
||||||
|
@ -101,7 +101,7 @@ public final class SurfaceControl {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Method method = getGetBuiltInDisplayMethod();
|
Method method = getGetBuiltInDisplayMethod();
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
// call getBuiltInDisplay(0)
|
// call getBuiltInDisplay(0)
|
||||||
return (IBinder) method.invoke(null, 0);
|
return (IBinder) method.invoke(null, 0);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue