Skip to content

Commit

Permalink
adding...
Browse files Browse the repository at this point in the history
  • Loading branch information
Roberto Viola committed Jun 24, 2024
1 parent 1d122fb commit afa1683
Show file tree
Hide file tree
Showing 6 changed files with 440 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package org.cagnulein.android_remote.wrappers;

import org.cagnulein.android_remote.FakeContext;
import org.cagnulein.android_remote.Ln;

import android.content.ClipData;
import android.content.IOnPrimaryClipChangedListener;
import android.os.Build;
import android.os.IInterface;

import java.lang.reflect.Method;

public final class ClipboardManager {
private final IInterface manager;
private Method getPrimaryClipMethod;
private Method setPrimaryClipMethod;
private Method addPrimaryClipChangedListener;
private int getMethodVersion;
private int setMethodVersion;
private int addListenerMethodVersion;

static ClipboardManager create() {
IInterface clipboard = ServiceManager.getService("clipboard", "android.content.IClipboard");
if (clipboard == null) {
// Some devices have no clipboard manager
// <https://github.com/Genymobile/scrcpy/issues/1440>
// <https://github.com/Genymobile/scrcpy/issues/1556>
return null;
}
return new ClipboardManager(clipboard);
}

private 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 {
try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
getMethodVersion = 0;
} catch (NoSuchMethodException e1) {
try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class);
getMethodVersion = 1;
} catch (NoSuchMethodException e2) {
try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
getMethodVersion = 2;
} catch (NoSuchMethodException e3) {
try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class);
getMethodVersion = 3;
} catch (NoSuchMethodException e4) {
try {
getPrimaryClipMethod = manager.getClass()
.getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class);
getMethodVersion = 4;
} catch (NoSuchMethodException e5) {
getPrimaryClipMethod = manager.getClass()
.getMethod("getPrimaryClip", String.class, String.class, String.class, String.class, int.class, int.class,
boolean.class);
getMethodVersion = 5;
}
}
}
}
}
}
}
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 {
try {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
setMethodVersion = 0;
} catch (NoSuchMethodException e1) {
try {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class);
setMethodVersion = 1;
} catch (NoSuchMethodException e2) {
try {
setPrimaryClipMethod = manager.getClass()
.getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class);
setMethodVersion = 2;
} catch (NoSuchMethodException e3) {
setPrimaryClipMethod = manager.getClass()
.getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class, boolean.class);
setMethodVersion = 3;
}
}
}
}
}
return setPrimaryClipMethod;
}

private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager) throws ReflectiveOperationException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME);
}

switch (methodVersion) {
case 0:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
case 1:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
case 2:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
case 3:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null);
case 4:
// The last boolean parameter is "userOperate"
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true);
default:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, null, null, FakeContext.ROOT_UID, 0, true);
}
}

private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData) throws ReflectiveOperationException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME);
return;
}

switch (methodVersion) {
case 0:
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
break;
case 1:
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
break;
case 2:
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
break;
default:
// The last boolean parameter is "userOperate"
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true);
}
}

public CharSequence getText() {
try {
Method method = getGetPrimaryClipMethod();
ClipData clipData = getPrimaryClip(method, getMethodVersion, manager);
if (clipData == null || clipData.getItemCount() == 0) {
return null;
}
return clipData.getItemAt(0).getText();
} catch (ReflectiveOperationException 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, setMethodVersion, manager, clipData);
return true;
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
return false;
}
}

private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener)
throws ReflectiveOperationException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
return;
}

switch (methodVersion) {
case 0:
method.invoke(manager, listener, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
break;
case 1:
method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
break;
default:
method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
break;
}
}

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 {
try {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
addListenerMethodVersion = 0;
} catch (NoSuchMethodException e1) {
try {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class,
int.class);
addListenerMethodVersion = 1;
} catch (NoSuchMethodException e2) {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class,
int.class, int.class);
addListenerMethodVersion = 2;
}
}
}
}
return addPrimaryClipChangedListener;
}

public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
try {
Method method = getAddPrimaryClipChangedListener();
addPrimaryClipChangedListener(method, addListenerMethodVersion, manager, listener);
return true;
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,87 @@
package org.cagnulein.android_remote.wrappers;

import com.genymobile.scrcpy.Command;
import com.genymobile.scrcpy.DisplayInfo;
import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.Size;

import android.annotation.SuppressLint;
import android.hardware.display.VirtualDisplay;
import android.os.IInterface;
import android.view.Display;
import android.view.Surface;

import org.cagnulein.android_remote.DisplayInfo;
import org.cagnulein.android_remote.Size;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressLint("PrivateApi,DiscouragedPrivateApi")
public final class DisplayManager {
private final IInterface manager;
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
private Method createVirtualDisplayMethod;

public DisplayManager(IInterface manager) {
static DisplayManager create() {
try {
Class<?> clazz = Class.forName("android.hardware.display.DisplayManagerGlobal");
Method getInstanceMethod = clazz.getDeclaredMethod("getInstance");
Object dmg = getInstanceMethod.invoke(null);
return new DisplayManager(dmg);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}

private DisplayManager(Object manager) {
this.manager = manager;
}

public static VirtualDisplay createVirtualDisplay(String name, int width, int height,
int displayIdToMirror, Surface surface)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
java.lang.Class<?> displayManagerClass =
java.lang.Class.forName("android.hardware.display.DisplayManager");
return (VirtualDisplay) displayManagerClass.getMethod("createVirtualDisplay",
String.class, int.class, int.class, int.class, Surface.class)
.invoke(null, name, width, height, displayIdToMirror, surface);
// public to call it from unit tests
public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) {
Pattern regex = Pattern.compile(
"^ mOverrideDisplayInfo=DisplayInfo\\{\".*?, displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, "
+ "rotation ([0-9]+).*?, layerStack ([0-9]+)",
Pattern.MULTILINE);
Matcher m = regex.matcher(dumpsysDisplayOutput);
if (!m.find()) {
return null;
}
int flags = parseDisplayFlags(m.group(1));
int width = Integer.parseInt(m.group(2));
int height = Integer.parseInt(m.group(3));
int rotation = Integer.parseInt(m.group(4));
int layerStack = Integer.parseInt(m.group(5));

return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags);
}

private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) {
try {
String dumpsysDisplayOutput = Command.execReadOutput("dumpsys", "display");
return parseDisplayInfo(dumpsysDisplayOutput, displayId);
} catch (Exception e) {
Ln.e("Could not get display info from \"dumpsys display\" output", e);
return null;
}
}

private static int parseDisplayFlags(String text) {
Pattern regex = Pattern.compile("FLAG_[A-Z_]+");
if (text == null) {
return 0;
}

int flags = 0;
Matcher m = regex.matcher(text);
while (m.find()) {
String flagString = m.group();
try {
Field filed = Display.class.getDeclaredField(flagString);
flags |= filed.getInt(null);
} catch (ReflectiveOperationException e) {
// Silently ignore, some flags reported by "dumpsys display" are @TestApi
}
}
return flags;
}

public DisplayInfo getDisplayInfo(int displayId) {
Expand All @@ -46,21 +104,24 @@ public DisplayInfo getDisplayInfo(int displayId) {
}
}

private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) {
try {
String dumpsysDisplayOutput = Command.execReadOutput("dumpsys", "display");
return parseDisplayInfo(dumpsysDisplayOutput, displayId);
} catch (Exception e) {
Ln.e("Could not get display info from \"dumpsys display\" output", e);
return null;
}
}

public int[] getDisplayIds() {
try {
return (int[]) manager.getClass().getMethod("getDisplayIds").invoke(manager);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}
}
}

private Method getCreateVirtualDisplayMethod() throws NoSuchMethodException {
if (createVirtualDisplayMethod == null) {
createVirtualDisplayMethod = android.hardware.display.DisplayManager.class
.getMethod("createVirtualDisplay", String.class, int.class, int.class, int.class, Surface.class);
}
return createVirtualDisplayMethod;
}

public VirtualDisplay createVirtualDisplay(String name, int width, int height, int displayIdToMirror, Surface surface) throws Exception {
Method method = getCreateVirtualDisplayMethod();
return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ public final class InputManager {
private final IInterface manager;
private final Method injectInputEventMethod;

static InputManager create() {
try {
Class<?> inputManagerClass = getInputManagerClass();
Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance");
Object im = getInstanceMethod.invoke(null);
return new InputManager(im);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}

public InputManager(IInterface manager) {
this.manager = manager;
try {
Expand Down
Loading

0 comments on commit afa1683

Please sign in to comment.