aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/google/android/mobly/snippet
diff options
context:
space:
mode:
authorAlexander Dorokhine <adorokhine@google.com>2017-05-02 19:34:59 -0700
committerGitHub <noreply@github.com>2017-05-02 19:34:59 -0700
commit796a97541ca681478b707c0b9e535e0d201496ac (patch)
tree9329f582913f12c356a2f694a469bff1162a95b3 /src/main/java/com/google/android/mobly/snippet
parentfec0d88ea2860ef5b595d6f04481cec963350a1d (diff)
downloadmobly-bundled-snippets-796a97541ca681478b707c0b9e535e0d201496ac.tar.gz
Simplify API required to call methods by reflection. (#45)
* Implement a cleaner way to call methods by reflection. * Port all callers to the new reflection API.
Diffstat (limited to 'src/main/java/com/google/android/mobly/snippet')
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java2
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/BluetoothAdapterSnippet.java69
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java53
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java90
4 files changed, 115 insertions, 99 deletions
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java
index 1ca96d0..4f6b0ae 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java
@@ -78,7 +78,7 @@ public class AudioSnippet implements Snippet {
* calling muteAll will throw. */
Class<?> audioSystem = Class.forName("android.media.AudioSystem");
Method getNumStreamTypes = audioSystem.getDeclaredMethod("getNumStreamTypes");
- int numStreams = (int) getNumStreamTypes.invoke(null);
+ int numStreams = (int) getNumStreamTypes.invoke(null /* instance */);
for (int i = 0; i < numStreams; i++) {
mAudioManager.setStreamVolume(i /* audio stream */, 0 /* value */, 0 /* flags */);
}
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothAdapterSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothAdapterSnippet.java
index 7a0a15e..6946216 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothAdapterSnippet.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothAdapterSnippet.java
@@ -29,7 +29,6 @@ import com.google.android.mobly.snippet.bundled.utils.JsonSerializer;
import com.google.android.mobly.snippet.bundled.utils.Utils;
import com.google.android.mobly.snippet.rpc.Rpc;
import com.google.android.mobly.snippet.rpc.RpcMinSdk;
-import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONException;
@@ -146,42 +145,24 @@ public class BluetoothAdapterSnippet implements Snippet {
throw new BluetoothAdapterSnippetException(
"Bluetooth is not enabled, cannot become discoverable.");
}
- boolean success;
- try {
- success =
- (boolean)
- mBluetoothAdapter
- .getClass()
- .getDeclaredMethod("setScanMode", int.class, int.class)
- .invoke(
- mBluetoothAdapter,
- BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
- duration);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- if (!success) {
+ if (!(boolean)
+ Utils.invokeByReflection(
+ mBluetoothAdapter,
+ "setScanMode",
+ BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
+ duration)) {
throw new BluetoothAdapterSnippetException("Failed to become discoverable.");
}
}
@Rpc(description = "Stop being discoverable in Bluetooth.")
public void btStopBeingDiscoverable() throws Throwable {
- boolean success;
- try {
- success =
- (boolean)
- mBluetoothAdapter
- .getClass()
- .getDeclaredMethod("setScanMode", int.class, int.class)
- .invoke(
- mBluetoothAdapter,
- BluetoothAdapter.SCAN_MODE_NONE,
- 0 /* duration is not used for this */);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- if (!success) {
+ if (!(boolean)
+ Utils.invokeByReflection(
+ mBluetoothAdapter,
+ "setScanMode",
+ BluetoothAdapter.SCAN_MODE_NONE,
+ 0 /* duration is not used for this */)) {
throw new BluetoothAdapterSnippetException("Failed to stop being discoverable.");
}
}
@@ -206,18 +187,7 @@ public class BluetoothAdapterSnippet implements Snippet {
@RpcMinSdk(Build.VERSION_CODES.KITKAT)
@Rpc(description = "Enable Bluetooth HCI snoop log for debugging.")
public void btEnableHciSnoopLog() throws Throwable {
- boolean success;
- try {
- success =
- (boolean)
- mBluetoothAdapter
- .getClass()
- .getDeclaredMethod("configHciSnoopLog", boolean.class)
- .invoke(mBluetoothAdapter, true);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- if (!success) {
+ if (!(boolean) Utils.invokeByReflection(mBluetoothAdapter, "configHciSnoopLog", true)) {
throw new BluetoothAdapterSnippetException("Failed to enable HCI snoop log.");
}
}
@@ -225,18 +195,7 @@ public class BluetoothAdapterSnippet implements Snippet {
@RpcMinSdk(Build.VERSION_CODES.KITKAT)
@Rpc(description = "Disable Bluetooth HCI snoop log.")
public void btDisableHciSnoopLog() throws Throwable {
- boolean success;
- try {
- success =
- (boolean)
- mBluetoothAdapter
- .getClass()
- .getDeclaredMethod("configHciSnoopLog", boolean.class)
- .invoke(mBluetoothAdapter, false);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- if (!success) {
+ if (!(boolean) Utils.invokeByReflection(mBluetoothAdapter, "configHciSnoopLog", false)) {
throw new BluetoothAdapterSnippetException("Failed to disable HCI snoop log.");
}
}
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java
index bc57ab0..f2ac9eb 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java
@@ -31,7 +31,6 @@ import com.google.android.mobly.snippet.bundled.utils.JsonSerializer;
import com.google.android.mobly.snippet.bundled.utils.Utils;
import com.google.android.mobly.snippet.rpc.Rpc;
import com.google.android.mobly.snippet.util.Log;
-import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONException;
@@ -243,15 +242,7 @@ public class WifiManagerSnippet implements Snippet {
@Rpc(description = "Check whether Wi-Fi Soft AP (hotspot) is enabled.")
public boolean wifiIsApEnabled() throws Throwable {
- try {
- return (boolean)
- mWifiManager
- .getClass()
- .getDeclaredMethod("isWifiApEnabled")
- .invoke(mWifiManager);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
+ return (boolean) Utils.invokeByReflection(mWifiManager, "isWifiApEnabled");
}
/**
@@ -270,21 +261,9 @@ public class WifiManagerSnippet implements Snippet {
// WifiConfiguration.SSID literally, unlike the WifiManager connection logic.
wifiConfiguration.SSID = JsonSerializer.trimQuotationMarks(wifiConfiguration.SSID);
}
- boolean success;
- try {
- success =
- (boolean)
- mWifiManager
- .getClass()
- .getDeclaredMethod(
- "setWifiApEnabled",
- WifiConfiguration.class,
- boolean.class)
- .invoke(mWifiManager, wifiConfiguration, true);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- if (!success) {
+ if (!(boolean)
+ Utils.invokeByReflection(
+ mWifiManager, "setWifiApEnabled", wifiConfiguration, true)) {
throw new WifiManagerSnippetException("Failed to initiate turning on Wi-Fi Soft AP.");
}
if (!Utils.waitUntil(() -> wifiIsApEnabled() == true, 60)) {
@@ -297,24 +276,12 @@ public class WifiManagerSnippet implements Snippet {
/** Disables Wi-Fi Soft AP (hotspot). */
@Rpc(description = "Disable Wi-Fi Soft AP (hotspot).")
public void wifiDisableSoftAp() throws Throwable {
- boolean success;
- try {
- success =
- (boolean)
- mWifiManager
- .getClass()
- .getDeclaredMethod(
- "setWifiApEnabled",
- WifiConfiguration.class,
- boolean.class)
- .invoke(
- mWifiManager,
- null, /* No configuration needed for disabling */
- false);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- if (!success) {
+ if (!(boolean)
+ Utils.invokeByReflection(
+ mWifiManager,
+ "setWifiApEnabled",
+ null /* No configuration needed for disabling */,
+ false)) {
throw new WifiManagerSnippetException("Failed to initiate turning off Wi-Fi Soft AP.");
}
if (!Utils.waitUntil(() -> wifiIsApEnabled() == false, 60)) {
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java
index 0ea5313..8736d85 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java
@@ -16,6 +16,11 @@
package com.google.android.mobly.snippet.bundled.utils;
+import com.google.common.primitives.Primitives;
+import com.google.common.reflect.TypeToken;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
public final class Utils {
private Utils() {}
@@ -56,4 +61,89 @@ public final class Utils {
public interface Predicate {
boolean waitCondition() throws Throwable;
}
+
+ /**
+ * Simplified API to invoke an instance method by reflection.
+ *
+ * <p>Sample usage:
+ *
+ * <pre>
+ * boolean result = (boolean) Utils.invokeByReflection(
+ * mWifiManager,
+ * "setWifiApEnabled", null /* wifiConfiguration * /, true /* enabled * /);
+ * </pre>
+ *
+ * @param instance Instance of object defining the method to call.
+ * @param methodName Name of the method to call. Can be inherited.
+ * @param args Variadic array of arguments to supply to the method. Their types will be used to
+ * locate a suitable method to call. Subtypes, primitive types, boxed types, and {@code
+ * null} arguments are properly handled.
+ * @return The return value of the method, or {@code null} if no return value.
+ * @throws NoSuchMethodException If no suitable method could be found.
+ * @throws Throwable The exception raised by the method, if any.
+ */
+ public static Object invokeByReflection(Object instance, String methodName, Object... args)
+ throws Throwable {
+ // Java doesn't know if invokeByReflection(instance, name, null) means that the array is
+ // null or that it's a non-null array containing a single null element. We mean the latter.
+ // Silly Java.
+ if (args == null) {
+ args = new Object[] {null};
+ }
+ // Can't use Class#getMethod(Class<?>...) because it expects that the passed in classes
+ // exactly match the parameters of the method, and doesn't handle superclasses.
+ Method method = null;
+ METHOD_SEARCHER:
+ for (Method candidateMethod : instance.getClass().getMethods()) {
+ // getMethods() returns only public methods, so we don't need to worry about checking
+ // whether the method is accessible.
+ if (!candidateMethod.getName().equals(methodName)) {
+ continue;
+ }
+ Class<?>[] declaredParams = candidateMethod.getParameterTypes();
+ if (declaredParams.length != args.length) {
+ continue;
+ }
+ for (int i = 0; i < declaredParams.length; i++) {
+ if (args[i] == null) {
+ // Null is assignable to anything except primitives.
+ if (declaredParams[i].isPrimitive()) {
+ continue METHOD_SEARCHER;
+ }
+ } else {
+ // Allow autoboxing during reflection by wrapping primitives.
+ Class<?> declaredClass = Primitives.wrap(declaredParams[i]);
+ Class<?> actualClass = Primitives.wrap(args[i].getClass());
+ TypeToken<?> declaredParamType = TypeToken.of(declaredClass);
+ TypeToken<?> actualParamType = TypeToken.of(actualClass);
+ if (!declaredParamType.isSupertypeOf(actualParamType)) {
+ continue METHOD_SEARCHER;
+ }
+ }
+ }
+ method = candidateMethod;
+ break;
+ }
+ if (method == null) {
+ StringBuilder methodString =
+ new StringBuilder(instance.getClass().getName())
+ .append('#')
+ .append(methodName)
+ .append('(');
+ for (int i = 0; i < args.length - 1; i++) {
+ methodString.append(args[i].getClass().getSimpleName()).append(", ");
+ }
+ if (args.length > 0) {
+ methodString.append(args[args.length - 1].getClass().getSimpleName());
+ }
+ methodString.append(')');
+ throw new NoSuchMethodException(methodString.toString());
+ }
+ try {
+ Object result = method.invoke(instance, args);
+ return result;
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
}