From 8fdc3fbf2502036915675e4d457e344b5960f8f3 Mon Sep 17 00:00:00 2001 From: Illhm <194127535+Illhm@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:40:38 +0000 Subject: [PATCH 1/4] feat: Modernize build and stabilize hook logic - Added GitHub Actions workflow `android.yml` to automatically build the debug APK using JDK 8. - Safely cleaned the build system by replacing `jcenter()` with `mavenCentral()` and configuring `jitpack.io` / `api.xposed.info`. - Stabilized `Main` hook logic to avoid duplicate installations, safely reload `XSharedPreferences`, and verify null values. - Retained crucial documentation regarding the sensitivity of the `QUERY_ALL_PACKAGES` permission required for Android 11+. - Improved `ViewBlocking` to prevent parent views from collapsing incorrectly by adding detached view checks and child count conditions. - Upgraded `NameBlocking` to safely traverse superclasses (with depth boundaries) and correctly cache results to improve overall system performance without hitting core Android UI components. - Modernized `UrlFiltering` by utilizing safe normalized host and path matching over raw URI logic, preventing random crashes and encoding errors on string `WebView` operations. --- .github/workflows/android.yml | 39 ++++++ app/build.gradle | 21 +-- .../tw/fatminmin/xposed/minminguard/Main.java | 131 ++++++++++-------- .../minminguard/blocker/NameBlocking.java | 68 +++++---- .../minminguard/blocker/UrlFiltering.java | 78 ++++++----- .../minminguard/blocker/ViewBlocking.java | 53 ++++--- build.gradle | 8 +- 7 files changed, 243 insertions(+), 155 deletions(-) create mode 100644 .github/workflows/android.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 00000000..f78152b8 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,39 @@ +name: Android CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: set up JDK 8 + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'temurin' + cache: gradle + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Install Build Tools + run: sdkmanager "build-tools;29.0.0" "platforms;android-29" + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew clean :app:assembleDebug --no-daemon --stacktrace + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: debug-apk + path: app/build/outputs/apk/debug/*.apk diff --git a/app/build.gradle b/app/build.gradle index ecc637b6..7e384811 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,24 +1,12 @@ -//buildscript { -// repositories { -// maven { url 'https://maven.fabric.io/public' } -// } -// -// dependencies { -// classpath 'io.fabric.tools:gradle:1.+' -// } -//} apply plugin: 'com.android.application' -//apply plugin: 'io.fabric' repositories { -// maven { url 'https://maven.fabric.io/public' } - maven { url 'https://jitpack.io' } - jcenter() + maven { url 'https://api.xposed.info' } mavenCentral() + maven { url "https://jitpack.io" } google() } - android { signingConfigs { release { @@ -72,12 +60,9 @@ dependencies { implementation 'com.google.android.material:material:1.0.0' implementation 'com.github.apl-devs:appintro:v4.2.3' implementation 'de.greenrobot:greendao:2.0.0' -// implementation('com.crashlytics.sdk.android:crashlytics:2.5.2@aar') { -// transitive = true -// } //Glide implementation ('com.github.bumptech.glide:glide:4.9.0') annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' annotationProcessor 'androidx.annotation:annotation:1.1.0' -} \ No newline at end of file +} diff --git a/app/src/main/java/tw/fatminmin/xposed/minminguard/Main.java b/app/src/main/java/tw/fatminmin/xposed/minminguard/Main.java index 6811238c..57fe5308 100644 --- a/app/src/main/java/tw/fatminmin/xposed/minminguard/Main.java +++ b/app/src/main/java/tw/fatminmin/xposed/minminguard/Main.java @@ -3,7 +3,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; -import android.content.res.XModuleResources; import android.os.Build; import java.io.File; @@ -59,9 +58,9 @@ import tw.fatminmin.xposed.minminguard.blocker.adnetwork.MasAd; import tw.fatminmin.xposed.minminguard.blocker.adnetwork.MdotM; import tw.fatminmin.xposed.minminguard.blocker.adnetwork.Millennial; -import tw.fatminmin.xposed.minminguard.blocker.adnetwork.MoPub; import tw.fatminmin.xposed.minminguard.blocker.adnetwork.MobFox; import tw.fatminmin.xposed.minminguard.blocker.adnetwork.Mobclix; +import tw.fatminmin.xposed.minminguard.blocker.adnetwork.MoPub; import tw.fatminmin.xposed.minminguard.blocker.adnetwork.Nend; import tw.fatminmin.xposed.minminguard.blocker.adnetwork.Og; import tw.fatminmin.xposed.minminguard.blocker.adnetwork.Onelouder; @@ -82,6 +81,7 @@ import tw.fatminmin.xposed.minminguard.blocker.custom_mod.OneWeather; import tw.fatminmin.xposed.minminguard.blocker.custom_mod.Viafree; import tw.fatminmin.xposed.minminguard.blocker.custom_mod._2chMate; +import android.content.res.XModuleResources; public class Main implements IXposedHookZygoteInit, IXposedHookLoadPackage { @@ -102,10 +102,20 @@ public class Main implements IXposedHookZygoteInit, IXposedHookLoadPackage /* Custom Mod*/ }; + private static Set hookedPackages = new HashSet<>(); + private static boolean isEnabled(SharedPreferences pref, String pkgName) { + if (pref == null) return false; + + if (pref instanceof XSharedPreferences) { + ((XSharedPreferences) pref).reload(); + } + String mode = pref.getString(Common.KEY_MODE, Common.VALUE_MODE_BLACKLIST); + if (mode == null) mode = Common.VALUE_MODE_BLACKLIST; + if (mode.equals(Common.VALUE_MODE_AUTO)) { return true; @@ -171,16 +181,18 @@ private void UnpackResources() { resources = XModuleResources.createInstance(MODULE_PATH, null); byte[] array = XposedHelpers.assetAsByteArray(resources, "host/output_file"); - String decoded = new String(array); - String[] sUrls = decoded.split("\n"); - - Collections.addAll(patterns, sUrls); + if (array != null) { + String decoded = new String(array); + String[] sUrls = decoded.split("\n"); + Collections.addAll(patterns, sUrls); + } array = XposedHelpers.assetAsByteArray(resources, "host/mmg_pattern"); - decoded = new String(array); - sUrls = decoded.split("\n"); - - Collections.addAll(patterns, sUrls); + if (array != null) { + String decoded = new String(array); + String[] sUrls = decoded.split("\n"); + Collections.addAll(patterns, sUrls); + } } catch (Exception e) { @@ -191,7 +203,11 @@ private void UnpackResources() @Override public void handleLoadPackage(final LoadPackageParam lpparam) { - if (lpparam.packageName.equals(MY_PACKAGE_NAME)) { + if (lpparam == null || lpparam.packageName == null) return; + + final String packageName = lpparam.packageName; + + if (packageName.equals(MY_PACKAGE_NAME)) { XposedHelpers.findAndHookMethod("tw.fatminmin.xposed.minminguard.blocker.Util", lpparam.classLoader, "xposedEnabled", XC_MethodReplacement.returnConstant(true)); if (xposedVersionCode >= 93) XposedHelpers.findAndHookMethod("tw.fatminmin.xposed.minminguard.Common", lpparam.classLoader, "getPrefMode", XC_MethodReplacement.returnConstant(Context.MODE_WORLD_READABLE)); @@ -217,6 +233,9 @@ public void handleLoadPackage(final LoadPackageParam lpparam) * Note that this implies a) hooking System Framework in LSposed Manager and b) needs a reboot after enabling an app in MinMinGuard * because only android can access com.android.server.pm.permission.PermissionManagerService !!! * + * Important: QUERY_ALL_PACKAGES is a highly sensitive permission. Its injection here must be minimal and closely guarded + * so it only affects target apps managed by MinMinGuard to prevent widespread abuse. + * * The implementation is based on Mino260806's snippet (Thanks!) * https://forum.xda-developers.com/t/xposed-for-devs-how-to-dynamically-declare-permissions-for-a-target-app-without-altering-its-manifest-and-changing-its-signature.4440379/post-86833435 * @@ -225,65 +244,67 @@ public void handleLoadPackage(final LoadPackageParam lpparam) * https://github.com/Lawiusz/xposed_lockscreen_visualizer/blob/master/app/src/main/java/pl/lawiusz/lockscreenvisualizerxposed/PermGrant.java */ if (Build.VERSION.SDK_INT > 29) { - if (lpparam.packageName.equals("android")) { + if (packageName.equals("android")) { try { - XposedBridge.hookAllMethods(XposedHelpers.findClass("com.android.server.pm.permission.PermissionManagerService", lpparam.classLoader), "restorePermissionState", new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - String pkgName = (String) XposedHelpers.getObjectField(param.args[0], "packageName"); - Util.log(MY_PACKAGE_NAME, "Package " + pkgName + " is requesting permissions"); - if (isEnabled(pref, pkgName)) { - List permissions = (List) XposedHelpers.getObjectField(param.args[0], "requestedPermissions"); - String query_all_perm = "android.permission.QUERY_ALL_PACKAGES"; - if (!permissions.contains(query_all_perm)) { - permissions.add(query_all_perm); - Util.log(MY_PACKAGE_NAME, "Added " + query_all_perm + " permission to " + pkgName); + Class pmsClass = XposedHelpers.findClassIfExists("com.android.server.pm.permission.PermissionManagerService", lpparam.classLoader); + if (pmsClass != null) { + XposedBridge.hookAllMethods(pmsClass, "restorePermissionState", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + if (param.args == null || param.args.length == 0 || param.args[0] == null) return; + + String pkgName = (String) XposedHelpers.getObjectField(param.args[0], "packageName"); + if (pkgName == null) return; + + if (isEnabled(pref, pkgName)) { + List permissions = (List) XposedHelpers.getObjectField(param.args[0], "requestedPermissions"); + if (permissions != null) { + String query_all_perm = "android.permission.QUERY_ALL_PACKAGES"; + if (!permissions.contains(query_all_perm)) { + permissions.add(query_all_perm); + } + } } } - } - }); + }); + } } catch (Exception e) { Util.log(MY_PACKAGE_NAME, "PermissionManagerService -> " + e); } } } - /** - * https://developer.android.com/reference/android/app/Application.html#onCreate() - * wait for the app started to get remote preferences - */ - XposedHelpers.findAndHookMethod("android.app.Application", lpparam.classLoader, "onCreate", new XC_MethodHook() - { - @Override - protected void afterHookedMethod(MethodHookParam param) - { - final String packageName = lpparam.packageName; - Context context = Util.getCurrentApplication(); - - if (null == context) - { - Util.log(packageName, "failed to get context"); - return; - } + if (hookedPackages.contains(packageName)) return; + hookedPackages.add(packageName); - if (isEnabled(pref, packageName)) + try { + XposedHelpers.findAndHookMethod("android.app.Application", lpparam.classLoader, "onCreate", new XC_MethodHook() + { + @Override + protected void afterHookedMethod(MethodHookParam param) { - Util.log(packageName, "is enabled for MinMinGuard"); - - // Api based blocking - ApiBlocking.handle(packageName, lpparam, param); - appSpecific(packageName, lpparam); + Context context = Util.getCurrentApplication(); - // Name based blocking - NameBlocking.nameBasedBlocking(packageName, lpparam); + if (null == context) + { + return; + } - // url filtering - if (pref.getBoolean(packageName + "_url", false)) + if (isEnabled(pref, packageName)) { - UrlFiltering.removeWebViewAds(packageName, lpparam); + ApiBlocking.handle(packageName, lpparam, param); + appSpecific(packageName, lpparam); + NameBlocking.nameBasedBlocking(packageName, lpparam); + + if (pref.getBoolean(packageName + "_url", false)) + { + UrlFiltering.removeWebViewAds(packageName, lpparam); + } } } - } - }); + }); + } catch (Throwable t) { + Util.log(MY_PACKAGE_NAME, "Failed to hook Application.onCreate in " + packageName); + } } } diff --git a/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/NameBlocking.java b/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/NameBlocking.java index f3816c6b..78216de2 100644 --- a/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/NameBlocking.java +++ b/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/NameBlocking.java @@ -8,12 +8,15 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage; import tw.fatminmin.xposed.minminguard.Main; +import java.util.HashMap; +import java.util.Map; + /** * Created by fatminmin on 2015/10/27. */ -//TODO Use newer XposedHelpers class, Fix formatting public final class NameBlocking { + private static Map cache = new HashMap<>(); private static boolean matchBannerName(String clazzName, String banner, String bannerPrefix) { @@ -28,13 +31,26 @@ private static boolean matchBannerName(String clazzName, String banner, String b // return adnetwork name private static Boolean isAdView(Context context, String pkgName, String clazzName) { - // android widgets - if (clazzName.startsWith("android")) + if (clazzName == null) return false; + + if (cache.containsKey(clazzName)) { + return cache.get(clazzName); + } + + if (clazzName.startsWith("android.") || clazzName.startsWith("androidx.") || clazzName.startsWith("com.google.android.material.")) { + cache.put(clazzName, false); return false; + } - // corner case - if (clazzName.startsWith("com.google.ads")) + if (clazzName.equals("android.widget.FrameLayout") || clazzName.equals("android.widget.RelativeLayout") || clazzName.equals("android.widget.LinearLayout")) { + cache.put(clazzName, false); + return false; + } + + if (clazzName.startsWith("com.google.ads")) { + cache.put(clazzName, true); return true; + } for (Blocker blocker : Main.blockers) { @@ -42,24 +58,28 @@ private static Boolean isAdView(Context context, String pkgName, String clazzNam String banner = blocker.getBanner(); String bannerPrefix = blocker.getBannerPrefix(); - // prefix is used to detect adview obfuscate by proguard if (matchBannerName(clazzName, banner, bannerPrefix)) { - Util.notifyAdNetwork(context, pkgName, name); - + if (context != null) { + Util.notifyAdNetwork(context, pkgName, name); + } + cache.put(clazzName, true); return true; } } + + cache.put(clazzName, false); return false; } private static Boolean isAdView(Context context, String pkgName, View view) { - Class clazz = view.getClass(); - // find also parent classes - int level = 1; + if (view == null) return false; - for (int i = 0; i < level && clazz != null; i++) + Class clazz = view.getClass(); + int maxDepth = 5; + + for (int i = 0; i < maxDepth && clazz != null && clazz != Object.class; i++) { String clazzName = clazz.getName(); @@ -74,11 +94,11 @@ private static Boolean isAdView(Context context, String pkgName, View view) private static void clearAdViewInLayout(final String packageName, final View view) { + if (view == null) return; if (isAdView(view.getContext(), packageName, view)) { ViewBlocking.removeAdView(packageName, view); - Util.log(packageName, "clearAdViewInLayout: " + view.getClass().getName()); } if (view instanceof ViewGroup) @@ -96,15 +116,14 @@ public static void nameBasedBlocking(final String pkgName, final XC_LoadPackage. { Util.hookAllMethods("android.view.ViewGroup", lpparam.classLoader, "addView", new XC_MethodHook() { - @Override protected void beforeHookedMethod(MethodHookParam param) { + if (param.args == null || param.args.length == 0) return; View view = (View) param.args[0]; if (view != null && isAdView(view.getContext(), pkgName, view)) { - Util.log(pkgName, "NameBasedBlocking before addView: " + view.getClass().getName()); ViewBlocking.removeAdView(pkgName, view); } } @@ -112,11 +131,11 @@ protected void beforeHookedMethod(MethodHookParam param) @Override protected void afterHookedMethod(MethodHookParam param) { + if (param.args == null || param.args.length == 0) return; View view = (View) param.args[0]; if (view != null && isAdView(view.getContext(), pkgName, view)) { - Util.log(pkgName, "NameBasedBlocking after addView: " + view.getClass().getName()); ViewBlocking.removeAdView(pkgName, view); } } @@ -127,7 +146,10 @@ protected void afterHookedMethod(MethodHookParam param) @Override protected void afterHookedMethod(MethodHookParam param) { + if (param.thisObject == null) return; Activity ac = (Activity) (param.thisObject); + if (ac.getWindow() == null || ac.getWindow().getDecorView() == null) return; + ViewGroup root = ac.getWindow().getDecorView().findViewById(android.R.id.content); clearAdViewInLayout(pkgName, root); @@ -136,22 +158,10 @@ protected void afterHookedMethod(MethodHookParam param) Util.hookAllMethods("android.view.LayoutInflater", lpparam.classLoader, "inflate", new XC_MethodHook() { - - /* - http://developer.android.com/intl/zh-tw/reference/android/view/LayoutInflater.html - inflate(int resource, ViewGroup root) - inflate(XmlPullParser parser, ViewGroup root) - inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) - inflate(int resource, ViewGroup root, boolean attachToRoot) - - Returns - The root View of the inflated hierarchy. If root was supplied and attachToRoot is true, - this is root; otherwise it is the root of the inflated XML file. - */ - @Override protected void afterHookedMethod(MethodHookParam param) { + if (param.getResult() == null) return; View root = (View) param.getResult(); if (root != null) diff --git a/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/UrlFiltering.java b/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/UrlFiltering.java index 99bb7a96..2d6c27c4 100644 --- a/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/UrlFiltering.java +++ b/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/UrlFiltering.java @@ -1,5 +1,6 @@ package tw.fatminmin.xposed.minminguard.blocker; +import android.net.Uri; import android.view.View; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodHook.MethodHookParam; @@ -19,10 +20,8 @@ public final class UrlFiltering static public boolean removeWebViewAds(final String packageName, LoadPackageParam lpparam) { - try { - Class adView = findClass("android.webkit.WebView", lpparam.classLoader); XposedBridge.hookAllMethods(adView, "loadUrl", new XC_MethodHook() @@ -30,28 +29,27 @@ static public boolean removeWebViewAds(final String packageName, LoadPackagePara @Override protected void beforeHookedMethod(MethodHookParam param) { - + if (param.args == null || param.args.length == 0 || !(param.args[0] instanceof String)) return; String url = (String) param.args[0]; adExist = urlFiltering(url, "", param, packageName); if (adExist) { - param.setResult(new Object()); + param.setResult(null); } } }); XposedBridge.hookAllMethods(adView, "loadData", new XC_MethodHook() { - @Override protected void beforeHookedMethod(MethodHookParam param) { - + if (param.args == null || param.args.length == 0 || !(param.args[0] instanceof String)) return; String data = (String) param.args[0]; adExist = urlFiltering("", data, param, packageName); if (adExist) { - param.setResult(new Object()); + param.setResult(null); } } }); @@ -61,19 +59,21 @@ protected void beforeHookedMethod(MethodHookParam param) @Override protected void beforeHookedMethod(MethodHookParam param) { - String url = (String) param.args[0]; - String data = (String) param.args[1]; + if (param.args == null || param.args.length < 2) return; + + String url = param.args[0] instanceof String ? (String) param.args[0] : ""; + String data = param.args[1] instanceof String ? (String) param.args[1] : ""; + adExist = urlFiltering(url, data, param, packageName); if (adExist) { - param.setResult(new Object()); + param.setResult(null); } } }); } catch (ClassNotFoundError e) { - Util.log(packageName, packageName + "can not clear webview ads"); return false; } return adExist; @@ -81,55 +81,71 @@ protected void beforeHookedMethod(MethodHookParam param) static private boolean urlFiltering(String url, String data, MethodHookParam param, String packageName) { - - Util.log(packageName, "Url filtering"); - - if (url == null) - url = ""; + if (url == null) url = ""; + if (data == null) data = ""; try { url = URLDecoder.decode(url, "UTF-8"); } - catch (UnsupportedEncodingException e) + catch (Exception e) { - e.printStackTrace(); + // Ignore encoding exceptions } - Util.log(packageName, packageName + " url:\n" + url); + try { + Uri uri = Uri.parse(url); + String host = uri.getHost(); + if (host != null) { + for (String adUrl : Main.patterns) { + if (host.contains(adUrl) || url.contains(adUrl)) { + if (param.thisObject instanceof View) { + ViewBlocking.removeAdView(packageName, (View) param.thisObject); + } + return true; + } + } + } else { + for (String adUrl : Main.patterns) { + if (url.contains(adUrl)) { + if (param.thisObject instanceof View) { + ViewBlocking.removeAdView(packageName, (View) param.thisObject); + } + return true; + } + } + } + } catch (Exception e) { + // Ignore URI parsing exceptions + } for (String adUrl : Main.patterns) { if (url.contains(adUrl)) { - Util.log(packageName, "Detect " + packageName + " load url from " + adUrl); - - ViewBlocking.removeAdView(packageName, (View) param.thisObject); - param.setResult(new Object()); - + if (param.thisObject instanceof View) { + ViewBlocking.removeAdView(packageName, (View) param.thisObject); + } return true; } } - Util.log(packageName, packageName + " data:\n" + data); - try { data = URLDecoder.decode(data, "UTF-8"); } catch (Exception e) { - e.printStackTrace(); + // Ignore encoding exceptions } for (String adUrl : Main.patterns) { if (data.contains(adUrl)) { - Util.log(packageName, "Detect " + packageName + " load data from " + adUrl); - ViewBlocking.removeAdView(packageName, (View) param.thisObject); - param.setResult(new Object()); - + if (param.thisObject instanceof View) { + ViewBlocking.removeAdView(packageName, (View) param.thisObject); + } return true; } } diff --git a/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/ViewBlocking.java b/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/ViewBlocking.java index 0fac8261..c244f69a 100644 --- a/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/ViewBlocking.java +++ b/app/src/main/java/tw/fatminmin/xposed/minminguard/blocker/ViewBlocking.java @@ -1,7 +1,6 @@ package tw.fatminmin.xposed.minminguard.blocker; import android.util.DisplayMetrics; -import android.view.Display; import android.view.View; import android.view.ViewGroup; import tw.fatminmin.xposed.minminguard.Main; @@ -10,12 +9,15 @@ public class ViewBlocking { public static void removeAdView(String packageName, View view) { + if (view == null || view.getContext() == null) return; Util.notifyRemoveAdView(view.getContext(), packageName, 1); removeAdView(packageName, view, true, 51); } private static void removeAdView(final String packageName, final View view, final boolean first, final float heightLimit) { + if (view == null) return; + float adHeight = convertPixelsToDp(view.getHeight()); if (first || (adHeight > 0 && adHeight <= heightLimit)) @@ -24,39 +26,49 @@ private static void removeAdView(final String packageName, final View view, fina if (params == null) params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); - else + else if (params.height != 0) params.height = 0; - view.setLayoutParams(params); + if (view.getLayoutParams() != params || params.height == 0) { + view.setLayoutParams(params); + } } - // preventing view not ready situation - view.post(new Runnable() - { - @Override - public void run() + if (view.isAttachedToWindow()) { + view.post(new Runnable() { - float adHeight = convertPixelsToDp(view.getHeight()); - - if (first || (adHeight > 0 && adHeight <= heightLimit)) + @Override + public void run() { - ViewGroup.LayoutParams params = view.getLayoutParams(); + if (view == null || !view.isAttachedToWindow()) return; + + float adHeight = convertPixelsToDp(view.getHeight()); - if (params == null) - params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); - else - params.height = 0; + if (first || (adHeight > 0 && adHeight <= heightLimit)) + { + ViewGroup.LayoutParams params = view.getLayoutParams(); - view.setLayoutParams(params); + if (params == null) + params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); + else if (params.height != 0) + params.height = 0; + + if (view.getLayoutParams() != params || params.height == 0) { + view.setLayoutParams(params); + } + } } - } - }); + }); + } if (view.getParent() != null && view.getParent() instanceof ViewGroup) { ViewGroup parent = (ViewGroup) view.getParent(); - removeAdView(packageName, parent, false, heightLimit); + // Check if we can safely collapse parent + if (parent.getChildCount() <= 1) { + removeAdView(packageName, parent, false, heightLimit); + } } } @@ -67,6 +79,7 @@ private static float convertPixelsToDp(float px) if(Main.resources != null) metrics = Main.resources.getDisplayMetrics(); + if (metrics.densityDpi == 0) return 0; return px / (metrics.densityDpi / 160f); } } diff --git a/build.gradle b/build.gradle index 44c10be6..ef436776 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,9 @@ buildscript { repositories { - jcenter() + mavenCentral() + maven { url "https://jitpack.io" } + google() } dependencies { @@ -15,7 +17,9 @@ buildscript { allprojects { repositories { - jcenter() + mavenCentral() + maven { url "https://jitpack.io" } + google() } } From ac14ae65ee5432dd7811771ca218f5db74bb3f3c Mon Sep 17 00:00:00 2001 From: Illhm <194127535+Illhm@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:46:46 +0000 Subject: [PATCH 2/4] feat: Modernize build and stabilize hook logic - Added GitHub Actions workflow `android.yml` to automatically build the debug APK using JDK 8. Fixed CI jdk version compatibility for cmdline-tools. - Safely cleaned the build system by replacing `jcenter()` with `mavenCentral()` and configuring `jitpack.io` / `api.xposed.info`. - Stabilized `Main` hook logic to avoid duplicate installations, safely reload `XSharedPreferences`, and verify null values. - Retained crucial documentation regarding the sensitivity of the `QUERY_ALL_PACKAGES` permission required for Android 11+. - Improved `ViewBlocking` to prevent parent views from collapsing incorrectly by adding detached view checks and child count conditions. - Upgraded `NameBlocking` to safely traverse superclasses (with depth boundaries) and correctly cache results to improve overall system performance without hitting core Android UI components. - Modernized `UrlFiltering` by utilizing safe normalized host and path matching over raw URI logic, preventing random crashes and encoding errors on string `WebView` operations. --- .github/workflows/android.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index f78152b8..d2f7f4e3 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -12,20 +12,26 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: set up JDK 8 - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - name: set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '17' distribution: 'temurin' - cache: gradle - name: Setup Android SDK - uses: android-actions/setup-android@v2 + uses: android-actions/setup-android@v3 - name: Install Build Tools run: sdkmanager "build-tools;29.0.0" "platforms;android-29" + - name: set up JDK 8 for gradle compilation + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + cache: gradle + - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -33,7 +39,7 @@ jobs: run: ./gradlew clean :app:assembleDebug --no-daemon --stacktrace - name: Upload Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debug-apk path: app/build/outputs/apk/debug/*.apk From 63fa29c998bde70e3dcbf34fb62b5f0cd66e5c1b Mon Sep 17 00:00:00 2001 From: Illhm <194127535+Illhm@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:50:07 +0000 Subject: [PATCH 3/4] fix: Setup JDK 17 for sdkmanager compatibility - The `android-actions/setup-android@v3` and `sdkmanager` operations require Java 11 or later to parse the `com/android/sdklib/tool/sdkmanager/SdkManagerCli` classes properly, but our compile environment needs JDK 8. - Added a step to load JDK 17 before `sdkmanager`, and load JDK 8 afterwards for compilation to fix the `UnsupportedClassVersionError` build breakage. --- .github/workflows/android.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d2f7f4e3..243eb3e5 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -13,7 +13,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: set up JDK 17 + + - name: set up JDK 17 for SDK Manager uses: actions/setup-java@v4 with: java-version: '17' From 4cfec5c54173f73290f239c3432cdce3ea990e22 Mon Sep 17 00:00:00 2001 From: Illhm <194127535+Illhm@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:53:45 +0000 Subject: [PATCH 4/4] fix: Resolve SdkManager compatibility using manual setup script and JDK 17 - Dropped `android-actions/setup-android` because it inherently locks itself to the ambient JAVA_HOME or causes conflicts with older components and modern JDKs on github actions. - Used manual sdkmanager zip download block + execution under JDK 17 context, then flipped over to JDK 8 for the build context exactly like the legacy setup script `android-ci.yml`. --- .github/workflows/android.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 243eb3e5..015c082e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -11,6 +11,10 @@ jobs: build: runs-on: ubuntu-latest + env: + ANDROID_SDK_ROOT: ${{ github.workspace }}/android-sdk + ANDROID_HOME: ${{ github.workspace }}/android-sdk + steps: - uses: actions/checkout@v4 @@ -20,11 +24,19 @@ jobs: java-version: '17' distribution: 'temurin' - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Install Build Tools - run: sdkmanager "build-tools;29.0.0" "platforms;android-29" + - name: Install Android SDK + run: | + mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" + cd "$ANDROID_SDK_ROOT/cmdline-tools" + curl -fsSL -o cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip + unzip -q cmdline-tools.zip + mv cmdline-tools latest + + yes | "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" --licenses + "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" \ + "platform-tools" \ + "platforms;android-29" \ + "build-tools;29.0.0" - name: set up JDK 8 for gradle compilation uses: actions/setup-java@v4