private static List<ApkVariant> getAllMatching(
      ApplicationInfo appInfo, ClassChecker classChecker) {
    List<ApkVariant> variants = new ArrayList<ApkVariant>();

    final String forceVariant = Res.modPrefs.getString("force_variant", "");
    if (forceVariant.length() != 0) {
      Util.debug("Using forced variant: " + forceVariant);
      try {
        final Class<?> variantClazz =
            Class.forName("at.jclehner.appopsxposed.variants." + forceVariant.replace('.', '$'));
        variants.add((ApkVariant) variantClazz.newInstance());
      } catch (ClassNotFoundException e) {
        Util.log(e);
      } catch (IllegalAccessException e) {
        Util.log(e);
      } catch (InstantiationException e) {
        Util.log(e);
      }
    } else {
      for (ApkVariant variant : VARIANTS) {
        if (variant.isMatching(appInfo, classChecker)) variants.add(variant);
      }
    }

    return variants;
  }
  protected Object onCreateAppOpsHeader(Context context, int addAfterHeaderId) {
    final Header appOpsHeader = new Header();
    appOpsHeader.title = getAppOpsTitle();
    appOpsHeader.id = R.id.app_ops_settings;
    appOpsHeader.iconRes = getAppOpsHeaderIcon();
    appOpsHeader.intent = Util.createAppOpsIntent(null);

    return appOpsHeader;
  }
  private boolean isMatching(ApplicationInfo appInfo, ClassChecker classChecker) {
    boolean havePackageMatch = false;

    for (String packageName : targetPackages()) {
      if (appInfo.packageName.equals(packageName)) havePackageMatch = true;
    }

    if (!havePackageMatch) return false;

    if (manufacturer() != ANY && !Util.containsManufacturer(manufacturer())) return false;

    if (release() != ANY && !Build.VERSION.RELEASE.equals(release())) return false;

    if (buildId() != ANY && !Build.ID.equals(buildId())) return false;

    try {
      if (md5Sum() != ANY && !XposedHelpers.getMD5Sum(appInfo.sourceDir).equals(md5Sum()))
        return false;
    } catch (IOException e) {
      log("Failed to get MD5 hash of " + appInfo.sourceDir);
      debug(e);
      return false;
    }

    if (apiLevel() != 0 && Build.VERSION.SDK_INT != apiLevel()) return false;

    if (!onMatch(appInfo, classChecker)) return false;

    final String[] classes = indicatorClasses();
    if (classes == null || classes.length == 0) return true;

    debug("Checking " + classes.length + " indicator classes");

    for (String className : classes) {
      if (classChecker.exists(className)) {
        debug("  OK: " + className);
        return true;
      } else {
        debug("  N/A: " + className);
        // continue
      }
    }

    return false;
  }
 public ClassCheckerWithApk(String apkFile) {
   mClassSet = Util.getClassList(apkFile, null, true);
 }
 protected final void debug(Throwable t) {
   Util.debug(t);
 }
 protected final void log(Throwable t) {
   Util.log(t);
 }
 protected final void debug(String message) {
   Util.debug(getLogPrefix() + message);
 }
 protected final void log(String message) {
   Util.log(getLogPrefix() + message);
 }