diff --git a/annotations/src/main/java/org/robolectric/annotation/Implementation.java b/annotations/src/main/java/org/robolectric/annotation/Implementation.java index a041c273cbc..b90a27fb825 100644 --- a/annotations/src/main/java/org/robolectric/annotation/Implementation.java +++ b/annotations/src/main/java/org/robolectric/annotation/Implementation.java @@ -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. + * + *

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 ""; } diff --git a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java index e08b3acd1b7..c7a845c9e97 100644 --- a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java +++ b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java @@ -384,7 +384,14 @@ private static String cleanMethodName(ExecutableElement methodElement) { } else if (STATIC_INITIALIZER_METHOD_NAME.equals(name)) { return ""; } 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; + } } } diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java index ab480d69a0e..0f18122e5e9 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java @@ -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; @@ -308,6 +309,7 @@ private Method findShadowMethod( Class shadowClass) { Method method = findShadowMethodDeclaredOnClass(shadowClass, name, types, shadowInfo.looseSignatures); + if (method != null) { return method; } else { @@ -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; @@ -349,6 +353,7 @@ private Method findShadowMethodDeclaredOnClass( foundMethod = method; break; } + if (looseSignatures) { boolean allParameterTypesAreObject = true; for (Class paramClass : method.getParameterTypes()) { @@ -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; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java index bff4fe2b772..95846c41483 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java @@ -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)