public void apply() {

    try {
      for (String clazzBunch : CLASSES_TO_HOOK) {

        String[] classesToHook = clazzBunch.split(",");

        for (final String className : classesToHook) {

          Log.d(this.getClass().getName(), "LETS HOOK -" + className + "-");

          MS.hookClassLoad(
              className,
              new MS.ClassLoadHook() {

                public void classLoaded(Class<?> hookedClass) {

                  Log.d(this.getClass().getName(), "LOCATED CLASS: " + className);

                  for (final Method method : hookedClass.getDeclaredMethods()) {

                    String arguments = "";
                    StringBuilder parameters = new StringBuilder();
                    if (method.getParameterTypes().length > 0) {
                      for (Class parameterClazz : method.getParameterTypes()) {
                        parameters.append("," + parameterClazz.getName());
                      }
                      arguments = parameters.substring(1);
                    }
                    final String methodSignature =
                        className + "." + method.getName() + "(" + arguments + ")";

                    if (Modifier.isAbstract(method.getModifiers())) {
                      Log.d(this.getClass().getName(), "IGNORED METHOD: " + methodSignature);

                    } else {

                      try {

                        Log.d(this.getClass().getName(), "HOOKED METHOD: " + methodSignature);
                        MS.hookMethod(
                            hookedClass,
                            method,
                            new MS.MethodAlteration() {

                              public Object invoked(Object capturedInstance, Object... args)
                                  throws Throwable {

                                try {
                                  throw new Exception("==> STACK_TRACE_PRINT");
                                } catch (Exception e) {
                                  Log.i(this.getClass().getName(), "\n\n\n\n\n\n\n\n\n\n\n\n");
                                  Log.i(
                                      this.getClass().getName(),
                                      "STACK_TRACE_START => " + methodSignature);
                                  Log.i(this.getClass().getName(), "\n\n\n\n\n\n\n\n\n\n\n\n");
                                  e.printStackTrace();
                                  Log.i(this.getClass().getName(), "\n\n\n\n\n\n\n\n\n\n\n\n");
                                  Log.i(
                                      this.getClass().getName(),
                                      "STACK_TRACE_END => " + methodSignature);
                                  Log.i(this.getClass().getName(), "\n\n\n\n\n\n\n\n\n\n\n\n");
                                }

                                try {
                                  String arguments = "";
                                  StringBuilder stringBuilder = new StringBuilder();
                                  if (args.length > 0) {
                                    for (int i = 0; i < args.length; i++) {
                                      stringBuilder.append("," + args[i]);
                                    }
                                    arguments = stringBuilder.substring(1);
                                  }

                                  Log.i(
                                      HOOK_LOG_BREADCRUMB,
                                      "ENTER "
                                          + methodSignature
                                          + PARAMETERS_SEPARATOR
                                          + arguments);
                                  Object result = this.invoke(capturedInstance, args);
                                  Log.i(HOOK_LOG_BREADCRUMB, "EXIT " + methodSignature);
                                  return result;

                                } catch (Exception e1) {
                                  Log.d(
                                      "SubstratePlugin",
                                      "Error level 1-a caught: " + e1.getMessage());
                                }

                                // if the previous code fails, then we try to keep at least the
                                // trace
                                try {
                                  Log.i(
                                      HOOK_LOG_BREADCRUMB,
                                      "ENTER " + methodSignature + PARAMETERS_SEPARATOR);
                                  Object result = this.invoke(capturedInstance, args);
                                  Log.i(HOOK_LOG_BREADCRUMB, "EXIT " + methodSignature);
                                  return result;

                                } catch (Exception e1) {
                                  Log.d(
                                      "SubstratePlugin",
                                      "Error level 1-b caught: " + e1.getMessage());

                                  // last attempt to recovery from an error
                                  return this.invoke(capturedInstance, args);
                                }
                              }
                            });

                      } catch (Exception e2) {
                        Log.d("SubstratePlugin", "Error level 2 caught: " + e2.getMessage());
                      }
                    }
                  }
                }
              });
        }
      }

    } catch (Throwable e3) {
      Log.d("SubstratePlugin", "Error level 3 caught: " + e3.getMessage());
    }
  }
  protected void hookMethods(
      final HookerListener listener,
      final String className,
      final Map<String, Integer> methodsToHook) {

    MS.hookClassLoad(
        className,
        new MS.ClassLoadHook() {

          @SuppressWarnings({"rawtypes", "unchecked"})
          @Override
          public void classLoaded(final Class<?> classToHook) {
            Map<GenericDeclaration, String> allMethods = new HashMap<GenericDeclaration, String>();
            try {
              Class clz = Class.forName(className);
              ApplicationConfig.setPackageName(
                  new StringBuilder(clz.getPackage().getName())
                      .append(".")
                      .append(clz.getSimpleName())
                      .toString());
              for (Method method : clz.getMethods()) {
                allMethods.put(method, method.getName());
              }
            } catch (ClassNotFoundException e1) {
              e1.printStackTrace();
            }

            Iterator iterator = allMethods.entrySet().iterator();
            while (iterator.hasNext()) {
              Map.Entry entry = (Map.Entry) iterator.next();
              final Method method = (Method) entry.getKey();
              final String methodName = (String) entry.getValue();

              if (!methodsToHook.containsKey(methodName)) continue;

              Log.v(
                  Config.DEBUG_TAG,
                  new StringBuilder("start to hook ")
                      .append(className)
                      .append(".")
                      .append(methodName)
                      .toString());

              final int intrusiveLevel = methodsToHook.get(methodName);

              final MS.MethodPointer<Object, Object> old = new MethodPointer<Object, Object>();
              MS.hookMethod(
                  classToHook,
                  method,
                  new MS.MethodHook() {

                    @Override
                    public Object invoked(Object resources, Object... args) throws Throwable {

                      InterceptEvent event =
                          new InterceptEvent(
                              hookerName,
                              intrusiveLevel,
                              System.identityHashCode(resources),
                              "unknown",
                              className,
                              methodName);

                      if (args != null) {
                        for (Object arg : args) {
                          if (arg != null) {
                            StringBuilder argValue = new StringBuilder();
                            if (arg.getClass().isArray()
                                && !arg.getClass().getName().equals("[B")) {
                              argValue.append("[");
                              int len = Array.getLength(arg);
                              for (int i = 0; i < len - 1; i++) {
                                argValue.append(Array.get(arg, i)).append(",");
                              }
                              argValue.append(Array.get(arg, len - 1));
                              argValue.append("]");
                            } else {
                              argValue.append(arg.toString());
                            }

                            event.addParameter(arg.getClass().getName(), argValue.toString());
                          } else {
                            event.addParameter(null, null);
                          }
                        }
                      }

                      if (ApplicationConfig.getPackageName() != null) {
                        event.setPackageName(ApplicationConfig.getPackageName());
                      }

                      final Context appContext = ApplicationConfig.getContext();
                      if (appContext != null) {
                        doBindService(appContext);
                        Log.v(
                            Config.DEBUG_TAG,
                            new StringBuilder("hook context from****************>")
                                .append(appContext.getPackageName())
                                .toString());
                      }

                      if (listener != null) {
                        listener.before(className, method, resources, event);
                      }

                      Object result = old.invoke(resources, args);

                      if (result != null) {
                        event.setReturns(result.getClass().getName(), result.toString());
                      } else {
                        event.setReturns(null, null);
                      }
                      Log.v(Config.DEBUG_TAG, event.toJson());
                      insertEvent(event);

                      if (listener != null) {
                        listener.after(className, method, resources, event);
                      }

                      return result;
                    }
                  },
                  old);
            }
          }
        });
  }