blob: 14320654542f18ad2cabd23d74042152ea4a2fb0 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.support_lib_boundary.util;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
/**
* A set of utility methods used for calling across the support library boundary.
*/
// Although this is not enforced in chromium, this is a requirement enforced when this file is
// mirrored into AndroidX. See http://b/120770118 for details.
public class BoundaryInterfaceReflectionUtil {
/**
* Check if an object is an instance of {@code className}, resolving {@code className} in
* the object's own ClassLoader. This is useful when {@code obj} may have been created in a
* ClassLoader other than the current one (in which case {@code obj instanceof Foo} would fail
* but {@code instanceOfInOwnClassLoader(obj, "Foo")} may succeed).
*/
public static boolean instanceOfInOwnClassLoader(Object obj, String className) {
try {
ClassLoader loader = obj.getClass().getClassLoader();
// We intentionally set initialize = false because instanceof shouldn't trigger
// static initialization.
Class<?> clazz = Class.forName(className, false, loader);
return clazz.isInstance(obj);
} catch (ClassNotFoundException e) {
// If className is not in the ClassLoader, then this cannot be an instance.
return false;
}
}
/**
* Utility method for fetching a method from {@param delegateLoader}, with the same signature
* (package + class + method name + parameters) as a given method defined in another
* classloader.
*/
public static Method dupeMethod(Method method, ClassLoader delegateLoader)
throws ClassNotFoundException, NoSuchMethodException {
// We're converting one type to another. This is analogous to instantiating the type on the
// other side of the Boundary, so it makes sense to perform static initialization if it
// hasn't already happened (initialize = true).
Class<?> declaringClass =
Class.forName(method.getDeclaringClass().getName(), true, delegateLoader);
// We do not need to convert parameter types across ClassLoaders because we never pass
// BoundaryInterfaces in methods, but pass InvocationHandlers instead.
Class[] parameterClasses = method.getParameterTypes();
return declaringClass.getDeclaredMethod(method.getName(), parameterClasses);
}
/**
* Returns an implementation of the boundary interface named clazz, by delegating method calls
* to the {@link InvocationHandler} invocationHandler.
*
* <p>A {@code null} {@link InvocationHandler} is treated as representing a {@code null} object.
*
* @param clazz a {@link Class} object representing the desired boundary interface.
* @param invocationHandler an {@link InvocationHandler} compatible with this boundary
* interface.
*/
@Nullable
public static <T> T castToSuppLibClass(
@NonNull Class<T> clazz, @Nullable InvocationHandler invocationHandler) {
if (invocationHandler == null) return null;
return clazz.cast(
Proxy.newProxyInstance(BoundaryInterfaceReflectionUtil.class.getClassLoader(),
new Class[] {clazz}, invocationHandler));
}
/**
* Create an {@link InvocationHandler} that delegates method calls to {@code delegate}, making
* sure that the {@link Method} and parameters being passed exist in the same {@link
* ClassLoader} as {@code delegate}.
*
* <p>A {@code null} delegate is represented with a {@code null} {@link InvocationHandler}.
*
* @param delegate the object which the resulting {@link InvocationHandler} should delegate
* method calls to.
* @return an InvocationHandlerWithDelegateGetter wrapping {@code delegate}
*/
@RequiresApi(Build.VERSION_CODES.KITKAT)
@Nullable
public static InvocationHandler createInvocationHandlerFor(@Nullable final Object delegate) {
if (delegate == null) return null;
return new InvocationHandlerWithDelegateGetter(delegate);
}
/**
* Plural version of {@link #createInvocationHandlerFor(Object)}. The resulting array will be
* the same length as {@code delegates}, where the nth {@code InvocationHandler} wraps the nth
* delegate object.
*
* <p>A {@code null} array of delegates is represented with a {@code null} array of {@link
* InvocationHandler}s. Any individual {@code null} delegate is represented with a {@code null}
* {@link InvocationHandler}.
* @param delegates an array of objects to which to delegate.
* @return an array of InvocationHandlerWithDelegateGetter instances, each delegating to
* the corresponding member of {@code delegates}.
*/
@RequiresApi(Build.VERSION_CODES.KITKAT)
@Nullable
public static InvocationHandler[] createInvocationHandlersForArray(
@Nullable final Object[] delegates) {
if (delegates == null) return null;
InvocationHandler[] handlers = new InvocationHandler[delegates.length];
for (int i = 0; i < handlers.length; i++) {
handlers[i] = createInvocationHandlerFor(delegates[i]);
}
return handlers;
}
/**
* Assuming that the given InvocationHandler was created in the current classloader and is an
* InvocationHandlerWithDelegateGetter, return the object the InvocationHandler delegates its
* method calls to.
*
* <p>A {@code null} {@link InvocationHandler} is treated as wrapping a {@code null} delegate.
*
* @param invocationHandler a {@link Nullable} InvocationHandlerWithDelegateGetter.
* @return the corresponding delegate.
*/
@RequiresApi(Build.VERSION_CODES.KITKAT)
@Nullable
public static Object getDelegateFromInvocationHandler(
@Nullable InvocationHandler invocationHandler) {
if (invocationHandler == null) return null;
InvocationHandlerWithDelegateGetter objectHolder =
(InvocationHandlerWithDelegateGetter) invocationHandler;
return objectHolder.getDelegate();
}
/**
* An InvocationHandler storing the original object that method calls are delegated to.
* This allows us to pass InvocationHandlers across the support library boundary and later
* unwrap the objects used as delegates within those InvocationHandlers.
*/
@RequiresApi(Build.VERSION_CODES.KITKAT)
private static class InvocationHandlerWithDelegateGetter implements InvocationHandler {
private final Object mDelegate;
public InvocationHandlerWithDelegateGetter(@NonNull final Object delegate) {
mDelegate = delegate;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
final ClassLoader delegateLoader = mDelegate.getClass().getClassLoader();
try {
return dupeMethod(method, delegateLoader).invoke(mDelegate, objects);
} catch (InvocationTargetException e) {
// If something went wrong, ensure we throw the original exception.
throw e.getTargetException();
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Reflection failed for method " + method, e);
}
}
/**
* Gets the delegate object (which is never {@code null}).
*/
@NonNull
public Object getDelegate() {
return mDelegate;
}
}
/**
* Check if this is a debuggable build of Android. Note: we copy BuildInfo's method because we
* cannot depend on the base-layer here (this folder is mirrored into Android).
*/
private static boolean isDebuggable() {
return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE);
}
/**
* Check whether a set of features {@param features} contains a certain feature {@param
* soughtFeature}.
*/
public static boolean containsFeature(Collection<String> features, String soughtFeature) {
assert !soughtFeature.endsWith(Features.DEV_SUFFIX);
return features.contains(soughtFeature)
|| (isDebuggable() && features.contains(soughtFeature + Features.DEV_SUFFIX));
}
public static boolean containsFeature(String[] features, String soughtFeature) {
return containsFeature(Arrays.asList(features), soughtFeature);
}
}