From 57d5b24b2ec8e3e8c87a2345ec58202ffa956eee Mon Sep 17 00:00:00 2001 From: utzcoz Date: Fri, 16 Feb 2024 12:06:06 +0800 Subject: [PATCH] Support customized implementation method name 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 --- .../annotation/Implementation.java | 14 ++++++++++ .../processing/validator/SdkStore.java | 9 +++++- .../internal/bytecode/ShadowWrangler.java | 25 ++++++++++++++++- .../shadows/ShadowBluetoothAdapter.java | 28 +++++++++---------- 4 files changed, 60 insertions(+), 16 deletions(-) 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)