Compare commits

..

6 Commits

Author SHA1 Message Date
Pawel Jasinski
c7b1d0ea9a Force mouse source when --forward-all-clicks
Right click and middle click require the source device to be a mouse,
not a touchscreen. Therefore, the source device was changed only when a
button other than the primary button was pressed (see
adc547fa6e).

However, this led to inconsistencies between the ACTION_DOWN when a
secondary button is pressed (with a mouse as source device) and the
matching ACTION_UP when the secondary button is released (with a
touchscreen as source device, because then there is no button pressed).

To avoid the problem in all cases, force a mouse as source device when
--forward-all-clicks is set.

Concretely, for mouse events in --forward-all-clicks mode:
 - device source is set to InputDevice.SOURCE_MOUSE;
 - motion event toolType is set to MotionEvent.TOOL_TYPE_MOUSE;

Otherwise (when --forward-all-clicks is unset, or for real touch
events), finger events are injected:
 - device source is set to InputDevice.SOURCE_TOUCHSCREEN;
 - motion event toolType is set to MotionEvent.TOOL_TYPE_FINGER.

Fixes #3568 <https://github.com/Genymobile/scrcpy/issues/3568>
PR #3579 <https://github.com/Genymobile/scrcpy/pull/3579>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-12-22 11:26:26 +01:00
Romain Vimont
18082f6069 Remove continuous resizing workaround for Windows
It turns out that the workaround only worked for MacOS.

Refs #3458 <https://github.com/Genymobile/scrcpy/issues/3458>
Refs SDL/#1059 <https://github.com/libsdl-org/SDL/issues/1059>
2022-12-21 22:06:43 +01:00
Romain Vimont
8b38b11875 Add parent directory in release zipfile
This avoids to mistakenly extract all the files in the current
directory.
2022-12-21 13:31:18 +01:00
Romain Vimont
64821466a1 Use "meson setup"
This fixes the following warning:

> WARNING: Running the setup command as `meson [options]` instead of
> `meson setup [options]` is ambiguous and deprecated.
2022-12-21 13:29:27 +01:00
Romain Vimont
82cb8ab870 Adapt ClipboardManager for Android 13
A new "attributionTag" parameter has been added to the methods
getPrimaryClip(), setPrimaryClip() and addPrimaryClipChangedListener()
of IClipboard.aidl.

Refs <0e3e509b3b%5E%21/>

Fixes #3497 <https://github.com/Genymobile/scrcpy/issues/3497>
2022-12-21 13:28:22 +01:00
Romain Vimont
b51841e85d Upgrade junit to 4.13.2 2022-12-21 13:28:22 +01:00
12 changed files with 111 additions and 46 deletions

View File

@@ -260,7 +260,7 @@ set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
Then, build: Then, build:
```bash ```bash
meson x --buildtype=release --strip -Db_lto=true meson setup x --buildtype=release --strip -Db_lto=true
ninja -Cx # DO NOT RUN AS ROOT ninja -Cx # DO NOT RUN AS ROOT
``` ```
@@ -281,7 +281,7 @@ Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:
```bash ```bash
meson x --buildtype=release --strip -Db_lto=true \ meson setup x --buildtype=release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server -Dprebuilt_server=/path/to/scrcpy-server
ninja -Cx # DO NOT RUN AS ROOT ninja -Cx # DO NOT RUN AS ROOT
``` ```

View File

@@ -277,7 +277,7 @@ The server is pushed to the device by the client on startup.
To debug it, enable the server debugger during configuration: To debug it, enable the server debugger during configuration:
```bash ```bash
meson x -Dserver_debugger=true meson setup x -Dserver_debugger=true
# or, if x is already configured # or, if x is already configured
meson configure x -Dserver_debugger=true meson configure x -Dserver_debugger=true
``` ```
@@ -286,7 +286,7 @@ If your device runs Android 8 or below, set the `server_debugger_method` to
`old` in addition: `old` in addition:
```bash ```bash
meson x -Dserver_debugger=true -Dserver_debugger_method=old meson setup x -Dserver_debugger=true -Dserver_debugger_method=old
# or, if x is already configured # or, if x is already configured
meson configure x -Dserver_debugger=true -Dserver_debugger_method=old meson configure x -Dserver_debugger=true -Dserver_debugger_method=old
``` ```

View File

@@ -61,6 +61,22 @@ static const char *const copy_key_labels[] = {
"cut", "cut",
}; };
static inline const char *
get_well_known_pointer_id_name(uint64_t pointer_id) {
switch (pointer_id) {
case POINTER_ID_MOUSE:
return "mouse";
case POINTER_ID_GENERIC_FINGER:
return "finger";
case POINTER_ID_VIRTUAL_MOUSE:
return "vmouse";
case POINTER_ID_VIRTUAL_FINGER:
return "vfinger";
default:
return NULL;
}
}
static void static void
write_position(uint8_t *buf, const struct sc_position *position) { write_position(uint8_t *buf, const struct sc_position *position) {
sc_write32be(&buf[0], position->point.x); sc_write32be(&buf[0], position->point.x);
@@ -159,11 +175,12 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
int action = msg->inject_touch_event.action int action = msg->inject_touch_event.action
& AMOTION_EVENT_ACTION_MASK; & AMOTION_EVENT_ACTION_MASK;
uint64_t id = msg->inject_touch_event.pointer_id; uint64_t id = msg->inject_touch_event.pointer_id;
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) { const char *pointer_name = get_well_known_pointer_id_name(id);
if (pointer_name) {
// string pointer id // string pointer id
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32 LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
" pressure=%f buttons=%06lx", " pressure=%f buttons=%06lx",
id == POINTER_ID_MOUSE ? "mouse" : "vfinger", pointer_name,
MOTIONEVENT_ACTION_LABEL(action), MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x, msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y, msg->inject_touch_event.position.point.y,

View File

@@ -18,7 +18,11 @@
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14) #define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
#define POINTER_ID_MOUSE UINT64_C(-1) #define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2) #define POINTER_ID_GENERIC_FINGER UINT64_C(-2)
// Used for injecting an additional virtual pointer for pinch-to-zoom
#define POINTER_ID_VIRTUAL_MOUSE UINT64_C(-3)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-4)
enum sc_control_msg_type { enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE, SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,

View File

@@ -353,6 +353,7 @@ struct sc_mouse_click_event {
struct sc_position position; struct sc_position position;
enum sc_action action; enum sc_action action;
enum sc_mouse_button button; enum sc_mouse_button button;
uint64_t pointer_id;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
}; };
@@ -365,6 +366,7 @@ struct sc_mouse_scroll_event {
struct sc_mouse_motion_event { struct sc_mouse_motion_event {
struct sc_position position; struct sc_position position;
uint64_t pointer_id;
int32_t xrel; int32_t xrel;
int32_t yrel; int32_t yrel;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values uint8_t buttons_state; // bitwise-OR of sc_mouse_button values

View File

@@ -335,7 +335,9 @@ simulate_virtual_finger(struct sc_input_manager *im,
msg.inject_touch_event.action = action; msg.inject_touch_event.action = action;
msg.inject_touch_event.position.screen_size = im->screen->frame_size; msg.inject_touch_event.position.screen_size = im->screen->frame_size;
msg.inject_touch_event.position.point = point; msg.inject_touch_event.position.point = point;
msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER; msg.inject_touch_event.pointer_id =
im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE
: POINTER_ID_VIRTUAL_FINGER;
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.buttons = 0; msg.inject_touch_event.buttons = 0;
@@ -564,6 +566,8 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
event->x, event->x,
event->y), event->y),
}, },
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER,
.xrel = event->xrel, .xrel = event->xrel,
.yrel = event->yrel, .yrel = event->yrel,
.buttons_state = .buttons_state =
@@ -687,6 +691,8 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
}, },
.action = sc_action_from_sdl_mousebutton_type(event->type), .action = sc_action_from_sdl_mousebutton_type(event->type),
.button = sc_mouse_button_from_sdl(event->button), .button = sc_mouse_button_from_sdl(event->button),
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER,
.buttons_state = .buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
im->forward_all_clicks), im->forward_all_clicks),

View File

@@ -69,7 +69,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = { .inject_touch_event = {
.action = AMOTION_EVENT_ACTION_MOVE, .action = AMOTION_EVENT_ACTION_MOVE,
.pointer_id = POINTER_ID_MOUSE, .pointer_id = event->pointer_id,
.position = event->position, .position = event->position,
.pressure = 1.f, .pressure = 1.f,
.buttons = convert_mouse_buttons(event->buttons_state), .buttons = convert_mouse_buttons(event->buttons_state),
@@ -90,7 +90,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = { .inject_touch_event = {
.action = convert_mouse_action(event->action), .action = convert_mouse_action(event->action),
.pointer_id = POINTER_ID_MOUSE, .pointer_id = event->pointer_id,
.position = event->position, .position = event->position,
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f, .pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
.buttons = convert_mouse_buttons(event->buttons_state), .buttons = convert_mouse_buttons(event->buttons_state),

View File

@@ -306,13 +306,14 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
} }
#if defined(__APPLE__) || defined(__WINDOWS__) #if defined(__APPLE__)
# define CONTINUOUS_RESIZING_WORKAROUND # define CONTINUOUS_RESIZING_WORKAROUND
#endif #endif
#ifdef CONTINUOUS_RESIZING_WORKAROUND #ifdef CONTINUOUS_RESIZING_WORKAROUND
// On Windows and MacOS, resizing blocks the event loop, so resizing events are // On Windows and MacOS, resizing blocks the event loop, so resizing events are
// not triggered. As a workaround, handle them in an event handler. // not triggered. On MacOS, as a workaround, handle them in an event handler
// (it does not work for Windows unfortunately).
// //
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077> // <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178> // <https://stackoverflow.com/a/40693139/1987178>

View File

@@ -24,13 +24,13 @@ SERVER_BUILD_DIR := build-server
WIN32_BUILD_DIR := build-win32 WIN32_BUILD_DIR := build-win32
WIN64_BUILD_DIR := build-win64 WIN64_BUILD_DIR := build-win64
DIST := dist
WIN32_TARGET_DIR := scrcpy-win32
WIN64_TARGET_DIR := scrcpy-win64
VERSION := $(shell git describe --tags --always) VERSION := $(shell git describe --tags --always)
WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip DIST := dist
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)
WIN64_TARGET_DIR := scrcpy-win64-$(VERSION)
WIN32_TARGET := $(WIN32_TARGET_DIR).zip
WIN64_TARGET := $(WIN64_TARGET_DIR).zip
RELEASE_DIR := release-$(VERSION) RELEASE_DIR := release-$(VERSION)
@@ -53,13 +53,13 @@ clean:
test: test:
[ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \ [ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \
meson "$(TEST_BUILD_DIR)" -Db_sanitize=address ) meson setup "$(TEST_BUILD_DIR)" -Db_sanitize=address )
ninja -C "$(TEST_BUILD_DIR)" ninja -C "$(TEST_BUILD_DIR)"
$(GRADLE) -p server check $(GRADLE) -p server check
build-server: build-server:
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \ [ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
meson "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false ) meson setup "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false )
ninja -C "$(SERVER_BUILD_DIR)" ninja -C "$(SERVER_BUILD_DIR)"
prepare-deps-win32: prepare-deps-win32:
@@ -76,7 +76,7 @@ prepare-deps-win64:
build-win32: prepare-deps-win32 build-win32: prepare-deps-win32
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
meson "$(WIN32_BUILD_DIR)" \ meson setup "$(WIN32_BUILD_DIR)" \
--cross-file cross_win32.txt \ --cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \ --buildtype release --strip -Db_lto=true \
-Dcompile_server=false \ -Dcompile_server=false \
@@ -85,7 +85,7 @@ build-win32: prepare-deps-win32
build-win64: prepare-deps-win64 build-win64: prepare-deps-win64
[ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \ [ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
meson "$(WIN64_BUILD_DIR)" \ meson setup "$(WIN64_BUILD_DIR)" \
--cross-file cross_win64.txt \ --cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \ --buildtype release --strip -Db_lto=true \
-Dcompile_server=false \ -Dcompile_server=false \
@@ -131,9 +131,9 @@ dist-win64: build-server build-win64
cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32 zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ cd "$(DIST)"; \
zip -r "../$(WIN32_TARGET)" . zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)"
zip-win64: dist-win64 zip-win64: dist-win64
cd "$(DIST)/$(WIN64_TARGET_DIR)"; \ cd "$(DIST)"; \
zip -r "../$(WIN64_TARGET)" . zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)"

View File

@@ -19,7 +19,7 @@ android {
} }
dependencies { dependencies {
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.2'
} }
apply from: "$project.rootDir/config/android-checkstyle.gradle" apply from: "$project.rootDir/config/android-checkstyle.gradle"

View File

@@ -16,6 +16,10 @@ public class Controller {
private static final int DEFAULT_DEVICE_ID = 0; private static final int DEFAULT_DEVICE_ID = 0;
// control_msg.h values of the pointerId field in inject_touch_event message
private static final int POINTER_ID_MOUSE = -1;
private static final int POINTER_ID_VIRTUAL_MOUSE = -3;
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
private final Device device; private final Device device;
@@ -194,7 +198,19 @@ public class Controller {
pointer.setPressure(pressure); pointer.setPressure(pressure);
pointer.setUp(action == MotionEvent.ACTION_UP); pointer.setUp(action == MotionEvent.ACTION_UP);
int source;
int pointerCount = pointersState.update(pointerProperties, pointerCoords); int pointerCount = pointersState.update(pointerProperties, pointerCoords);
if (pointerId == POINTER_ID_MOUSE || pointerId == POINTER_ID_VIRTUAL_MOUSE) {
// real mouse event (forced by the client when --forward-on-click)
pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_MOUSE;
source = InputDevice.SOURCE_MOUSE;
} else {
// POINTER_ID_GENERIC_FINGER, POINTER_ID_VIRTUAL_FINGER or real touch from device
pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_FINGER;
source = InputDevice.SOURCE_TOUCHSCREEN;
// Buttons must not be set for touch events
buttons = 0;
}
if (pointerCount == 1) { if (pointerCount == 1) {
if (action == MotionEvent.ACTION_DOWN) { if (action == MotionEvent.ACTION_DOWN) {
@@ -209,14 +225,6 @@ public class Controller {
} }
} }
// 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;
if (source != InputDevice.SOURCE_MOUSE) {
// Buttons must not be set for touch events
buttons = 0;
}
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
0); 0);

View File

@@ -15,6 +15,9 @@ public class ClipboardManager {
private Method getPrimaryClipMethod; private Method getPrimaryClipMethod;
private Method setPrimaryClipMethod; private Method setPrimaryClipMethod;
private Method addPrimaryClipChangedListener; private Method addPrimaryClipChangedListener;
private boolean alternativeGetMethod;
private boolean alternativeSetMethod;
private boolean alternativeAddListenerMethod;
public ClipboardManager(IInterface manager) { public ClipboardManager(IInterface manager) {
this.manager = manager; this.manager = manager;
@@ -25,7 +28,12 @@ public class ClipboardManager {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
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); try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
} catch (NoSuchMethodException e) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class);
alternativeGetMethod = true;
}
} }
} }
return getPrimaryClipMethod; return getPrimaryClipMethod;
@@ -36,23 +44,34 @@ public class ClipboardManager {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
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); try {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
} catch (NoSuchMethodException e) {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class);
alternativeSetMethod = true;
}
} }
} }
return setPrimaryClipMethod; return setPrimaryClipMethod;
} }
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException { private static ClipData getPrimaryClip(Method method, boolean alternativeMethod, IInterface manager)
throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { 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);
} }
if (alternativeMethod) {
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID);
}
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
} }
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData) private static void setPrimaryClip(Method method, boolean alternativeMethod, IInterface manager, ClipData clipData)
throws InvocationTargetException, IllegalAccessException { throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME); method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME);
} else if (alternativeMethod) {
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID);
} else { } else {
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
} }
@@ -61,7 +80,7 @@ public class ClipboardManager {
public CharSequence getText() { public CharSequence getText() {
try { try {
Method method = getGetPrimaryClipMethod(); Method method = getGetPrimaryClipMethod();
ClipData clipData = getPrimaryClip(method, manager); ClipData clipData = getPrimaryClip(method, alternativeGetMethod, manager);
if (clipData == null || clipData.getItemCount() == 0) { if (clipData == null || clipData.getItemCount() == 0) {
return null; return null;
} }
@@ -76,7 +95,7 @@ public class ClipboardManager {
try { try {
Method method = getSetPrimaryClipMethod(); Method method = getSetPrimaryClipMethod();
ClipData clipData = ClipData.newPlainText(null, text); ClipData clipData = ClipData.newPlainText(null, text);
setPrimaryClip(method, manager, clipData); setPrimaryClip(method, alternativeSetMethod, manager, clipData);
return true; return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
@@ -84,10 +103,12 @@ public class ClipboardManager {
} }
} }
private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener) private static void addPrimaryClipChangedListener(Method method, boolean alternativeMethod, IInterface manager,
throws InvocationTargetException, IllegalAccessException { IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME); method.invoke(manager, listener, ServiceManager.PACKAGE_NAME);
} else if (alternativeMethod) {
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID);
} else { } else {
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
} }
@@ -99,8 +120,14 @@ public class ClipboardManager {
addPrimaryClipChangedListener = manager.getClass() addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class); .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
} else { } else {
addPrimaryClipChangedListener = manager.getClass() try {
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class); addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
} catch (NoSuchMethodException e) {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, int.class);
alternativeAddListenerMethod = true;
}
} }
} }
return addPrimaryClipChangedListener; return addPrimaryClipChangedListener;
@@ -109,7 +136,7 @@ public class ClipboardManager {
public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
try { try {
Method method = getAddPrimaryClipChangedListener(); Method method = getAddPrimaryClipChangedListener();
addPrimaryClipChangedListener(method, manager, listener); addPrimaryClipChangedListener(method, alternativeAddListenerMethod, manager, listener);
return true; return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);