From afa1683e1d7940fd44f3f0efe288a58d38a50ac9 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 24 Jun 2024 11:24:05 +0200 Subject: [PATCH] adding... --- .../wrappers/ClipboardManager.java | 234 ++++++++++++++++++ .../wrappers/DisplayManager.java | 115 +++++++-- .../android_remote/wrappers/InputManager.java | 11 + .../android_remote/wrappers/PowerManager.java | 5 + .../wrappers/StatusBarManager.java | 97 ++++++++ .../wrappers/WindowManager.java | 5 + 6 files changed, 440 insertions(+), 27 deletions(-) create mode 100644 server/src/main/java/org/cagnulein/android_remote/wrappers/ClipboardManager.java create mode 100644 server/src/main/java/org/cagnulein/android_remote/wrappers/StatusBarManager.java diff --git a/server/src/main/java/org/cagnulein/android_remote/wrappers/ClipboardManager.java b/server/src/main/java/org/cagnulein/android_remote/wrappers/ClipboardManager.java new file mode 100644 index 0000000..57a6225 --- /dev/null +++ b/server/src/main/java/org/cagnulein/android_remote/wrappers/ClipboardManager.java @@ -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 + // + // + 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; + } + } +} diff --git a/server/src/main/java/org/cagnulein/android_remote/wrappers/DisplayManager.java b/server/src/main/java/org/cagnulein/android_remote/wrappers/DisplayManager.java index f8da7c5..899e77c 100644 --- a/server/src/main/java/org/cagnulein/android_remote/wrappers/DisplayManager.java +++ b/server/src/main/java/org/cagnulein/android_remote/wrappers/DisplayManager.java @@ -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) { @@ -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); + } +} \ No newline at end of file diff --git a/server/src/main/java/org/cagnulein/android_remote/wrappers/InputManager.java b/server/src/main/java/org/cagnulein/android_remote/wrappers/InputManager.java index 71d0763..1981f9a 100644 --- a/server/src/main/java/org/cagnulein/android_remote/wrappers/InputManager.java +++ b/server/src/main/java/org/cagnulein/android_remote/wrappers/InputManager.java @@ -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 { diff --git a/server/src/main/java/org/cagnulein/android_remote/wrappers/PowerManager.java b/server/src/main/java/org/cagnulein/android_remote/wrappers/PowerManager.java index a3ff5eb..e4b0db1 100644 --- a/server/src/main/java/org/cagnulein/android_remote/wrappers/PowerManager.java +++ b/server/src/main/java/org/cagnulein/android_remote/wrappers/PowerManager.java @@ -11,6 +11,11 @@ public final class PowerManager { private final IInterface manager; private final Method isScreenOnMethod; + static PowerManager create() { + IInterface manager = ServiceManager.getService("power", "android.os.IPowerManager"); + return new PowerManager(manager); + } + public PowerManager(IInterface manager) { this.manager = manager; try { diff --git a/server/src/main/java/org/cagnulein/android_remote/wrappers/StatusBarManager.java b/server/src/main/java/org/cagnulein/android_remote/wrappers/StatusBarManager.java new file mode 100644 index 0000000..7428e3d --- /dev/null +++ b/server/src/main/java/org/cagnulein/android_remote/wrappers/StatusBarManager.java @@ -0,0 +1,97 @@ +package org.cagnulein.android_remote.wrappers; + +import org.cagnulein.android_remote.Ln; + +import android.os.IInterface; + +import java.lang.reflect.Method; + +public final class StatusBarManager { + + private final IInterface manager; + private Method expandNotificationsPanelMethod; + private boolean expandNotificationPanelMethodCustomVersion; + private Method expandSettingsPanelMethod; + private boolean expandSettingsPanelMethodNewVersion = true; + private Method collapsePanelsMethod; + + static StatusBarManager create() { + IInterface manager = ServiceManager.getService("statusbar", "com.android.internal.statusbar.IStatusBarService"); + return new StatusBarManager(manager); + } + + private StatusBarManager(IInterface manager) { + this.manager = manager; + } + + private Method getExpandNotificationsPanelMethod() throws NoSuchMethodException { + if (expandNotificationsPanelMethod == null) { + try { + expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); + } catch (NoSuchMethodException e) { + // Custom version for custom vendor ROM: + expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel", int.class); + expandNotificationPanelMethodCustomVersion = true; + } + } + return expandNotificationsPanelMethod; + } + + private Method getExpandSettingsPanel() throws NoSuchMethodException { + if (expandSettingsPanelMethod == null) { + try { + // Since Android 7: https://android.googlesource.com/platform/frameworks/base.git/+/a9927325eda025504d59bb6594fee8e240d95b01%5E%21/ + expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel", String.class); + } catch (NoSuchMethodException e) { + // old version + expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel"); + expandSettingsPanelMethodNewVersion = false; + } + } + return expandSettingsPanelMethod; + } + + private Method getCollapsePanelsMethod() throws NoSuchMethodException { + if (collapsePanelsMethod == null) { + collapsePanelsMethod = manager.getClass().getMethod("collapsePanels"); + } + return collapsePanelsMethod; + } + + public void expandNotificationsPanel() { + try { + Method method = getExpandNotificationsPanelMethod(); + if (expandNotificationPanelMethodCustomVersion) { + method.invoke(manager, 0); + } else { + method.invoke(manager); + } + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } + + public void expandSettingsPanel() { + try { + Method method = getExpandSettingsPanel(); + if (expandSettingsPanelMethodNewVersion) { + // new version + method.invoke(manager, (Object) null); + } else { + // old version + method.invoke(manager); + } + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } + + public void collapsePanels() { + try { + Method method = getCollapsePanelsMethod(); + method.invoke(manager); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } +} diff --git a/server/src/main/java/org/cagnulein/android_remote/wrappers/WindowManager.java b/server/src/main/java/org/cagnulein/android_remote/wrappers/WindowManager.java index d8e1e9a..64f07a9 100644 --- a/server/src/main/java/org/cagnulein/android_remote/wrappers/WindowManager.java +++ b/server/src/main/java/org/cagnulein/android_remote/wrappers/WindowManager.java @@ -10,6 +10,11 @@ public WindowManager(IInterface manager) { this.manager = manager; } + static WindowManager create() { + IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager"); + return new WindowManager(manager); + } + public int getRotation() { try { Class cls = manager.getClass();