Do not crash on control error

Some devices do not have some methods that we invoke via reflection, or
their call do not return the expected value. In that case, do not crash
the whole controller.
This commit is contained in:
Romain Vimont 2019-10-17 21:45:52 +02:00
parent 6220456def
commit bab9361948
6 changed files with 163 additions and 52 deletions

View file

@ -162,6 +162,10 @@ public final class Device {
*/ */
public void setScreenPowerMode(int mode) { public void setScreenPowerMode(int mode) {
IBinder d = SurfaceControl.getBuiltInDisplay(0); IBinder d = SurfaceControl.getBuiltInDisplay(0);
if (d == null) {
Ln.e("Could not get built-in display");
return;
}
SurfaceControl.setDisplayPowerMode(d, mode); SurfaceControl.setDisplayPowerMode(d, mode);
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
} }

View file

@ -1,5 +1,7 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.content.ClipData; import android.content.ClipData;
import android.os.IInterface; import android.os.IInterface;
@ -8,37 +10,62 @@ import java.lang.reflect.Method;
public class ClipboardManager { public class ClipboardManager {
private final IInterface manager; private final IInterface manager;
private final Method getPrimaryClipMethod; private Method getPrimaryClipMethod;
private final Method setPrimaryClipMethod; private Method setPrimaryClipMethod;
public ClipboardManager(IInterface manager) { public ClipboardManager(IInterface manager) {
this.manager = manager; this.manager = manager;
try { }
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class); private Method getGetPrimaryClipMethod() {
} catch (NoSuchMethodException e) { if (getPrimaryClipMethod == null) {
throw new AssertionError(e); try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
} }
return getPrimaryClipMethod;
}
private Method getSetPrimaryClipMethod() {
if (setPrimaryClipMethod == null) {
try {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
}
return setPrimaryClipMethod;
} }
public CharSequence getText() { public CharSequence getText() {
Method method = getGetPrimaryClipMethod();
if (method == null) {
return null;
}
try { try {
ClipData clipData = (ClipData) getPrimaryClipMethod.invoke(manager, "com.android.shell"); ClipData clipData = (ClipData) method.invoke(manager, "com.android.shell");
if (clipData == null || clipData.getItemCount() == 0) { if (clipData == null || clipData.getItemCount() == 0) {
return null; return null;
} }
return clipData.getItemAt(0).getText(); return clipData.getItemAt(0).getText();
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
return null;
} }
} }
public void setText(CharSequence text) { public void setText(CharSequence text) {
Method method = getSetPrimaryClipMethod();
if (method == null) {
return;
}
ClipData clipData = ClipData.newPlainText(null, text); ClipData clipData = ClipData.newPlainText(null, text);
try { try {
setPrimaryClipMethod.invoke(manager, clipData, "com.android.shell"); method.invoke(manager, clipData, "com.android.shell");
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
} }
} }
} }

View file

@ -1,5 +1,7 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.os.IInterface; import android.os.IInterface;
import android.view.InputEvent; import android.view.InputEvent;
@ -13,22 +15,33 @@ public final class InputManager {
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
private final IInterface manager; private final IInterface manager;
private final Method injectInputEventMethod; private Method injectInputEventMethod;
public InputManager(IInterface manager) { public InputManager(IInterface manager) {
this.manager = manager; this.manager = manager;
try { }
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
} catch (NoSuchMethodException e) { private Method getInjectInputEventMethod() {
throw new AssertionError(e); if (injectInputEventMethod == null) {
try {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
} }
return injectInputEventMethod;
} }
public boolean injectInputEvent(InputEvent inputEvent, int mode) { public boolean injectInputEvent(InputEvent inputEvent, int mode) {
Method method = getInjectInputEventMethod();
if (method == null) {
return false;
}
try { try {
return (Boolean) injectInputEventMethod.invoke(manager, inputEvent, mode); return (Boolean) method.invoke(manager, inputEvent, mode);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
return false;
} }
} }
} }

View file

@ -1,5 +1,7 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Build; import android.os.Build;
import android.os.IInterface; import android.os.IInterface;
@ -9,24 +11,35 @@ import java.lang.reflect.Method;
public final class PowerManager { public final class PowerManager {
private final IInterface manager; private final IInterface manager;
private final Method isScreenOnMethod; private Method isScreenOnMethod;
public PowerManager(IInterface manager) { public PowerManager(IInterface manager) {
this.manager = manager; this.manager = manager;
try { }
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn"; private Method getIsScreenOnMethod() {
isScreenOnMethod = manager.getClass().getMethod(methodName); if (isScreenOnMethod == null) {
} catch (NoSuchMethodException e) { try {
throw new AssertionError(e); @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
isScreenOnMethod = manager.getClass().getMethod(methodName);
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
} }
return isScreenOnMethod;
} }
public boolean isScreenOn() { public boolean isScreenOn() {
Method method = getIsScreenOnMethod();
if (method == null) {
return false;
}
try { try {
return (Boolean) isScreenOnMethod.invoke(manager); return (Boolean) method.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
return false;
} }
} }
} }

View file

@ -17,35 +17,49 @@ public class StatusBarManager {
this.manager = manager; this.manager = manager;
} }
public void expandNotificationsPanel() { private Method getExpandNotificationsPanelMethod() {
if (expandNotificationsPanelMethod == null) { if (expandNotificationsPanelMethod == null) {
try { try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.expandNotificationsPanel() is not available on this device"); Ln.e("Could not find method", e);
return;
} }
} }
try { return expandNotificationsPanelMethod;
expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke ServiceBarManager.expandNotificationsPanel()", e);
}
} }
public void collapsePanels() { private Method getCollapsePanelsMethod() {
if (collapsePanelsMethod == null) { if (collapsePanelsMethod == null) {
try { try {
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels"); collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.collapsePanels() is not available on this device"); Ln.e("Could not find method", e);
return;
} }
} }
return collapsePanelsMethod;
}
public void expandNotificationsPanel() {
Method method = getExpandNotificationsPanelMethod();
if (method == null) {
return;
}
try { try {
collapsePanelsMethod.invoke(manager); method.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke ServiceBarManager.collapsePanels()", e); Ln.e("Could not invoke " + method.getName(), e);
}
}
public void collapsePanels() {
Method method = getCollapsePanelsMethod();
if (method == null) {
return;
}
try {
method.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e);
} }
} }
} }

View file

@ -1,11 +1,16 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.view.Surface; import android.view.Surface;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressLint("PrivateApi") @SuppressLint("PrivateApi")
public final class SurfaceControl { public final class SurfaceControl {
@ -23,6 +28,9 @@ public final class SurfaceControl {
} }
} }
private static Method getBuiltInDisplayMethod;
private static Method setDisplayPowerModeMethod;
private SurfaceControl() { private SurfaceControl() {
// only static methods // only static methods
} }
@ -76,24 +84,56 @@ public final class SurfaceControl {
} }
} }
public static IBinder getBuiltInDisplay(int builtInDisplayId) { private static Method getGetBuiltInDisplayMethod() {
try { if (getBuiltInDisplayMethod == null) {
// the method signature has changed in Android Q try {
// <https://github.com/Genymobile/scrcpy/issues/586> // the method signature has changed in Android Q
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // <https://github.com/Genymobile/scrcpy/issues/586>
return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
} else {
getBuiltInDisplayMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class);
}
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
} }
return (IBinder) CLASS.getMethod("getPhysicalDisplayToken", long.class).invoke(null, builtInDisplayId); }
} catch (Exception e) { return getBuiltInDisplayMethod;
throw new AssertionError(e); }
public static IBinder getBuiltInDisplay(int builtInDisplayId) {
Method method = getGetBuiltInDisplayMethod();
if (method == null) {
return null;
}
try {
return (IBinder) method.invoke(null, builtInDisplayId);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e);
return null;
} }
} }
private static Method getSetDisplayPowerModeMethod() {
if (setDisplayPowerModeMethod == null) {
try {
setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
}
return setDisplayPowerModeMethod;
}
public static void setDisplayPowerMode(IBinder displayToken, int mode) { public static void setDisplayPowerMode(IBinder displayToken, int mode) {
Method method = getSetDisplayPowerModeMethod();
if (method == null) {
return;
}
try { try {
CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode); method.invoke(null, displayToken, mode);
} catch (Exception e) { } catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e); Ln.e("Could not invoke " + method.getName(), e);
} }
} }