Skip to content

Commit

Permalink
Replace looseSignature with WithType
Browse files Browse the repository at this point in the history
Signed-off-by: utzcoz <utzcoz@outlook.com>
  • Loading branch information
utzcoz committed Jan 27, 2024
1 parent 53dd73c commit 0ab7c1b
Show file tree
Hide file tree
Showing 28 changed files with 235 additions and 139 deletions.
Expand Up @@ -8,6 +8,7 @@
import static org.robolectric.util.reflector.Reflector.reflector;

import com.google.auto.service.AutoService;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
Expand All @@ -28,6 +29,7 @@
import org.robolectric.util.Function;
import org.robolectric.util.PerfStatsCollector;
import org.robolectric.util.Util;
import org.robolectric.util.reflector.WithType;

/**
* ShadowWrangler matches shadowed classes up with corresponding shadows based on a {@link
Expand Down Expand Up @@ -322,9 +324,16 @@ private Method findShadowMethod(
Class<?> shadowClass) {
Method method = findShadowMethodDeclaredOnClass(shadowClass, name, types);

if (method == null && shadowInfo.looseSignatures) {
Class<?>[] genericTypes = MethodType.genericMethodType(types.length).parameterArray();
method = findShadowMethodDeclaredOnClass(shadowClass, name, genericTypes);
if (method == null) {
// If user sets looseSignatures for shadow class, we will try to find method with generic
// types again.
if (shadowInfo.looseSignatures) {
Class<?>[] genericTypes = MethodType.genericMethodType(types.length).parameterArray();
method = findShadowMethodDeclaredOnClass(shadowClass, name, genericTypes);
} else {
// Otherwise, we will try to find method with ClassName annotation that can match signature.
method = findShadowMethodWithClassNameDeclaredOnClass(shadowClass, name, types);
}
}

if (method != null) {
Expand All @@ -348,6 +357,76 @@ private Method findShadowMethod(
return method;
}

private WithType findWithTypeAnnotation(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (WithType.class.isAssignableFrom(annotation.annotationType())) {
return (WithType) annotation;
}
}
return null;
}

private boolean hasWithTypeAnnotation(Annotation[][] annotations) {
for (Annotation[] parameterAnnotations : annotations) {
for (Annotation annotation : parameterAnnotations) {
if (WithType.class.isAssignableFrom(annotation.annotationType())) {
return true;
}
}
}
return false;
}

private Method findShadowMethodWithClassNameDeclaredOnClass(
Class<?> shadowClass, String methodName, Class<?>[] paramClasses) {
// We don't process the method without input parameters now.
if (paramClasses == null || paramClasses.length == 0) {
return null;
}
Method[] methods = shadowClass.getDeclaredMethods();
for (Method method : methods) {
if (method == null
|| !method.getName().equals(methodName)
|| method.getParameterCount() != paramClasses.length
|| !isValidShadowMethod(method)) {
continue;
}
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] allAnnotations = method.getParameterAnnotations();
if (!hasWithTypeAnnotation(allAnnotations)) {
continue;
}
boolean matched = true;
for (int i = 0; i < parameterTypes.length; i++) {
// If method's parameter type is superclass of input parameter, we can pass checking for
// this parameter.
if (parameterTypes[i].isAssignableFrom(paramClasses[i])) {
continue;
}
if (allAnnotations.length <= i) {
matched = false;
break;
}
WithType className = findWithTypeAnnotation(allAnnotations[i]);
try {
if (className != null
&& Class.forName(className.value()).isAssignableFrom(paramClasses[i])) {
continue;
}
} catch (ClassNotFoundException ignored) {
// Do nothing
}
matched = false;
break;
}
// TODO identify why above logic will affect __constructor__ without ClassName
if (matched) {
return method;
}
}
return null;
}

private Method findShadowMethodDeclaredOnClass(
Class<?> shadowClass, String methodName, Class<?>[] paramClasses) {
try {
Expand Down
Expand Up @@ -81,7 +81,7 @@
import org.robolectric.util.reflector.WithType;

@SuppressWarnings("NewApi")
@Implements(value = Activity.class, looseSignatures = true)
@Implements(value = Activity.class)
public class ShadowActivity extends ShadowContextThemeWrapper {

@RealObject protected Activity realActivity;
Expand Down
Expand Up @@ -42,7 +42,7 @@
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Reflector;

@Implements(value = ActivityThread.class, isInAndroidSdk = false, looseSignatures = true)
@Implements(value = ActivityThread.class, isInAndroidSdk = false)
public class ShadowActivityThread {
private static ApplicationInfo applicationInfo;
@RealObject protected ActivityThread realActivityThread;
Expand Down
Expand Up @@ -63,9 +63,10 @@
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.WithType;

/** Shadow for {@link AppOpsManager}. */
@Implements(value = AppOpsManager.class, minSdk = KITKAT, looseSignatures = true)
@Implements(value = AppOpsManager.class, minSdk = KITKAT)
public class ShadowAppOpsManager {

// OpEntry fields that the shadow doesn't currently allow the test to configure.
Expand Down Expand Up @@ -416,18 +417,18 @@ protected int noteProxyOpNoThrow(
@RequiresApi(api = S)
@Implementation(minSdk = S)
protected int noteProxyOpNoThrow(
Object op, Object attributionSource, Object message, Object ignoredSkipProxyOperation) {
Preconditions.checkArgument(op instanceof Integer);
int op,
@WithType(value = "android.content.AttributionSource") Object attributionSource,
String message,
boolean ignoredSkipProxyOperation) {
Preconditions.checkArgument(attributionSource instanceof AttributionSource);
Preconditions.checkArgument(message == null || message instanceof String);
Preconditions.checkArgument(ignoredSkipProxyOperation instanceof Boolean);
AttributionSource castedAttributionSource = (AttributionSource) attributionSource;
return noteProxyOpNoThrow(
(int) op,
op,
castedAttributionSource.getNextPackageName(),
castedAttributionSource.getNextUid(),
castedAttributionSource.getNextAttributionTag(),
(String) message);
message);
}

@Implementation
Expand Down
Expand Up @@ -28,6 +28,7 @@
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
import org.robolectric.util.reflector.WithType;

// transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_content_res_ApkAssets.cpp
Expand All @@ -37,8 +38,7 @@
value = ApkAssets.class,
minSdk = P,
shadowPicker = Picker.class,
isInAndroidSdk = false,
looseSignatures = true)
isInAndroidSdk = false)
public class ShadowArscApkAssets9 extends ShadowApkAssets {
// #define ATRACE_TAG ATRACE_TAG_RESOURCES
//
Expand Down Expand Up @@ -150,11 +150,15 @@ protected static long nativeLoad(

@Implementation(minSdk = R)
protected static Object nativeLoad(
Object format, Object javaPath, Object flags, Object assetsProvider) throws IOException {
boolean system = ((int) flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
boolean overlay = ((int) flags & PROPERTY_OVERLAY) == PROPERTY_OVERLAY;
boolean forceSharedLib = ((int) flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
return nativeLoad((String) javaPath, system, forceSharedLib, overlay);
int format,
String javaPath,
int flags,
@WithType(value = "android.content.res.loader.AssetsProvider") Object assetsProvider)
throws IOException {
boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
boolean overlay = (flags & PROPERTY_OVERLAY) == PROPERTY_OVERLAY;
boolean forceSharedLib = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
return nativeLoad(javaPath, system, forceSharedLib, overlay);
}

// static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
Expand Down
Expand Up @@ -49,9 +49,10 @@
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Constructor;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.WithType;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(value = AudioManager.class, looseSignatures = true)
@Implements(value = AudioManager.class)
public class ShadowAudioManager {
@RealObject AudioManager realAudioManager;

Expand Down Expand Up @@ -884,7 +885,8 @@ public AudioRecordingConfiguration createActiveRecordingConfiguration(
@HiddenApi
@Implementation(minSdk = P)
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
protected int registerAudioPolicy(@NonNull Object audioPolicy) {
protected int registerAudioPolicy(
@NonNull @WithType(value = "android.media.audiopolicy.AudioPolicy") Object audioPolicy) {
Preconditions.checkNotNull(audioPolicy, "Illegal null AudioPolicy argument");
AudioPolicy policy = (AudioPolicy) audioPolicy;
String id = getIdForAudioPolicy(audioPolicy);
Expand All @@ -898,7 +900,8 @@ protected int registerAudioPolicy(@NonNull Object audioPolicy) {

@HiddenApi
@Implementation(minSdk = Q)
protected void unregisterAudioPolicy(@NonNull Object audioPolicy) {
protected void unregisterAudioPolicy(
@NonNull @WithType(value = "android.media.audiopolicy.AudioPolicy") Object audioPolicy) {
Preconditions.checkNotNull(audioPolicy, "Illegal null AudioPolicy argument");
AudioPolicy policy = (AudioPolicy) audioPolicy;
registeredAudioPolicies.remove(getIdForAudioPolicy(policy));
Expand Down
Expand Up @@ -44,7 +44,7 @@
* other methods are expected run through the real class. The two {@link WriteMode} are treated the
* same.
*/
@Implements(value = AudioTrack.class, looseSignatures = true)
@Implements(value = AudioTrack.class)
public class ShadowAudioTrack {

/**
Expand Down
Expand Up @@ -58,9 +58,10 @@
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
import org.robolectric.util.reflector.WithType;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(value = BluetoothAdapter.class, looseSignatures = true)
@Implements(value = BluetoothAdapter.class)
public class ShadowBluetoothAdapter {
@RealObject private BluetoothAdapter realAdapter;

Expand Down Expand Up @@ -131,9 +132,9 @@ protected static BluetoothAdapter getDefaultAdapter() {
return reflector(BluetoothAdapterReflector.class).getDefaultAdapter();
}

/** Requires LooseSignatures because of {@link AttributionSource} parameter */
@Implementation(minSdk = VERSION_CODES.TIRAMISU)
protected static Object createAdapter(Object attributionSource) {
protected static Object createAdapter(
@WithType(value = "android.content.AttributionSource") Object attributionSource) {
IBluetoothManager service =
ReflectionHelpers.createDelegatingProxy(
IBluetoothManager.class, new BluetoothManagerDelegate());
Expand All @@ -145,7 +146,7 @@ protected static Object createAdapter(Object attributionSource) {

/** Sets whether the Le Audio is supported or not. Minimum sdk version required is TIRAMISU. */
public void setLeAudioSupported(int supported) {
isLeAudioSupported = supported;
isLeyeAudioSupported = supported;
}

@Implementation(minSdk = VERSION_CODES.TIRAMISU)
Expand Down Expand Up @@ -357,10 +358,7 @@ protected boolean setName(String name) {
return true;
}

/**
* Needs looseSignatures because in Android T the return value of this method was changed from
* bool to int.
*/
/** T return value changed from {@code int} to {@link Duration} starting in T. */
@Implementation
protected Object setScanMode(int scanMode) {
boolean result = true;
Expand Down Expand Up @@ -396,10 +394,7 @@ protected int getScanMode() {
return scanMode;
}

/**
* Needs looseSignatures because the return value changed from {@code int} to {@link Duration}
* starting in T.
*/
/** In Android T the return value of this method was changed from bool to int. */
@Implementation
protected Object getDiscoverableTimeout() {
if (RuntimeEnvironment.getApiLevel() <= S_V2) {
Expand Down
Expand Up @@ -38,12 +38,13 @@
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.WithType;

/**
* For tests, display properties may be changed and devices may be added or removed
* programmatically.
*/
@Implements(value = DisplayManager.class, minSdk = JELLY_BEAN_MR1, looseSignatures = true)
@Implements(value = DisplayManager.class, minSdk = JELLY_BEAN_MR1)
public class ShadowDisplayManager {

@RealObject private DisplayManager realDisplayManager;
Expand Down Expand Up @@ -316,14 +317,17 @@ public void setSaturationLevel(float level) {

@Implementation(minSdk = P)
@HiddenApi
protected void setBrightnessConfiguration(Object config) {
protected void setBrightnessConfiguration(
@WithType(value = "android.hardware.display.BrightnessConfiguration") Object config) {
setBrightnessConfigurationForUser(config, 0, context.getPackageName());
}

@Implementation(minSdk = P)
@HiddenApi
protected void setBrightnessConfigurationForUser(
Object config, Object userId, Object packageName) {
@WithType(value = "android.hardware.display.BrightnessConfiguration") Object config,
int userId,
String packageName) {
getShadowDisplayManagerGlobal().setBrightnessConfigurationForUser(config, userId, packageName);
}

Expand Down
Expand Up @@ -40,6 +40,7 @@ protected static long nCreateProxy(
// need to use loose signatures here to account for signature changes
@Implementation(minSdk = S)
protected static long nCreateProxy(Object translucent, Object rootRenderNode) {
// TODO Find an approach to support the same method with disconnected range
return nCreateProxy((boolean) translucent, (long) rootRenderNode);
}

Expand Down
Expand Up @@ -30,11 +30,12 @@
import org.robolectric.config.ConfigurationRegistry;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.WithType;
import org.robolectric.versioning.AndroidVersions.U;
import org.robolectric.versioning.AndroidVersions.V;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(value = Paint.class, looseSignatures = true)
@Implements(value = Paint.class)
public class ShadowPaint {

private int color;
Expand Down Expand Up @@ -534,8 +535,11 @@ protected static int nGetFontMetricsInt(
}

@Implementation(minSdk = N, maxSdk = N_MR1)
protected int nGetFontMetricsInt(Object nativePaint, Object nativeTypeface, Object fmi) {
return nGetFontMetricsInt((long) nativePaint, (FontMetricsInt) fmi);
protected int nGetFontMetricsInt(
long nativePaint,
long nativeTypeface,
@WithType(value = "android.graphics.Paint#FontMetricsInt") Object fmi) {
return nGetFontMetricsInt(nativePaint, (FontMetricsInt) fmi);
}

@Implementation(maxSdk = M)
Expand Down
Expand Up @@ -13,11 +13,11 @@
import org.robolectric.annotation.RealObject;
import org.robolectric.util.reflector.ForType;

/**
* Shadow for PhoneWindow for APIs 23+
*/
@Implements(className = "com.android.internal.policy.PhoneWindow", isInAndroidSdk = false,
minSdk = M, looseSignatures = true)
/** Shadow for PhoneWindow for APIs 23+ */
@Implements(
className = "com.android.internal.policy.PhoneWindow",
isInAndroidSdk = false,
minSdk = M)
public class ShadowPhoneWindow extends ShadowWindow {
protected @RealObject Window realWindow;
protected boolean decorFitsSystemWindows = true;
Expand Down

0 comments on commit 0ab7c1b

Please sign in to comment.