Skip to content

Commit

Permalink
Support customized implementation method name
Browse files Browse the repository at this point in the history
1. Expand Implementation with a new method called methodName for mapped
   method name.
2. Expand SdkStore to consider mapped shadow method for validation.
3. Expand shadow method finding logic when running to add mapped shadow
   method finding logic as the third priority after looseSignature.
4. Split `ShadowBluetoothAdapter#setScanMode` into two methods for
   different SDKs to test mapped shadow method.

Signed-off-by: utzcoz <utzcoz@outlook.com>
  • Loading branch information
utzcoz committed Mar 31, 2024
1 parent ab2d2cf commit 57d5b24
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 16 deletions.
Expand Up @@ -21,4 +21,18 @@

/** The annotated shadow method will be invoked only for the specified SDK or lesser. */
int maxSdk() default DEFAULT_SDK;

/**
* The implemented method name.
*
* <p>Sometimes internal methods return different types for different SDKs. It's safe because
* these methods are internal/private methods, not public methods. To support different return
* types of a method for different SDKs, we often use looseSignature method, although all return
* types are common types like bool and int. This field/property can be used to fix this issue by
* using different real methods for different SDKs.
*
* @return The expected implemented method name. If it is empty/null, the Robolectric will uses
* the method's name that marked by @Implementation as the implemented method name.
*/
String methodName() default "";
}
Expand Up @@ -384,7 +384,14 @@ private static String cleanMethodName(ExecutableElement methodElement) {
} else if (STATIC_INITIALIZER_METHOD_NAME.equals(name)) {
return "<clinit>";
} else {
return name;
Implementation implementation = methodElement.getAnnotation(Implementation.class);
String methodName = implementation == null ? "" : implementation.methodName();
methodName = methodName == null ? "" : methodName.trim();
if (methodName.isEmpty()) {
return name;
} else {
return methodName;
}
}
}

Expand Down
Expand Up @@ -22,6 +22,7 @@
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Priority;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.ReflectorObject;
import org.robolectric.sandbox.ShadowMatcher;
Expand Down Expand Up @@ -308,6 +309,7 @@ private Method findShadowMethod(
Class<?> shadowClass) {
Method method =
findShadowMethodDeclaredOnClass(shadowClass, name, types, shadowInfo.looseSignatures);

if (method != null) {
return method;
} else {
Expand All @@ -332,7 +334,9 @@ private Method findShadowMethod(
private Method findShadowMethodDeclaredOnClass(
Class<?> shadowClass, String methodName, Class<?>[] paramClasses, boolean looseSignatures) {
Method foundMethod = null;
for (Method method : shadowClass.getDeclaredMethods()) {
// Try to find shadow method with exact method name and looseSignature.
Method[] methods = shadowClass.getDeclaredMethods();
for (Method method : methods) {
if (!method.getName().equals(methodName)
|| method.getParameterCount() != paramClasses.length) {
continue;
Expand All @@ -349,6 +353,7 @@ private Method findShadowMethodDeclaredOnClass(
foundMethod = method;
break;
}

if (looseSignatures) {
boolean allParameterTypesAreObject = true;
for (Class<?> paramClass : method.getParameterTypes()) {
Expand All @@ -364,6 +369,24 @@ private Method findShadowMethodDeclaredOnClass(
}
}

if (foundMethod == null) {
// Try to find shadow method with Implementation#methodName's mapping name
for (Method method : methods) {
Implementation implementation = method.getAnnotation(Implementation.class);
if (implementation == null) {
continue;
}
String mappedMethodName = implementation.methodName().trim();
if (mappedMethodName.isEmpty() || !mappedMethodName.equals(methodName)) {
continue;
}
if (Arrays.equals(method.getParameterTypes(), paramClasses)) {
foundMethod = method;
break;
}
}
}

if (foundMethod != null) {
foundMethod.setAccessible(true);
return foundMethod;
Expand Down
Expand Up @@ -381,21 +381,21 @@ protected boolean setName(String name) {
* Needs looseSignatures because in Android T the return value of this method was changed from
* bool to int.
*/
@Implementation
protected Object setScanMode(int scanMode) {
boolean result = true;
if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE
&& scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
&& scanMode != BluetoothAdapter.SCAN_MODE_NONE) {
result = false;
}

@Implementation(maxSdk = S_V2)
protected boolean setScanMode(int scanMode) {
boolean result =
scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
|| scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
|| scanMode == BluetoothAdapter.SCAN_MODE_NONE;
this.scanMode = scanMode;
if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.TIRAMISU) {
return result ? BluetoothStatusCodes.SUCCESS : BluetoothStatusCodes.ERROR_UNKNOWN;
} else {
return result;
}
return result;
}

@Implementation(minSdk = TIRAMISU, methodName = "setScanMode")
protected int setScanModeFromT(int scanMode) {
return setScanMode(scanMode)
? BluetoothStatusCodes.SUCCESS
: BluetoothStatusCodes.ERROR_UNKNOWN;
}

@Implementation(maxSdk = Q)
Expand Down

0 comments on commit 57d5b24

Please sign in to comment.