mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-06 10:45:23 +08:00
Added: Add ReflectionUtils and add dependency for org.lsposed.hiddenapibypass:hiddenapibypass
The call to bypassHiddenAPIReflectionRestrictions() must be made before trying to reflect hidden or non-sdk APIs. Reflection will be used for accessing hidden (@hide) APIs by Termux and its plugins later. https://github.com/LSPosed/AndroidHiddenApiBypass https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces
This commit is contained in:
@@ -14,6 +14,7 @@ android {
|
|||||||
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
||||||
implementation "io.noties.markwon:linkify:$markwonVersion"
|
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||||
implementation "io.noties.markwon:recycler:$markwonVersion"
|
implementation "io.noties.markwon:recycler:$markwonVersion"
|
||||||
|
implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:2.0'
|
||||||
|
|
||||||
// Do not increment version higher than 1.0.0-alpha09 since it will break ViewUtils and needs to be looked into
|
// Do not increment version higher than 1.0.0-alpha09 since it will break ViewUtils and needs to be looked into
|
||||||
// noinspection GradleDependency
|
// noinspection GradleDependency
|
||||||
|
@@ -0,0 +1,275 @@
|
|||||||
|
package com.termux.shared.reflection;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.termux.shared.logger.Logger;
|
||||||
|
|
||||||
|
import org.lsposed.hiddenapibypass.HiddenApiBypass;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ReflectionUtils {
|
||||||
|
|
||||||
|
private static boolean HIDDEN_API_REFLECTION_RESTRICTIONS_BYPASSED = Build.VERSION.SDK_INT < Build.VERSION_CODES.P;
|
||||||
|
|
||||||
|
private static final String LOG_TAG = "ReflectionUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bypass android hidden API reflection restrictions.
|
||||||
|
* https://github.com/LSPosed/AndroidHiddenApiBypass
|
||||||
|
* https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces
|
||||||
|
*/
|
||||||
|
public static void bypassHiddenAPIReflectionRestrictions() {
|
||||||
|
if (!HIDDEN_API_REFLECTION_RESTRICTIONS_BYPASSED && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
Logger.logDebug(LOG_TAG, "Bypassing android hidden api reflection restrictions");
|
||||||
|
HiddenApiBypass.addHiddenApiExemptions("");
|
||||||
|
HIDDEN_API_REFLECTION_RESTRICTIONS_BYPASSED = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if android hidden API reflection restrictions are bypassed. */
|
||||||
|
public static boolean areHiddenAPIReflectionRestrictionsBypassed() {
|
||||||
|
return HIDDEN_API_REFLECTION_RESTRICTIONS_BYPASSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link Field} for the specified class.
|
||||||
|
*
|
||||||
|
* @param clazz The {@link Class} for which to return the field.
|
||||||
|
* @param fieldName The name of the {@link Field}.
|
||||||
|
* @return Returns the {@link Field} if getting the it was successful, otherwise {@code null}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Field getDeclaredField(@NonNull Class<?> clazz, @NonNull String fieldName) {
|
||||||
|
try {
|
||||||
|
Field field = clazz.getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return field;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get \"" + fieldName + "\" field for \"" + clazz.getName() + "\" class", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Class that represents result of invoking a field. */
|
||||||
|
public static class FieldInvokeResult {
|
||||||
|
public boolean success;
|
||||||
|
public Object value;
|
||||||
|
|
||||||
|
FieldInvokeResult(boolean success, Object value) {
|
||||||
|
this.value = success;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value for a {@link Field} of an object for the specified class.
|
||||||
|
*
|
||||||
|
* @param clazz The {@link Class} to which the object belongs to.
|
||||||
|
* @param fieldName The name of the {@link Field}.
|
||||||
|
* @param object The {@link Object} instance from which to get the field value.
|
||||||
|
* @return Returns the {@link FieldInvokeResult} of invoking the field. The
|
||||||
|
* {@link FieldInvokeResult#success} will be {@code true} if invoking the field was successful,
|
||||||
|
* otherwise {@code false}. The {@link FieldInvokeResult#value} will contain the field
|
||||||
|
* {@link Object} value.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static <T> FieldInvokeResult invokeField(@NonNull Class<T> clazz, @NonNull String fieldName, T object) {
|
||||||
|
try {
|
||||||
|
Field field = getDeclaredField(clazz, fieldName);
|
||||||
|
if (field == null) return new FieldInvokeResult(false, null);
|
||||||
|
return new FieldInvokeResult(true, field.get(object));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get \"" + fieldName + "\" field value for \"" + clazz.getName() + "\" class", e);
|
||||||
|
return new FieldInvokeResult(false, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for {@link #getDeclaredMethod(Class, String, Class[])} without parameters.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Method getDeclaredMethod(@NonNull Class<?> clazz, @NonNull String methodName) {
|
||||||
|
return getDeclaredMethod(clazz, methodName, new Class<?>[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link Method} for the specified class with the specified parameters.
|
||||||
|
*
|
||||||
|
* @param clazz The {@link Class} for which to return the method.
|
||||||
|
* @param methodName The name of the {@link Method}.
|
||||||
|
* @param parameterTypes The parameter types of the method.
|
||||||
|
* @return Returns the {@link Method} if getting the it was successful, otherwise {@code null}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Method getDeclaredMethod(@NonNull Class<?> clazz, @NonNull String methodName, Class<?>... parameterTypes) {
|
||||||
|
try {
|
||||||
|
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get \"" + methodName + "\" method for \"" + clazz.getName() + "\" class with parameter types: " + Arrays.toString(parameterTypes), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for {@link #invokeVoidMethod(Method, Object, Object...)} without arguments.
|
||||||
|
*/
|
||||||
|
public static boolean invokeVoidMethod(@NonNull Method method, Object obj) {
|
||||||
|
return invokeVoidMethod(method, obj, new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a {@link Method} on the specified object with the specified arguments that returns
|
||||||
|
* {@code void}.
|
||||||
|
*
|
||||||
|
* @param method The {@link Method} to invoke.
|
||||||
|
* @param obj The {@link Object} the method should be invoked from.
|
||||||
|
* @param args The arguments to pass to the method.
|
||||||
|
* @return Returns {@code true} if invoking the method was successful, otherwise {@code false}.
|
||||||
|
*/
|
||||||
|
public static boolean invokeVoidMethod(@NonNull Method method, Object obj, Object... args) {
|
||||||
|
try {
|
||||||
|
method.setAccessible(true);
|
||||||
|
method.invoke(obj, args);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to invoke \"" + method.getName() + "\" method with object \"" + obj + "\" and args: " + Arrays.toString(args), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Class that represents result of invoking a method that has a non-void return type. */
|
||||||
|
public static class MethodInvokeResult {
|
||||||
|
public boolean success;
|
||||||
|
public Object value;
|
||||||
|
|
||||||
|
MethodInvokeResult(boolean success, Object value) {
|
||||||
|
this.value = success;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for {@link #invokeMethod(Method, Object, Object...)} without arguments.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static MethodInvokeResult invokeMethod(@NonNull Method method, Object obj) {
|
||||||
|
return invokeMethod(method, obj, new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a {@link Method} on the specified object with the specified arguments.
|
||||||
|
*
|
||||||
|
* @param method The {@link Method} to invoke.
|
||||||
|
* @param obj The {@link Object} the method should be invoked from.
|
||||||
|
* @param args The arguments to pass to the method.
|
||||||
|
* @return Returns the {@link MethodInvokeResult} of invoking the method. The
|
||||||
|
* {@link MethodInvokeResult#success} will be {@code true} if invoking the method was successful,
|
||||||
|
* otherwise {@code false}. The {@link MethodInvokeResult#value} will contain the {@link Object}
|
||||||
|
* returned by the method.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static MethodInvokeResult invokeMethod(@NonNull Method method, Object obj, Object... args) {
|
||||||
|
try {
|
||||||
|
method.setAccessible(true);
|
||||||
|
return new MethodInvokeResult(true, method.invoke(obj, args));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to invoke \"" + method.getName() + "\" method with object \"" + obj + "\" and args: " + Arrays.toString(args), e);
|
||||||
|
return new MethodInvokeResult(false, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for {@link #getConstructor(String, Class[])} without parameters.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Constructor<?> getConstructor(@NonNull String className) {
|
||||||
|
return getConstructor(className, new Class<?>[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for {@link #getConstructor(Class, Class[])} to get a {@link Constructor} for the
|
||||||
|
* {@code className}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Constructor<?> getConstructor(@NonNull String className, Class<?>... parameterTypes) {
|
||||||
|
try {
|
||||||
|
return getConstructor(Class.forName(className), parameterTypes);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get constructor for \"" + className + "\" class with parameter types: " + Arrays.toString(parameterTypes), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link Constructor} for the specified class with the specified parameters.
|
||||||
|
*
|
||||||
|
* @param clazz The {@link Class} for which to return the constructor.
|
||||||
|
* @param parameterTypes The parameter types of the constructor.
|
||||||
|
* @return Returns the {@link Constructor} if getting the it was successful, otherwise {@code null}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Constructor<?> getConstructor(@NonNull Class<?> clazz, Class<?>... parameterTypes) {
|
||||||
|
try {
|
||||||
|
Constructor<?> constructor = clazz.getConstructor(parameterTypes);
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return constructor;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get constructor for \"" + clazz.getName() + "\" class with parameter types: " + Arrays.toString(parameterTypes), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for {@link #invokeConstructor(Constructor, Object...)} without arguments.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Object invokeConstructor(@NonNull Constructor<?> constructor) {
|
||||||
|
return invokeConstructor(constructor, new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a {@link Constructor} with the specified arguments.
|
||||||
|
*
|
||||||
|
* @param constructor The {@link Constructor} to invoke.
|
||||||
|
* @param args The arguments to pass to the constructor.
|
||||||
|
* @return Returns the new instance if invoking the constructor was successful, otherwise {@code null}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Object invokeConstructor(@NonNull Constructor<?> constructor, Object... args) {
|
||||||
|
try {
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return constructor.newInstance(args);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to invoke \"" + constructor.getName() + "\" constructor with args: " + Arrays.toString(args), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user