objects) {
+ super(context, android.R.layout.simple_spinner_dropdown_item, objects);
+ this.context = context;
+ this.list = objects;
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ return getMyView(parent, position);
+ }
+
+ @Override
+ public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ return getMyView(parent, position);
+ }
+
+ private View getMyView(ViewGroup parent, int position) {
+ View row;
+ ItemHolder holder = new ItemHolder();
+
+ LayoutInflater inflater = ((AppCompatActivity) context).getLayoutInflater();
+ row = inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
+
+ holder.name = row.findViewById(android.R.id.text1);
+
+ row.setTag(holder);
+
+ holder.name.setText(list.get(position).name);
+ return row;
+ }
+
+ private static class ItemHolder {
+ TextView name;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/meowcat/edxposed/manager/util/light/Hack.java b/app/src/main/java/org/meowcat/edxposed/manager/util/light/Hack.java
new file mode 100644
index 00000000..69198517
--- /dev/null
+++ b/app/src/main/java/org/meowcat/edxposed/manager/util/light/Hack.java
@@ -0,0 +1,1203 @@
+package org.meowcat.edxposed.manager.util.light;
+
+import android.util.Log;
+
+import androidx.annotation.CheckResult;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Java reflection helper optimized for hacking non-public APIs.
+ * The core design philosophy behind is compile-time consistency enforcement.
+ *
+ * It's suggested to declare all hacks in a centralized point, typically as static fields in a class.
+ * Then call it during application initialization, thus they are verified all together in an early stage.
+ * If any assertion failed, a fall-back strategy is suggested.
+ *
+ *
https://gist.github.com/oasisfeng/75d3774ca5441372f049818de4d52605
+ *
+ * @author Oasis
+ * @see Demo
+ */
+@SuppressWarnings({"Convert2Lambda", "WeakerAccess", "unused"})
+class Hack {
+
+ public static Class> ANY_TYPE = $.class;
+ private static final HackedClass> FALLBACK = new HackedClass<>(ANY_TYPE);
+ private static AssertionFailureHandler sFailureHandler;
+
+ private Hack() {
+ }
+
+ public static HackedClass into(final @NonNull Class clazz) {
+ return new HackedClass<>(clazz);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static HackedClass into(final String class_name) {
+ try {
+ return new HackedClass(Class.forName(class_name));
+ } catch (final ClassNotFoundException e) {
+ fail(new AssertionException(e));
+ return new HackedClass(ANY_TYPE); // Use AnyType as a lazy trick to make fallback working and avoid null.
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static HackedClass onlyIf(final boolean condition, final Hacking> hacking) {
+ if (condition) return hacking.hack();
+ return (HackedClass) FALLBACK;
+ }
+
+ public static ConditionalHack onlyIf(final boolean condition) {
+ return condition ? new ConditionalHack() {
+ @Override
+ public HackedClass into(@NonNull final Class clazz) {
+ return Hack.into(clazz);
+ }
+
+ @Override
+ public HackedClass into(final String class_name) {
+ return Hack.into(class_name);
+ }
+ } : new ConditionalHack() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public HackedClass into(@NonNull final Class clazz) {
+ return (HackedClass) FALLBACK;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public HackedClass into(final String class_name) {
+ return (HackedClass) FALLBACK;
+ }
+ };
+ }
+
+ private static void fail(final AssertionException e) {
+ if (sFailureHandler != null) sFailureHandler.onAssertionFailure(e);
+ }
+
+ /**
+ * Specify a handler to deal with assertion failure, and decide whether the failure should be thrown.
+ */
+ public static AssertionFailureHandler setAssertionFailureHandler(final AssertionFailureHandler handler) {
+ final AssertionFailureHandler old = sFailureHandler;
+ sFailureHandler = handler;
+ return old;
+ }
+
+ /**
+ * Use {@link Hack#setAssertionFailureHandler(AssertionFailureHandler) } to set the global handler
+ */
+ public interface AssertionFailureHandler {
+ void onAssertionFailure(AssertionException failure);
+ }
+
+ public interface HackedField {
+ T get(C instance);
+
+ void set(C instance, T value);
+
+ HackedTargetField on(C target);
+
+ Class getType();
+
+ boolean isAbsent();
+ }
+
+ public interface HackedTargetField {
+ T get();
+
+ void set(T value);
+
+ Class getType();
+
+ boolean isAbsent();
+ }
+
+ public interface HackedInvokable {
+ @CheckResult
+ HackedInvokable throwing(Class type);
+
+ @CheckResult
+ HackedInvokable throwing(Class type1, Class type2);
+
+ @CheckResult
+ HackedInvokable throwing(Class type1, Class type2, Class type3);
+
+ @Nullable
+ HackedMethod0 withoutParams();
+
+ @Nullable
+ HackedMethod1 withParam(Class type);
+
+ @Nullable
+ HackedMethod2 withParams(Class type1, Class type2);
+
+ @Nullable
+ HackedMethod3 withParams(Class type1, Class type2, Class type3);
+
+ @Nullable
+ HackedMethod4 withParams(Class type1, Class type2, Class type3, Class type4);
+
+ @Nullable
+ HackedMethod5 withParams(Class type1, final Class type2, final Class type3, final Class type4, final Class type5);
+
+ @Nullable
+ HackedMethodN withParams(Class>... types);
+ }
+
+ public interface NonNullHackedInvokable extends HackedInvokable {
+ @CheckResult
+ NonNullHackedInvokable throwing(Class type);
+
+ @CheckResult
+ NonNullHackedInvokable throwing(Class type1, Class type2);
+
+ @CheckResult
+ NonNullHackedInvokable throwing(Class type1, Class type2, Class type3);
+
+ @NonNull
+ HackedMethod0 withoutParams();
+
+ @NonNull
+ HackedMethod1 withParam(Class type);
+
+ @NonNull
+ HackedMethod2 withParams(Class type1, Class type2);
+
+ @NonNull
+ HackedMethod3 withParams(Class type1, Class type2, Class type3);
+
+ @NonNull
+ HackedMethod4 withParams(Class type1, Class type2, Class type3, Class type4);
+
+ @NonNull
+ HackedMethod5 withParams(Class type1, final Class type2, final Class type3, final Class type4, final Class type5);
+
+ @NonNull
+ HackedMethodN withParams(Class>... types);
+ }
+
+ public interface HackedMethod extends HackedInvokable {
+ /**
+ * Optional
+ */
+ @CheckResult
+ HackedMethod returning(Class type);
+
+ /**
+ * Fallback to the given value if this field is unavailable at runtime. (Optional)
+ */
+ @CheckResult
+ NonNullHackedMethod fallbackReturning(R return_value);
+
+ @CheckResult
+ HackedMethod throwing(Class type);
+
+ @CheckResult
+ HackedMethod throwing(Class type1, Class type2);
+
+ @CheckResult
+ HackedMethod throwing(Class type1, Class type2, Class type3);
+
+ @CheckResult
+ HackedMethod throwing(Class>... types);
+ }
+
+ @SuppressWarnings("NullableProblems") // Force to NonNull
+ public interface NonNullHackedMethod extends HackedMethod, NonNullHackedInvokable {
+ /**
+ * Optional
+ */
+ @CheckResult
+ HackedMethod returning(Class type);
+
+ @CheckResult
+ NonNullHackedMethod throwing(Class type);
+
+ @CheckResult
+ NonNullHackedMethod throwing(Class type1, Class type2);
+
+ @CheckResult
+ NonNullHackedMethod throwing(Class type1, Class type2, Class type3);
+ }
+
+ interface Invokable