From 50a97b1977fd3a8881e0f69ef81894cb4d173ba3 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 21 Oct 2021 23:05:45 +0500 Subject: [PATCH] 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 --- termux-shared/build.gradle | 1 + .../shared/reflection/ReflectionUtils.java | 275 ++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 termux-shared/src/main/java/com/termux/shared/reflection/ReflectionUtils.java diff --git a/termux-shared/build.gradle b/termux-shared/build.gradle index 297e1157..4883864e 100644 --- a/termux-shared/build.gradle +++ b/termux-shared/build.gradle @@ -14,6 +14,7 @@ android { implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" implementation "io.noties.markwon:linkify:$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 // noinspection GradleDependency diff --git a/termux-shared/src/main/java/com/termux/shared/reflection/ReflectionUtils.java b/termux-shared/src/main/java/com/termux/shared/reflection/ReflectionUtils.java new file mode 100644 index 00000000..d4f2ac28 --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/reflection/ReflectionUtils.java @@ -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 FieldInvokeResult invokeField(@NonNull Class 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; + } + } + +}