Skip to content

Java API Reference

Emirhan Uçan edited this page Jul 31, 2026 · 3 revisions

Java API Reference

Function-level reference for the Android application layer of HydraDragonAV Mobile. Package root: com.hydradragon.antivirus (note: not com.hydradragon.av).

The app is organized into seven packages: engine/ (46 classes — the Java scan orchestration, JNI bridge, behavioral/network/whitelist/self-protection logic), service/ (foreground services + broadcast receivers), ui/ (fragments + activities), views/ (custom views), adapter/ (RecyclerView adapters), model/ (immutable data types), security/ (window/task-hijack guards), plus the root Application/MainActivity/BootReceiver.

Cross-references: Rust-API-Reference for the native side of every NativeScanner call; YARA-X-Modules for the hydradragon module schemas (static APK + HIPS/network) the Java layer feeds; Architecture for the system-level view.


Core scan engine

ScanEngine.java (2483 lines) — the core orchestrator

The central scan orchestrator. Drives quick, full, custom-folder and single-file scans; calls into the native (Rust) engine via NativeScanner; runs the Java-side heuristic/permission/signature/whitelist analysis; manages scan caching, whitelisting, batch/deferred verdict processing, thread pools, wake locks, pause/cancel, and the auto-rule self-learning path. Built and held by GuardService, InstallReceiver (new instance per install broadcast), ScanFragment, and the download observer.

Constructor and fields of note:

  • public ScanEngine(Context context, AIEngine aiEngine) (:391) — loads the package whitelist from the SQLite whitelist_packages.db asset and calls NativeScanner.init(context).
  • Holds AIEngine aiEngine, CodeAnalyzer codeAnalyzer, AntiFnCache antiFnCache (:96-98).

Major responsibilities:

  1. Driving the scanscanAllApps/scanCustomFolder submit work to a static bounded scanExecutor (newFixedThreadPool(NATIVE_PARALLELISM), NATIVE_PARALLELISM = max(1, min(2, cores/2)) at :109-119). A scanRunning AtomicBoolean CAS guards against two overlapping runs (:703, :758). If a background scan is already running with the same requested type, a user scan "adopts" it (:759). Wake lock acquired/released around each scan (acquireScanWakeLock :408, releaseScanWakeLock :431).

  2. Calling into NativeScanner (JNI) — every native call goes through runNativeInterruptible(Callable<NativeScanner.Verdict>) (:325): submits to nativeCallExecutor, polls cancelRequested every 150 ms, returns null on cancel (abandoning the in-flight JNI call rather than killing the thread). PackageManager calls go through runPkgInfoInterruptible(Callable<PackageInfo>) (:306) with a 5 s timeout that flips apkPkgAnalyzerBroken on Vivo ROM deadlocks.

  3. Handling results — per-detection whitelist suppression via survivingDetections(Verdict) (:627) / isDetectionWhitelisted(Detection) (:576): a hit is an FP iff its extraction-lineage hash is whitelisted OR the signature name is in IgnoredSignatures. Anti-FN up-sell via TLSH similarity in upsellAntiFnDetections (:637) and cache update via updateAntiFnCache (:671). Verdict categories are gated by DetectionCategories.isEnabled: real malware (risk 100, MALWARE), EICAR (50, TEST_MALWARE), PUA (50, PUA), auto-rule only (30, SUSPICIOUS), ML (only if jaccard>=0.55 && anomaly>=0.33), dangerous permissions (>=30 MALWARE, >=25 SUSPICIOUS).

  4. Whitelist loadingloadPackageWhitelist() (:492) copies assets/scan/whitelist_packages.db to noBackupFilesDir and loads the whitelist_package.key column into whitelistPackages HashSet. Auto-clear requires BOTH isFromStore (trusted installer: Vending/Samsung/Xiaomi/Huawei/HeyTap/Oppo market, :1628) AND isPackageWhitelisted (:1646). Hash whitelist is native (xor filter) via NativeScanner.isHashWhitelisted / isHashWhitelistedForFile (:523).

  5. Signature DB management — deferred to native; Java only manages the auto-generated rules dir. saveGeneratedRule(Verdict) (:600) writes the yarGen-style rule to filesDir/hydra-scan/generated_rules/auto_<md5>.yar (gated by SaveAutoRules.isEnabled) and hot-loads it via NativeScanner.learnRule so the current session benefits. generateRuleForApp(String apkPath, String packageName) (:364) is the manual, user-requested, non-gated variant (forces zero_trust=true).

  6. Scheduling — no internal scheduler; periodic scans are driven by GuardService using ScanSchedule intervals. ScanEngine exposes setBackgroundScan(boolean) (:236), setBackgroundPriority(boolean) (:117), pause/resume/cancel: cancelScan() (:261), pauseScan() (:273), resumeScan() (:278), isCancelled() (:283), isPaused() (:286).

  7. Relationship to ScanFragment — ScanFragment registers a ScanCallback (setCallback :693, interface at :383 with onProgress/onThreatFound/onScanComplete/onError/onFileScanned). scanAllApps(boolean isFullScan) (:746) returns true iff it actually started a scan (race-free contract ScanFragment must check). scanCustomFolder(File) (:881) and scanSingleFile(File) (:1078) are the other entry points. invalidateCache(String) (:158), clearCache() (:162), clearCache(Context) (:171) manage the MD5-keyed session caches.

Major public methods (exact signatures):

Method Line
public boolean scanAllApps(boolean isFullScan) :746
public boolean scanCustomFolder(java.io.File dir) :881
public ThreatResult scanSingleFile(java.io.File file) :1078
public ThreatResult analyzeSingleApp(ApplicationInfo app, PackageManager pm, boolean isApkFile) :1575
public ThreatResult analyzeApp(ApplicationInfo app, PackageManager pm, boolean isApkFile) :1579
public String nativeScanApk(String apkPath) :444
public boolean generateRuleForApp(String apkPath, String packageName) :364
public void setCallback(ScanCallback callback) :693
public void cancelScan() / pauseScan() / resumeScan() :261 / :273 / :278
public boolean isScanRunning() / isCancelled() / isPaused() :706 / :283 / :286
public void setBackgroundScan(boolean background) / isBackgroundScan() :236 / :239
public static void setBackgroundPriority(boolean on) :117
public static void runOrchestrated(Runnable task) :137
public static void invalidateCache(String packageName) / clearCache() / clearCache(Context) :158 / :162 / :171

Private helper groups: caching (photonCacheEnabled :181, computeFileMd5 :546, photonCache/fileScanCache :141); whitelist predicates (isHashWhitelisted :523, isFileHashWhitelisted :530, isPackageWhitelisted :537, isDetectionWhitelisted :576, survivingDetections :627, upsellAntiFnDetections :637, updateAntiFnCache :671); name classification (isPuaName :449, isAutoGeneratedName :464, isEicarName :468, isDexHeuristicName :478, subFileSuffix :487, hasNoLauncherIcon :65); interruptible executors (runPkgInfoInterruptible :306, runNativeInterruptible :325); full-scan extra passes (scanAllStorageRoots :1376, deepNativeScanInstalledApks :1404, scanRecentProcesses :1528, scanAccessibleDataDirs :1560); directory walk (scanDirectoryForApks :923, scanGenericFile :1163, reportFileScanned :941); batch/deferred (DeferredScanState :2037, flushBatchScans :2057, processFinalVerdict :2165, processFinalVerdictGeneric :2347, processFinalVerdictDeep :2425); misc (checkDefaultLauncher :711 emits the LAUNCHER_CHANGE HIPS signal, acquireScanWakeLock/releaseScanWakeLock :408/:431, addTiming/logEngineTimings :202).

Constants: ROOTKIT_SUSPICIOUS_PERMS :47, TRUSTED_COMPANIES :77, WHITELIST_PREFIXES :85, SCAN_TYPE_NONE/QUICK/FULL :241.

The detection pipeline order in analyzeApp (:1579): photon cache → self/system/store+NSRL whitelist → user-allowed → signature+company extraction → rootkit (no-launcher + suspicious perms) → CodeAnalyzer + native scan (interruptible) → per-detection whitelist suppression + anti-FN up-sell → category-gated verdict (ML/EICAR/PUA/AUTO/SIGNATURES/PERMISSIONS) → CodeAnalyzer corroboration (only if native corroborated) → BehaviorFlags runtime override → Zero-Trust fallback → build ThreatResult + cache.

NativeScanner.java (745 lines) — the JNI bridge to libhydradragonandroid.so

The Java bridge to the native Rust scanner. Combines compiled YARA-X rulesets (.yrc), ClamAV signature DBs, the Burn ML model, a Unicorn-based native-code emulation pass, and native xor-filter URL/IP/NSRL-hash lookups — all in native memory. Java side parses the JSON verdicts and exposes typed wrappers. See Rust-API-Reference for the Rust side of every method below.

Loading the .so: static initializer :54 calls System.loadLibrary("hydradragonandroid"); on any Throwable sets LIB_LOADED=false and degrades gracefully (every method no-ops). ASSET_DIR = "scan" (:37); Rust reads assets directly via AAssetManager (no disk copy).

Thread management: INIT_STARTED AtomicBoolean (:51) prevents redundant concurrent nativeInit() threads (one-time load takes ~70 s). init is synchronized. waitUntilReady(long timeoutMs) (:728) blocks (never on UI thread) polling isReady() every 100 ms up to the deadline — used by ScanEngine.scanGenericFile to avoid false negatives while the DB loads.

Every native method declaration (JNI bridge) — these correspond 1:1 to the 25 Rust Java_com_hydradragon_antivirus_engine_NativeScanner_* functions in Rust-API-Reference:

Java native declaration Line
private static native boolean nativeInit(String assetDir, boolean loadAutoRules, AssetManager assetManager, String filesDir) :72
private static native boolean nativeIsReady() :77
private static native boolean nativeLearnRule(String yarPath) :79
private static native boolean nativeIsEmulationAvailable() :81
private static native void nativeSetEmulationEnabled(boolean) :83
private static native void nativeSetMaxScanSizeMb(int) :85
private static native void nativeSetDetectZipBomb(boolean) :87
private static native void nativeSetScanRelevantOnly(boolean) :89
private static native String nativeScanApk(String path, String hydradragonJson, String fileMd5, boolean zeroTrust) :149
private static native void nativeBeginBatchScan() :150
private static native String nativeEndBatchScan() :151
private static native void nativeAbortBatchScan() :152
private static native String nativeStatus() :173
private static native boolean nativeIsHashWhitelisted(String md5) :175
private static native boolean nativeIsHashWhitelistedForFile(String path, String md5) :176
private static native String nativeScanUrl(String url) :178
private static native String nativeScanIp(String ip) :191
private static native String nativeScanText(String text) :195
private static native String nativeScanHips(String hipsJson) :197
private static native void nativeSetTlshThreshold(int) :199
private static native int nativeTlshDiff(String tlsh1, String tlsh2) :201
private static native void nativeEnableVpnScan(boolean) :205
private static native String nativeScanPackets(String packetsJson) :207

Java-side wrappers (public API):

Wrapper Line Notes
public static synchronized boolean init(Context context) :413 Idempotent; creates filesDir/hydra-scan, calls nativeInit with loadAutoRules from DetectionCategories.AUTO_RULES; if ready, probes Unicorn and pushes all live settings into native.
public static boolean isReady() :712 LIB_LOADED && nativeIsReady().
public static boolean waitUntilReady(long timeoutMs) :728
public static String scanApk(String apkPath) / (String,String,String) / (String,String,String,boolean zeroTrust) :468/:477/:486 Feeds NetworkObservations.buildReportJson(packageName) as the hydradragon module metadata.
public static Verdict scan(String apkPath) / (String,String) / (String,String,String) / (String,String,String,boolean) :556-576 Wraps scanApk + parseVerdictJson.
public static List<Verdict> parseBatchVerdicts(String jsonArray) :596
public static Verdict parseVerdictJson(JSONObject o) :613
public static void beginBatchScan() / String endBatchScan() / abortBatchScan() :154/:159/:167
public static boolean learnRule(String yarPath) :144 Hot-loads a .yar into the live engine.
public static void setEmulationEnabled(boolean) / onEmulationUnavailable(String) :97/:105
public static void setMaxScanSizeMb(int) / setDetectZipBomb(boolean) / setScanRelevantOnly(boolean) :113/:123/:132
public static String scanUrl(String url) :183 Returns malicious category or null.
public static String scanIp(String ip) :320 Public IPv4 only.
public static boolean isValidPublicIp(String ip) :332 Rejects private/loopback/link-local/multicast/reserved.
public static boolean isHashWhitelisted(String md5) / isHashWhitelistedForFile(String, String) :367/:374 Rust validates ZIP magic first.
public static List<String> scanText(String text) :384 OCR'd screen text against hydradragon.screen_text rules.
public static HipsResult scanHips() :254 Builds HipsMonitor.buildReportJson, calls nativeScanHips, parses malicious/suggestion/matches.
public static void scanAndRespond(Context context) :279 scanHips + auto-uninstall if suggestion="uninstall".
public static String scanPackets(String packetsJson) :219 VPN packets vs emerging-all.yrc.
public static void enableVpnScan(boolean) :211
public static void setTlshThreshold(int) / int tlshDiff(String, String) :230/:236
public static String status() :397 Human-readable load report.

Data structures:

  • public static final class Verdict (:495) — boolean malicious, List<String> matches, boolean mlMalicious, double jaccard, double anomaly, String nearest, int permissions, List<String> packages, List<String> hashes, List<Detection> detections, String md5, String fileTlsh, HashMap<String,String> entryMd5s, HashMap<String,String> entryTlshs, Integer skippedTarget, String error, String generatedRule, boolean deferred, String path. Methods isError() :552, isSkipped() :553.
    • public static final class Verdict.Detection (:518) — String name, String objectPath (full in-archive path, e.g. app.apk!/classes.dex), List<String> hashes.
  • public static final class HipsResult (:243) — boolean malicious, List<String> matches, String suggestion ("uninstall"/"warn"/"none"), isMalicious().

Called by: ScanEngine (every scan path), HipsMonitor (via scanHips), UrlThreatScanner.scanUrl, NetworkMonitor.isSuspiciousDomain, AntiFnCache.findSimilarTlsh (tlshDiff), DnsVpnService (enableVpnScan/scanPackets), ScreenCaptureService (scanText), HydraDragonApp/MainActivity/BootReceiver (init).

AIEngine.java (78 lines)

Tiny Java-side logistic-regression "anomaly" scorer over 8 boolean code-feature flags produced by CodeAnalyzer. Not the main ML model (that's the native Burn classifier — see Rust-API-Reference / AI-ML-Models). Used only as a corroboration signal.

  • public AIEngine() / public AIEngine(Context context) (:25) — no-op constructors for backward compat.
  • public void close() (:27).
  • public static AIResult predictMalwareProbability(Map<String,Boolean> features) (:44) — sum = BIAS(-2.0) + Σ weights, sigmoid → 0-100; returns an anomaly result (aiScore/2, description, isAnomalyDetected=true) only if aiScore>50 && activeFeatures>=3, else clean. Classifies a threatType (RANSOMWARE/SPYWARE/ADWARE/TROJAN) from which feature is set.
  • public static class AIResult (:30) — riskScore, description, isAnomalyDetected, threatType.

Called by CodeAnalyzer.analyzeApk (CodeAnalyzer.java:117).

CodeAnalyzer.java (163 lines)

Cheap Java-side DEX-bytecode substring heuristic. Unzips an APK, concatenates classes*.dex bytes (capped ~200 KB each), scans for a hardcoded list of suspicious API strings. Most hits are logged-only — only DexClassLoader/DexFile add to totalRisk (100). Its verdict is applied by ScanEngine only when the real (native) engine corroborates it (ScanEngine.java:1951).

  • public CodeAnalyzer(Context context) (:35).
  • public AnalysisResult analyzeApk(String apkPath) (:53) — returns AnalysisResult(riskScore, findings, threatType); isMalicious = riskScore>=60. Feeds 8 boolean feature flags into AIEngine.predictMalwareProbability.
  • public AnalysisResult analyzeInstalledApp(String packageName) (:153) — resolves sourceDir then analyzeApk.
  • public static class AnalysisResult (:39) — riskScore, findings, isMalicious, threatType.

Called by ScanEngine.analyzeApp (:1778).

AntiFnCache.java (106 lines)

Persistent SQLite "anti-false-negative" cache of malicious TLSH hashes + detection names, so a repackaged/renamed variant of a previously-caught sample is still flagged. Backs the TLSH up-sell in ScanEngine.upsellAntiFnDetections. Exportable/importable via KnowledgeDatabase.

  • public AntiFnCache(Context context) (:26) — opens/creates noBackupFilesDir/anti_fn_cache.db, table malicious_tlsh(tlsh TEXT PK, detection_name TEXT, added_at INTEGER). enabled = pref anti_fn_enabled (default true).
  • public boolean isEnabled() (:42).
  • public void addEntry(String tlsh, String detectionName) (:46) — INSERT OR IGNORE.
  • public String findSimilarTlsh(String tlsh, int threshold) (:61) — full-scan cursor, calls NativeScanner.tlshDiff(tlsh, cached) for each; returns the first detection name within threshold, else null.
  • public static int getTlshThreshold(Context context) (:80) — pref anti_fn_tlsh_threshold, default 40.
  • public void clean(long maxAgeMs) (:85); public void clear() (:96).

CleanupEngine.java (133 lines)

"Bloatware"/memory cleanup scanner — lists non-system background/service/cached processes with their RAM usage, for a user-driven "free up memory" UI. Not a malware detector.

  • public CleanupEngine(Context ctx) (:47).
  • public void scan(Callback cb) (:49) — single-thread executor; walks ActivityManager.getRunningAppProcesses, filters by importance, skips own package + SAFE prefixes (:20), measures PSS, emits onFound per app then onDone(total).
  • public static boolean disableWithRoot(String pkg) (:123) — su -c pm disable-user --user 0 <pkg>; true if output contains "disabled".
  • public static class BloatwareApp (:27) — pkg/name/reason/memKb/icon.
  • public interface Callback (:39) — onFound/onDone.

Behavioral protection

HipsMonitor.java (449 lines)

Process-wide HIPS metadata collector. Gathers behavioral signals from every detector and exposes buildReportJson() — merged into the YARA-X hydradragon module metadata so HIPS rules can match runtime behavior. All static, synchronized, bounded (MAX_EVENTS_PER_TYPE=256, MAX_PACKAGES=1024). It does not detect — it collects. Each detector calls a report* method; buildReportJson (:281) serializes everything (and clears the event lists, one-shot) into the JSON shape documented in YARA-X-Modules: ui_spam_events, notification_spam_events, clickjack_events, ransomware_events, canary_events, network_events, strandhogg_events, removal_resistance_events, launcher_change_events, system, behavior_flags, behavior_state, plus network.packets from DnsVpnService.getCapturedPacketsJson.

All public static synchronized:

  • void reportUiSpam(String pkg, int clicks, int windows, long windowMs, boolean malicious) (:145)
  • void reportNotificationSpam(String pkg, int count, long windowMs, boolean malicious) (:155)
  • void reportClickjack(String pkg, int clicks, String target, long windowMs, boolean malicious) (:165)
  • void reportRansomware(String pkg, int renames, String suffix, boolean accessGranted, boolean isAllFiles, long windowMs, boolean malicious) (:175)
  • void reportCanary(String pkg, boolean triggered) (:188)
  • void reportNetwork(String pkg, int connections, int hosts, int queries) (:197)
  • void reportStrandHogg(String pkg, int activities, boolean suspicious) (:207)
  • void reportRemovalResistance(String pkg, int kicks, String screenKind, long windowMs, boolean malicious) (:216) — also addBehaviorFlag(pkg, "REMOVAL_RESISTANCE")
  • void reportLauncherChange(String pkg, boolean changed, String method, boolean suspicious) (:228) — also addBehaviorFlag(pkg, "LAUNCHER_CHANGE")
  • void setRooted(boolean) (:238); void setDebugMode(boolean) (:240); void setSelfProtectionTriggered(boolean, String pkg) (:242); void setForegroundPackage(String pkg) (:247)
  • void addBehaviorFlag(String pkg, String flag) (:252); boolean hasBehaviorFlag(String pkg, String flag) (:264)
  • String buildReportJson() (:281) — one-shot: clears events after building.

Called by all the behavioral guards, ScanEngine.checkDefaultLauncher (:730), NativeScanner.scanHips (consumes buildReportJson).

RansomwareBehaviorGuard.java (224 lines)

Dynamic ransomware detection based on the SHAPE of mass in-place file encryption — not any hardcoded extension. Detects: (1) an app transitions from NOT-having to HAVING file/storage access (a fresh on-screen grant), then (2) shortly after, files it can reach start disappearing and reappearing with an EXTRA suffix, (3) repeated across enough distinct files in a short window.

  • onForegroundPermissionCheck(Context, String pkg) (:85) — on every foreground change, computes storageState (FLAG_READ_WRITE=1, FLAG_ALL_FILES=2 via MANAGE_EXTERNAL_STORAGE AppOps). Only a state CHANGE after a baseline counts as a grant. On a new grant, records grantedAt and whether it was All-Files; if All-Files, calls FileCanaryGuard.maybeDeployFor.
  • onFileEvent(Context, String dirPath, String fileName) (:128) — checks if the new name is an EARLIER known file with something appended (fileName.startsWith(existing + ".")). Requires a recent grant within GRANT_WINDOW_MS=15min. Counts renames in RENAME_BURST_WINDOW_MS=1min; triggers at RENAME_BURST_THRESHOLD=5.
  • On trigger: BehaviorFlags.flag, HipsMonitor.reportRansomware, ThreatLogger.logThreat, alert, BehaviorResponse.killAndPromptUninstall.

Gated by BehaviorDetectionSettings.isEnabled(c, RANSOMWARE). Called by DynamicAnalysisService (foreground checks) and GuardService (file observers).

FileCanaryGuard.java (313 lines)

File-trap (canary) ransomware detection — complements the statistical heuristic with a decoy file (0000_Important_Do_Not_Delete.txt, sorts first alphabetically) that no legitimate app has reason to touch. If it gets renamed with an appended suffix or its content changes, that alone is conclusive proof.

  • maybeDeployFor(Context, String pkg) (:105) — deploys ONLY when ALL of: (a) app just granted "All Files Access" on screen, (b) genuinely unknown (not trusted / not already flagged / not user-allowlisted), (c) installed within FRESH_INSTALL_WINDOW_MS=24h. Writes the canary into Downloads/Documents/Pictures/DCIM (targetDirs :92) and attaches a FileObserver (MOVED_TO|CLOSE_WRITE|DELETE).
  • onCanaryDirEvent (:179) — if the canary is DELETED, its content no longer matches (contentMatches :233), or it's renamed to <canary>.<suffix> → tampered. Attributes to current foreground package (fallback to the suspect pkg). One hit alone is conclusive.
  • On trigger: BehaviorFlags.flag, HipsMonitor.reportCanary, ThreatLogger.logThreat, alert, BehaviorResponse.killAndPromptUninstall, then cleanupNow.
  • Traps auto-delete after TRAP_LIFETIME_MS=24h via scheduleCleanup (:242).

Gated by BehaviorDetectionSettings.isEnabled(c, FILE_CANARY). Called by RansomwareBehaviorGuard.onForegroundPermissionCheck (:98).

RemovalResistanceGuard.java (144 lines)

Detects malware defending itself from removal: the device-admin deactivation / uninstall confirmation screen for a package opens and closes abnormally fast (faster than a human can read), landing the user back on that app — produced by malware racing its AccessibilityService to slam GLOBAL_ACTION_BACK.

  • onWindowSwitch(Context, String newPkg, String prevPkg, boolean newIsSensitiveScreen) (:50) — on a sensitive screen open, records pendingTarget=prevPkg + pendingOpenedAt. On the next switch, if pendingConfirmed && elapsed <= KICK_MAX_MS=1500ms && returns to pendingTarget, calls recordKick.
  • confirmSensitiveScreen(String kind) (:80) — called when on-screen text matches an uninstall/device-admin prompt.
  • recordKick (:87) — counts kicks per pkg in KICK_WINDOW_MS=5min; reports to HIPS each time; triggers at KICK_THRESHOLD=3. On trigger: BehaviorFlags.flag, ThreatLogger.logThreat, alert, BehaviorResponse.killAndPromptUninstall, resets the window.

Gated by BehaviorDetectionSettings.isEnabled(c, REMOVAL_RESISTANCE). Called by DynamicAnalysisService (window switches + screen-text matching).

ProcessDetector.java (294 lines)

Hidden/suspicious process detection via ActivityManager and /proc/. Detects dangerous process names, hidden process names, high memory usage, unknown packages, and processes hiding their name in /proc.

  • DANGEROUS_PROCESS_NAMES (:38): su, supersu, magisk, daemonsu, netcat, nc, ncat, tcpdump, wireshark, frida, xposed, substrate, metasploit, msfconsole, meterpreter, cryptominer, xmrig, keylogger, spyware.
  • HIGH_MEMORY_THRESHOLD_MB=500 (:49), HIGH_CPU_THRESHOLD=80% (:51).
  • public ProcessDetector(Context context) (:63); public void setCallback(ProcessCallback callback) (:69).
  • public List<ProcessInfo> scanRunningProcesses() (:76) — walks getRunningAppProcesses, analyzes each (analyzeProcess :115: name match +80, hidden name +20, high mem +15, unknown pkg +30), then scanProcFilesystem (:186, cached 5 min via PROC_SCAN_TTL_MS=300000) reading /proc/<pid>/comm and /proc/<pid>/status. Hidden name in /proc +90, unreadable name +25.
  • public interface ProcessCallback (:58) — onSuspiciousProcess/onProcessListUpdated.

ScreenThreatKeywords.java (427 lines)

Multi-language (20 languages) phrase lists for the accessibility screen-text scanner. Matching is plain lowercase substring — no NLP/translation — so phrases are listed per language. Used to recognize ransomware notes, device-admin/uninstall prompts, SMS phishing, and fake-virus-warning tech-support scams rendered on screen.

Phrase arrays (all public static final String[]):

  • RANSOMWARE (:18) — ransom-note text per language.
  • DEVICE_ADMIN (:142) — device-admin grant prompts.
  • UNINSTALL (:166) — uninstall confirmation prompts (used by RemovalResistanceGuard).
  • SMS_PHISHING (:196) — lures.
  • FAKE_VIRUS_WARNING (:300) — tech-support-scam popups.
  • public static boolean containsAny(String lowerText, String[] phrases) (:404) — true if any phrase is a substring.
  • public static boolean containsAtLeast(String lowerText, String[] phrases, int min) (:417) — true if at least min DISTINCT phrases match (used for SMS_PHISHING/FAKE_VIRUS_WARNING/RANSOMWARE where a single hit is too weak).

DynamicRiskEngine.java (208 lines)

Combines several independently-weak dynamic signals into one per-app risk score, instead of alerting on any single one. Signals: permission combinations actually granted (overlay+accessibility = banker-trojan; SMS+Internet = exfiltration), time-since-install (fresh install + suspicious DNS = dropper beacon), and plain-DNS lookups of anonymizer/tunnel-shaped domains (.onion, tor2web, i2p, ngrok.io, serveo.net, dyndns, no-ip, ddns). Honest caveat: this is NOT Tor-connection detection (the VPN only sees DNS, not TCP/UDP payload).

  • onDomainObserved(Context, String pkg, String host) (:79) — no-op for normal domains; for suspicious-pattern hits, increments torHitCounts and calls evaluate.
  • evaluate (:104) — netScore = min(50, torHits*20); +25 if installed within FRESH_INSTALL_WINDOW_MS=1hr; +25 overlay+accessibility; +20 SMS+Internet; +10 device-admin; +10 REQUEST_INSTALL_PACKAGES. Triggers at SCORE_THRESHOLD=70.
  • On trigger: BehaviorFlags.flag, ThreatLogger.logThreat, alert, BehaviorResponse.killAndPromptUninstall.

Gated by BehaviorDetectionSettings.isEnabled(c, DYNAMIC_RISK). Called by DnsVpnService.

BehaviorResponse.java (207 lines)

The immediate-action response shared by every behavior detector and by every scan (via the ThreatResult overload) the instant something is flagged above ThreatResult.isThreat(). Honest about a non-system app's limits: killBackgroundProcesses works only when the target isn't foreground; the system uninstall dialog (ACTION_DELETE) is fired immediately but the user still confirms; a standalone file has no process to kill so it routes to a delete-file notification.

  • public static void killAndPromptUninstall(Context context, String pkg) (:60) — killBackgroundProcesses + ACTION_DELETE intent.
  • public static void killAndPromptUninstall(Context context, ThreatResult threat) (:87) — routes installed-app → killAndPromptUninstall(pkg), standalone file → promptDeleteFile, then showMalwareFoundScreen (full-screen MalwareFoundActivity over whatever's in foreground; relies on declared SYSTEM_ALERT_WINDOW).
  • public static void autoDeleteThreat(Context context, ThreatResult threat) (:129) — the AutoDeleteMalware-on path: installed app → uninstall prompt + log; standalone file → file.delete() outright + log. No full-screen prompt.
  • Private: showMalwareFoundScreen :103, promptDeleteFile :169 (notification with Destroy/Ignore actions via UserActionReceiver), isPackageInstalled :153.

BehaviorFlags.java (65 lines)

Persistent SharedPreferences store (hydra_behavior_flags) of packages flagged by runtime behavior (not static signatures). The dynamic-analysis detectors write here; ScanEngine.analyzeApp reads it so a behaviourally-flagged app is reported as MALWARE (riskScore=100) on the next scan. Entry format pkg + SEP + reason where SEP is a control char ().

  • public static synchronized void flag(Context c, String pkg, String reason) (:29) — replaces any existing entry for pkg.
  • public static String reasonFor(Context c, String pkg) (:39).
  • public static boolean isFlagged(Context c, String pkg) (:50).
  • public static synchronized void clear(Context c, String pkg) (:54).

Called by all behavioral guards (write), ScanEngine.analyzeApp/processFinalVerdict (read), UserDecisions.allowThreat (clear).

BehaviorDetectionSettings.java (44 lines)

On/off switches for every dynamic (runtime-behavior) detector, as opposed to DetectionCategories (which gates static scan engines). Each detector's entry point checks isEnabled before doing work, so "off" is a genuine no-op. Category key constants (all public static final String): UI_SPAM, ROOT_EXPLOIT, DYNAMIC_RISK, RANSOMWARE, TASK_HIJACK, SCREEN_SECURITY, FILE_CANARY, REMOVAL_RESISTANCE (:17-33). All default true. public static boolean isEnabled(Context c, String key) (:37); public static void setEnabled(Context c, String key, boolean on) (:41). Pref key = behavior_detect_<key>.

AppLifecycleTracker.java (46 lines)

Tracks how many of HydraDragon's OWN activities are alive. Used by StrandHoggGuard to detect task hijacking: a malicious app inserting ITS activity into our task makes the real ActivityManager activity count higher than what we created.

  • public static int getExpectedActivityCount() (:21).
  • public static void register(Application app) (:25) — idempotent; registers an instance as ActivityLifecycleCallbacks. onActivityCreated increments createdCount, onActivityDestroyed decrements.

Network security

NetworkMonitor.java (370 lines)

Network traffic monitor — suspicious IP/domain/port detection, C2 detection, Tor/VPN/tunnel-service domain detection, DNS-leak test, live traffic stats. The real blocking happens in DnsVpnService; this class is the UI-facing façade whose static counters/log/callbacks DnsVpnService pushes to.

  • BLACKLISTED_IPS (:50): Tor exit-node prefixes + a few C2 ranges.
  • SUSPICIOUS_PORTS (:59): Metasploit/backdoor ports (4444, 5555, 31337, etc.).
  • SUSPICIOUS_DOMAIN_PATTERNS (:66): .onion, tor2web, i2p, ngrok.io, serveo.net, dyndns, no-ip, ddns.
  • public NetworkMonitor(Context context) (:144); public void setCallback(NetworkCallback callback) (:158).
  • public void startMonitoring() (:192) — registers ConnectivityManager callback, schedules DNS-leak test every 5 min.
  • public boolean checkConnection(String destIp, int destPort, String packageName) (:261) — blacklist-prefix → block; suspicious port → block; else allow.
  • public boolean isSuspiciousDomain(String domain) (:288) — hardcoded patterns first, then UrlThreatScanner.get(c).scanUrl("http://"+lower).
  • public List<NetworkEvent> getEventLog() (:348); public static List<NetworkEvent> getEventLogStatic() (:351); public static CopyOnWriteArrayList<NetworkEvent> getEventLogStaticRef() (:354).
  • public long getBytesReceived() / getBytesSent() (:357/:360) — lazy refreshStats cached 5 s.
  • public int getBlockedCount() / getAllowedCount() (:361/:362); public static void recordBlocked() / recordAllowed() (:110/:113).
  • public static void recordEvent(String destIp, int port, String protocol, boolean blocked, String reason) (:169) — the real DnsVpnService path; pushes to eventLog cap 1000, fires onSuspiciousActivity ONLY for blocked.
  • public void stopMonitoring() (:364).
  • public static class NetworkEvent (:115) — timestamp/sourceIp/destIp/destPort/protocol/blocked/reason/pid.
  • public interface NetworkCallback (:138) — onSuspiciousActivity/onStatsUpdate/onNetworkChange.

NetworkObservations.java (152 lines)

Live network activity observed by the DNS Web-Shield VPN, attributed per-app (via getConnectionOwnerUid → package), fed to the YARA-X hydradragon module as JSON when that app is scanned. Also carries per-app OCR'd screen text (from ScreenCaptureService). Bounded per-app (MAX_PER_APP=2048, MAX_APPS=1024, MAX_SCREEN_TEXT_CHARS=8192; oldest evicted).

All static synchronized:

  • void addDomain(String pkg, String domain) (:62); void addHost(String pkg, String host) (:63); void addUrl(String pkg, String url) (:64).
  • void addScreenText(String pkg, String text) (:69) — appends, trims oldest over cap.
  • String buildReportJson(String pkg) (:105) — JSON shape {"network":{"domains":[...],"hosts":[...]}, "urls":[...], "screen_text":"..."}, or "" if nothing observed / pkg null.

Called by DnsVpnService, ScreenCaptureService, ScanEngine.scanApk/analyzeApp (fed to native as the hydradragon report), DynamicRiskEngine.

MitmDetector.java (143 lines)

MITM / TLS-interception detector. Bundles legitimate Android system root CAs (from assets/cacerts/) as a baseline, compares against the device's AndroidCAStore. A user-installed CA, or a trusted root whose SHA-256 fingerprint is not in the baseline, indicates traffic can be intercepted. Does not decrypt anything — inspects the trust store only.

  • public static MitmDetector get(Context context) (:58) — DCL singleton.
  • public Result scan() (:93) — walks AndroidCAStore aliases; user: aliases → userInstalledCas + raises MITM verdict; system: alias not in baseline → unknownTrustedCas (info-only, does not raise verdict to avoid FPs from benign OEM/Google root updates).
  • public static final class Result (:45) — boolean mitmSuspected, List<String> userInstalledCas, List<String> unknownTrustedCas.

NetworkSecurityScanner.java (102 lines)

Wi-Fi / network security scanner — combines the MITM check with ARP-spoofing (on-path) detection for the current Wi-Fi.

  • public NetworkSecurityScanner(Context context) (:19).
  • public SecurityReport scanCurrentNetwork() (:33) — MITM first; then if no network → "No Network Connection" (secure); if mobile data → "Mobile Data Secure"; if Wi-Fi → checkArpSpoofing (:78, parses /proc/net/arp, flags one MAC mapped to two IPs). SSID intentionally not used.
  • public static class SecurityReport (:21) — boolean isSecure, String statusMessage, boolean isArpSpoofing.

CidrBlacklist.java (143 lines)

Direct (exact, non-probabilistic) CIDR blacklist matcher for resolved/contacted IPs, loaded from assets/CIDRBlackListIPv4.txt and CIDRBlackListIPv6.txt (sourced from github.com/T145/black-mirror). Grouped by prefix length into hash sets, so a test is a few hash lookups (not a scan of ~11k entries).

  • public static CidrBlacklist get(Context ctx) (:37) — DCL singleton; constructor loads both assets.
  • public boolean contains(InetAddress ip) (:89) — v4: ipv4ToInt & maskV4(prefix) set lookup; v6: hex-masked-key lookup.
  • public boolean contains(String ip) (:108).

Called by DnsVpnService (resolved-IP block check).

NetworkTrafficDB.java (109 lines)

Export/import of the NetworkMonitor event log to/from a JSON file (via ContentResolver Uri), for backup/restore.

  • public static void exportTraffic(Context context, Uri uri) (:25) — writes {"version":1,"exported_at":...,"events":[...]} from NetworkMonitor.getEventLogStatic().
  • public static int importTraffic(Context context, Uri uri) (:58) — reads JSON, reconstructs NetworkEvents, caps log at 1000. Returns count.

UrlThreatScanner.java (100 lines)

Thin Java wrapper over the native xor-filter URL/domain scanner (NativeScanner.scanUrl). The xor filters and public-suffix list live in native memory. Java side keeps URL extraction and a steamcommunity.com typosquat regex guard.

  • public static UrlThreatScanner get(Context context) (:35) — DCL singleton, no state.
  • public static List<String> extractUrls(String text) (:50) — regex https?://... over arbitrary text.
  • public String scanUrl(String url) (:65) — returns category e.g. "PHISHING_URL"/"STEAM_PHISHING" or null; runs isSteamFake first, then NativeScanner.scanUrl.
  • public static boolean isSteamFake(String url) (:74) — regex STEAM_FAKE :28; whitelists genuine steamcommunity.com/subdomains first.

Whitelist / config

TrustedPackages.java (44 lines)

Prefix-based + system-app whitelist of trusted packages (Google, OEMs, big vendors, system apps) so legitimate apps are never flagged by behavioral detection.

  • public static boolean isTrusted(Context c, String pkg) (:28) — true if pkg equals/starts with any PREFIXES entry (:14: com.google., com.android., com.whatsapp, com.facebook., com.miui., com.samsung., org.fdroid., etc.) OR is a FLAG_SYSTEM/FLAG_UPDATED_SYSTEM_APP app.

WebsiteWhitelist.java (109 lines)

User-maintained allowlist for the Web Shield: a domain, bare IP, or CIDR range the user said to never block. Small, user-edited → linear scan (no xor filter). Stored in hydra_prefs StringSet website_whitelist.

  • public static List<String> getAll(Context c) (:31); public static synchronized void add(Context, String) / remove(Context, String) (:35/:45).
  • public static boolean isDomainWhitelisted(Context c, String host) (:56) — exact or subdomain match; skips IP/CIDR entries.
  • public static boolean isIpWhitelisted(Context c, InetAddress ip) (:67) — bare-IP equality or cidrContains :91.

UserDecisions.java (66 lines)

Remembers the user's "no" answers so the app never nags twice: denied permissions/consents, threats marked safe (package or URL), dismissed redirects. SharedPreferences hydra_user_decisions, three StringSets (denied_perms, allowed_threats, dismissed_redirects).

  • Permissions: void denyPermission(Context, String) :50, boolean isPermissionDenied(Context, String) :51, void clearPermissionDenial(Context, String) :52.
  • Threats: void allowThreat(Context, String id) :55 (also clears BehaviorFlags for that id), boolean isThreatAllowed(Context, String) :60, void revokeThreatAllowance(Context, String) :61.
  • Redirects: void dismissRedirect(Context, String) :64, boolean isRedirectDismissed(Context, String) :65.

IgnoredSignatures.java (56 lines)

User-maintained list of detection/signature NAMES (e.g. "PUA.SomeAdware", "YARA-X.auto_2b891fca...", "ML") to suppress engine-wide — not one app (that's UserDecisions.allowThreat), but every future hit carrying that exact name on any app. Editable from Settings and from a completed-scan threat dialog's "ignore this signature" action.

  • public static boolean isIgnored(Context c, String signatureName) (:29) — lowercased.
  • public static synchronized void add(Context, String) (:34); public static synchronized void remove(Context, String) (:44); public static List<String> getAll(Context) (:53).

Called by ScanEngine.isDetectionWhitelisted (:577).

KnowledgeDatabase.java (126 lines)

Export/import of the persistent knowledge bases (currently the AntiFnCache SQLite DB) to/from a JSON file, for backup/restore.

  • public static void exportCache(Context context, Uri uri) (:27) — reads noBackupFilesDir/anti_fn_cache.db table malicious_tlsh into {"version":1,"exported_at":...,"anti_fn":[...]}.
  • public static int importCache(Context context, Uri uri) (:67) — reads JSON, (re)creates the table, INSERT OR IGNORE each row. Returns count.

Self-protection / integrity / environment checks

SelfProtection.java (62 lines)

Legitimate self-protection via the Device Admin API — while active, the app can't be uninstalled until the user deactivates the admin. Uses only the uninstall speed-bump; no lock/wipe powers. AdminReceiver is the DeviceAdminReceiver component.

  • public static ComponentName admin(Context c) (:31); public static boolean isActive(Context c) (:36); public static Intent activationIntent(Context c) (:45); public static void deactivate(Context c) (:56).

IntegrityCheck.java (60 lines)

Detects a repackaged/re-signed copy of THIS APK by comparing the running signing cert's SHA-256 against BuildConfig.EXPECTED_SIGNATURE_SHA256 (baked in at build time from the same keystore). A tampered copy is necessarily signed by a different key. Fails CLOSED to "not tampered" on any read error.

  • public static boolean isTampered(Context context) (:26) — GET_SIGNING_CERTIFICATES (API 28+) with a GET_SIGNATURES fallback for minSdk 26/27.

RootCheck.java (26 lines)

Detects whether the device is rooted (the app refuses to run on rooted devices and never scans/flags system files).

  • public static boolean isRooted() (:19) — checks SU_PATHS (:13: /system/bin/su, /system/xbin/su, /sbin/su, /data/adb/magisk, /data/adb/ksu, /data/adb/ap, Superuser.apk, etc.) for existence, plus Build.TAGS contains "test-keys".

DebugModeCheck.java (23 lines)

Detects whether USB/wireless debugging (ADB) is enabled. Unlike root (a hard block), this is a WARNING — ADB lets another app/PC push input/pull data/sideload, a real attack surface but not proof of compromise.

  • public static boolean isEnabled(Context context) (:15) — reads Settings.Global.ADB_ENABLED.

ProtectionState.java (24 lines)

Global on/off switch for real-time protection. When disabled, the antivirus is "paused": no alerts/redirects/block pages even if a threat is seen. Defaults ON.

  • public static boolean isEnabled(Context c) (:17); public static void setEnabled(Context c, boolean on) (:20). Pref protection_enabled.

DebugModeWarning.java (22 lines) / RootWarning.java (21 lines)

Whether MainActivity should warn the user when ADB is on / device is rooted. Both default ON; user can dismiss. Two-method pref toggles on hydra_prefs (debug_mode_warning_enabled / root_warning_enabled).


Auto-rule generation / scan-action settings

AutoRuleGeneration.java (25 lines)

Controls whether a confirmed-malware hit gets turned into a self-learned YARA-X.auto_* rule (ScanEngine.saveGeneratedRule). Not recommended (high FP risk: a rule from one sample's strings can later match clean apps). Default ON.

  • public static boolean isEnabled(Context c) (:18); public static void setEnabled(Context c, boolean on) (:22). Pref auto_rule_generation.

SaveAutoRules.java (28 lines)

Controls whether auto-generated YARA rules are persisted to generated_rules/ (survive restarts). When OFF, the rule is still generated in memory + hot-loaded for the current session but NOT written to disk. Default ON.

  • public static boolean isEnabled(Context c) (:22); public static void setEnabled(Context c, boolean on) (:26). Pref save_auto_rules.

AskSignatureOnRemove.java (26 lines)

Zero-Trust companion: when an UNKNOWN-verdict app is about to be uninstalled, ask whether to generate a signature from it first (its APK is still on disk pre-uninstall). Default OFF; only meaningful with Zero Trust Mode also on.

  • public static boolean isEnabled(Context c) (:19); public static void setEnabled(Context c, boolean on) (:23). Pref ask_signature_on_remove.

AutoDeleteMalware.java (27 lines)

When ON, a detected threat is removed the instant it's found (manual or background scan) instead of waiting for a user tap — an installed app still needs the system uninstall confirmation (auto-launched immediately), a standalone file is deleted outright. Default OFF.

  • public static boolean isEnabled(Context c) (:20); public static void setEnabled(Context c, boolean on) (:24). Pref auto_delete_malware_enabled.

UI helpers / scan-action toggles

ScanSchedule.java (64 lines)

User-configurable periodic-scan intervals + the wake-lock + periodic-scan-enabled toggles, consumed by GuardService.startPeriodicScans. Defaults: quick=30 min, full=180 min. MIN_INTERVAL_MIN=5.

  • boolean isPeriodicScanEnabled(Context) (:27); void setPeriodicScanEnabled(Context, boolean) (:31).
  • boolean isScanWakeLockEnabled(Context) (:39); void setScanWakeLockEnabled(Context, boolean) (:43).
  • int getQuickScanIntervalMinutes(Context) (:47); int getFullScanIntervalMinutes(Context) (:51); setters at :55/:60 (clamped to MIN_INTERVAL_MIN).

DetectionCategories.java (49 lines)

Per-category static-scan-engine toggles (Settings). Turning one off means a hit of that kind alone no longer counts toward a verdict (other enabled categories still do). All default ON. Category constants (all public static final String): SIGNATURES, ML, PUA, AUTO_RULES, PERMISSIONS, EICAR, URL_STRINGS, NATIVE_EMULATION, ROOTKIT (:18-38).

  • public static boolean isEnabled(Context c, String category) (:42); public static void setEnabled(Context c, String category, boolean on) (:46).

Called by ScanEngine (every verdict-category gate), NativeScanner.init (reads AUTO_RULES and NATIVE_EMULATION).

MaxScanFileSize.java (43 lines)

User-configurable ceiling on how large a file the native engine will scan. DEFAULT_MB=650, MIN_MB=10, MAX_MB=2048.

  • int getMaxMb(Context) (:23); void setMaxMb(Context, int) (:27, clamped); long getMaxBytes(Context) (:32); boolean isWithinLimit(Context, File) (:40).

ZeroTrustMode.java (31 lines)

Default-deny posture. When ON, an app that NO detector matched is reported SUSPICIOUS ("unverified", not "safe") with a full dump of everything the engine knows, instead of "clean". Not recommended (high FP). Default OFF.

  • public static boolean isEnabled(Context c) (:24); public static void setEnabled(Context c, boolean on) (:28). Pref zero_trust_mode.

Passed through to NativeScanner.scan (zero_trust param), which forces the native side to build a generated_rule even for a clean verdict.

FastScanMode.java (19 lines)

Toggle for a faster-scan mode. Default OFF. Pref fast_scan_mode_enabled. isEnabled/setEnabled.

BootAutoStart.java (24 lines)

Whether GuardService should silently start itself on device boot (via BootReceiver). Default ON. Pref boot_auto_start. isEnabled/setEnabled.


Services (service/)

DnsVpnService.java — VPN-based DNS/Web Shield

Local DNS-filtering VPN (extends VpnService) providing MITM-free web protection. Only DNS (UDP/TCP port 53, IPv4/IPv6) is routed into the tunnel; TLS and all other traffic go direct and are never decrypted. Each DNS query's hostname (plaintext in the DNS packet) is read, matched against blocklists, and malicious queries are sinkholed (NXDOMAIN) while clean ones are forwarded to upstream 1.1.1.1. Has an optional full-traffic capture mode (Suricata-style payload matching).

Lifecycle & startup. Started by MainActivity.onVpnReady() (MainActivity.java:453) after VpnService.prepare() consent, or by SettingsFragment.startShieldService(). onStartCommand (:138): handles ACTION_STOP (teardown + stopSelf, START_NOT_STICKY); reads EXTRA_FULL_CAPTURE; if already running returns START_NOT_STICKY; calls startForegroundShield(), optionally NativeScanner.enableVpnScan(true), loads CidrBlacklist.get(this), creates a fixed thread pool of 32 forwarders, calls establish(), and starts the dns-vpn worker thread. onDestroy (:792) and onRevoke (:800) both call teardown().

VPN tunnel setup (establish(), :194): Builder set session name, addAddress(TUN4="10.111.222.1", 32), addAddress(TUN6="fd00:0:1110:222::1", 128), addDnsServer(DNS4="10.111.222.2"), addDnsServer(DNS6). Full capture: addRoute("0.0.0.0", 0) + addRoute("::", 0) (all traffic). DNS-only: addRoute(DNS4, 32) + addRoute(DNS6, 128) (only DNS). addDisallowedApplication(getPackageName()) so the app's own traffic bypasses the tunnel. vpnInterface = b.establish().

DNS interception. loop() (:223) reads packets from vpnInterface.getFileDescriptor() into a 32767-byte buffer; handlePacket (:278) parses IP version, locates transport header, and in DNS-only mode filters on dstPort==53, dispatching handleUdpDns/handleTcpDns. parseQName (:618) reads the DNS question name labels; extractDnsIps (:416) pulls A/AAAA record IPs from the answer.

Blocklist matching. Domain/URL matching delegates to UrlThreatScanner.get(this).scanUrl("http://" + host) (:367, :503) — the actual xor-filter membership runs in native memory via NativeScanner.scanUrl. No xor filter is held in the Java heap. Resolved-IP sinkholing uses CidrBlacklist.get(this) (cidr.contains(ip)) against the plain CIDRBlackListIPv4/IPv6.txt assets. Both domain and IP whitelists are honored via WebsiteWhitelist before sinkholing.

Network events feeding the hydradragon module. For each query the owning app is resolved via ownerPackage (:591) using ConnectivityManager.getConnectionOwnerUid (API 29+) → getPackagesForUid(uid)[0]. The host and resolved IPs feed NetworkObservations.addDomain/addHost (:364, :394). DynamicRiskEngine.onDomainObserved is also notified. Every blocking decision calls NetworkMonitor.recordEvent (:762).

YARA network rules load on-demand. Full-capture mode toggles NativeScanner.enableVpnScan(true) at start (:151) and false at teardown (:177). NativeScanner.enableVpnScan lazy-loads emerging-all.yrc (13 MB, assets/scan/) via nativeEnableVpnScan. scanCapturedPackets() (:248) is throttled to every 5 s; it serializes up to 50 captured packets (getCapturedPacketsJson, each payload capped at 2048 bytes base64) and calls NativeScanner.scanPackets. A malicious result is recorded via NetworkMonitor.recordEvent.

Key public API.

  • public static final String EXTRA_FULL_CAPTURE = "full_capture" (:47).
  • public static final String ACTION_STOP = "com.hydradragon.antivirus.STOP_VPN" (:52).
  • public static void setFullCapture(boolean) / isFullCapture() (:104/:108).
  • public static synchronized String getCapturedPacketsJson() (:113).
  • public static class CapturedPacket (:80) — {srcIp, dstIp, srcPort, dstPort, protocol, payload, payloadB64}.

Foreground notification. startForegroundShield (:779) deliberately reuses GuardService.CHANNEL_ID ("hydradragon_guard") and NOTIFICATION_ID=1001 so the VPN foreground requirement does not post a second duplicate notification. teardown (:175) calls stopForeground(STOP_FOREGROUND_DETACH) (not remove) so GuardService's persistent notification survives.

TCP DNS state machine. handleTcpDns (:463) replies SYN-ACK (ISN=1000), parses the 2-byte length-prefixed DNS query, builds replies with buildTcp/frameTcpDns, sends FIN. Forwarding to upstream uses a Socket to 1.1.1.1:53.

GuardService.java — Persistent Foreground Guard

Long-running foreground Service (START_STICKY) that owns and drives the core scan/AI/network/process engines, runs periodic background scans, monitors Downloads (always) and full storage (opt-in) via MediaStore ContentObservers, and posts the persistent "System protected" notification. The central engine host every UI fragment binds to.

Lifecycle & startup. Started by MainActivity.onCreate (:183) if ProtectionState.isEnabled; by BootReceiver.onReceive on ACTION_BOOT_COMPLETED (when BootAutoStart.isEnabled); and by SettingsFragment toggles that restart it. onCreate (:293): createNotificationChannel(), startForeground(NOTIFICATION_ID, buildNotification("engine loading", true)) BEFORE heavy init (ANR guard), then spawns the guard-init thread running initializeEngines, sets engineLoading=false, updates notification, and starts startServiceMonitors + startPeriodicScans + startDownloadMonitor + startFullStorageMonitor. onStartCommand (:661): belt-and-suspenders startForeground; returns START_STICKY.

What it binds / drives. initializeEngines (:330) instantiates AIEngine, ScanEngine (foreground/user scans), a SECOND ScanEngine backgroundScanEngine (own scanRunning, so user scans are never blocked by background scans), NetworkMonitor, ProcessDetector; sets background priority via ScanEngine.setBackgroundPriority; wires permanent ScanCallbacks on both engines and a NetworkCallback/ProcessCallback. networkMonitor.startMonitoring() at the end. Callbacks forward to uiScanCallback (set by ScanFragment) and callback (GuardCallback, set by DashboardFragment).

Engines exposed (getters, :650).

  • public boolean isEngineLoading().
  • public void setCallback(GuardCallback cb) (:651) — DashboardFragment sets this.
  • public void setUiScanCallback(ScanEngine.ScanCallback cb) (:280) — ScanFragment registers UI updates here, NOT on scanEngine directly.
  • public ScanEngine getScanEngine() — used by ScanFragment for scanAllApps, scanCustomFolder, scanSingleFile, pause/resume/cancel, generateRuleForApp.
  • public AIEngine getAiEngine(), public NetworkMonitor getNetworkMonitor(), public ProcessDetector getProcessDetector().

Public constants/state.

  • public static final String KEY_REALTIME_STORAGE_WATCH = "realtime_storage_watch" (:34).
  • public static java.util.Set<String> unlockedApps (:233).
  • static final String CHANNEL_ID = "hydradragon_guard" (:245), static final int NOTIFICATION_ID = 1001 (:246).
  • public interface GuardCallback (:282) — onThreatDetected(ThreatResult), onSuspiciousProcess(ProcessInfo), onNetworkAlert(NetworkMonitor.NetworkEvent), onStatusUpdate(String).
  • public class GuardBinder extends Binder (:289) — public GuardService getService().

Foreground notification. buildNotification(String text, boolean secure) (:616) — ongoing, PRIORITY_LOW, content intent to MainActivity, shield icon green/red. Channel IMPORTANCE_LOW (createNotificationChannel :638). Threat/network/process/root-exploit alerts use PRIORITY_HIGH/MAX on the same channel with incrementing alertNotificationId from ALERT_NOTIFICATION_BASE=2000 (:247).

Periodic scans. startPeriodicScans (:474): if ScanSchedule.isPeriodicScanEnabled, schedules scheduleAtFixedRate quick (getQuickScanIntervalMinutes) and full (getFullScanIntervalMinutes) scans on backgroundScanEngine.scanAllApps(false/true). startServiceMonitors (:498): processDetector.scanRunningProcesses() every 60 s and checkRootTransition() every 60 s.

Root-exploit transition detection. checkRootTransition (:508): if BehaviorDetectionSettings.ROOT_EXPLOIT enabled, calls RootCheck.isRooted(), sets HipsMonitor.setRooted; on false→true transition, attributes foreground package via DynamicAnalysisService.getForegroundPackage(), fires sendRootExploitAlert, logs to ThreatLogger, and if the suspect isn't trusted/allowed, BehaviorFlags.flag + BehaviorResponse.killAndPromptUninstall.

Downloads monitoring. startDownloadMonitor (:45) registers a ContentObserver on MediaStore.Downloads.EXTERNAL_CONTENT_URI. scanMediaStoreDownloads (:69) queries for files modified since last poll and calls scanDownloadedFile. startFullStorageMonitor (:112) is gated on KEY_REALTIME_STORAGE_WATCH and observes MediaStore.Files. scanDownloadedFile (:180) runs scanEngine.scanSingleFile(file) via ScanEngine.runOrchestrated; on a hit logs to ThreatLogger, invokes callback.onThreatDetected, posts a notification with Remove/Ignore actions targeting UserActionReceiver.ACTION_REMOVE_FILE/ACTION_IGNORE (never auto-deletes).

DynamicAnalysisService.java — Behavioral Analysis Host (AccessibilityService)

Extends AccessibilityService. Hosts runtime behavioral detection: clickjacking/auto-permission-grant detection, UI spam & notification-spam adware detection, on-screen ransomware/phishing/smishing/fake-virus-text scanning, URL scanning inside browsers, and device-admin/uninstall-screen detection for removal-resistance. Tracks the foreground package for ScreenCaptureService attribution.

Lifecycle. onServiceConnected (:86) logs + creates the hydradragon_dynamic_alert notification channel. onAccessibilityEvent (:93) is the main entry — gated on ProtectionState.isEnabled, skips own package and trusted packages. Started by the user enabling the accessibility service in system Settings (not a startService call — the OS binds it).

What it drives. HipsMonitor.reportClickjack/reportUiSpam/reportNotificationSpam; RemovalResistanceGuard.onWindowSwitch/confirmSensitiveScreenText; RansomwareBehaviorGuard.onForegroundPermissionCheck; ScreenThreatKeywords.containsAtLeast/containsAny; UrlThreatScanner.get().scanUrl + extractUrls; BehaviorFlags.flag; BehaviorResponse.killAndPromptUninstall; UserDecisions.isThreatAllowed/isRedirectDismissed; TrustedPackages.isTrusted. Node walking is offloaded to a single-thread nodeWalkExecutor with an AtomicBoolean nodeWalkBusy guard and MAX_NODES_PER_WALK=500 cap (:34, :203).

Key public API.

  • public static String getForegroundPackage() (:67) — returns static sForegroundPackage; used by ScreenCaptureService.onRecognizedText, GuardService.checkRootTransition, StrandHoggGuard.isSuspectForeignForeground.

Detection logic highlights.

  • Clickjacking: ≥3 rapid clicks (<300 ms apart) on installer/settings screens → HipsMonitor.reportClickjack, GLOBAL_ACTION_BACK, redirectIfNotDismissed (:142).
  • UI spam: checkSpamBehavior (:337), SPAM_THRESHOLD=30 within SPAM_WINDOW_MS=8000, restricted to TYPE_VIEW_CLICKED/TYPE_WINDOW_STATE_CHANGED.
  • Notification spam: checkNotificationSpam (:382), NOTIF_THRESHOLD=20 within NOTIF_WINDOW_MS=10000.
  • Screen-text: checkNodesForSuspiciousKeywords (:223) matches ScreenThreatKeywords.RANSOMWARE (≥2), DEVICE_ADMIN, UNINSTALL, SMS_PHISHING (≥2), FAKE_VIRUS_WARNING (≥2); URL scan only in browsers (BROWSERS set :76) or already-flagged apps.
  • Alerts: sendAlert(title, message, threatId) (:422) — single replacing notification (ALERT_NOTIF_ID=0xA1E7), 5 s anti-spam, "Safe (ignore)" action → UserActionReceiver.ACTION_IGNORE, opens MainActivity with alert extras, logs to ThreatLogger. showBlockPageIfNotDismissed (:500) launches BlockActivity. redirectIfNotDismissed/redirectToApp (:519) bring MainActivity to foreground instead of home.

ScreenCaptureService.java — Screen Threat Detection (MediaProjection OCR)

Foreground Service (type mediaProjection) that periodically captures the screen via MediaProjection, OCRs each frame on-device with ML Kit Latin text recognition, then (1) feeds recognized text into NetworkObservations.addScreenText attributed to the current foreground app (so the next APK scan carries it as hydradragon.screen_text), and (2) immediately runs NativeScanner.scanText to catch scam/ransomware/phishing text rendered right now without waiting for a scan.

Lifecycle. Started by SettingsFragment.onActivityResult (REQ_SCREEN_CAPTURE) with extras EXTRA_RESULT_CODE + EXTRA_RESULT_DATA. onStartCommand (:83): validates consent extras exist (Android 14+ requires fresh consent at startForeground); creates channel, startForeground(NOTIF_ID=0x5C4E, buildNotification()) BEFORE getMediaProjection; creates TextRecognizer; starts HandlerThread "ScreenOCR"; registers a MediaProjection.Callback; calls startCapture(); returns START_NOT_STICKY (token invalidated on process death). onDestroy (:277): running=false, releases virtualDisplay/imageReader/projection.

What it drives. DynamicAnalysisService.getForegroundPackage() for attribution; NetworkObservations.addScreenText(pkg, text); NativeScanner.scanText(text). On hits → sendAlert(pkg, hits) (:229), notification id 0xA1EA on channel hydradragon_screen_ocr.

Key public API / constants.

  • public static final String EXTRA_RESULT_CODE = "result_code" / EXTRA_RESULT_DATA = "result_data" (:68/:69).
  • Capture interval CAPTURE_INTERVAL_MS=4000 (:66). captureLoop (:182) paces capture; processLatestFrame (:192) OCRs with a 10 s timeout; imageToBitmap (:243) handles RGBA_8888 row-stride padding.

AdminReceiver.java — Device Admin (Self-Protection)

Extends DeviceAdminReceiver. Legitimate self-protection speed bump: while active, Android won't let the app be uninstalled until the user explicitly deactivates the admin. No lock/wipe/password powers (per res/xml/device_admin.xml).

  • public void onEnabled(Context, Intent) (:21); public CharSequence onDisableRequested(Context, Intent) (:26) — returns a Turkish-language warning; public void onDisabled(Context, Intent) (:33).

Started by SelfProtection.activationIntent launched from MainActivity and SettingsFragment.

InstallReceiver.java — On-Install Scan

BroadcastReceiver that scans a newly installed or replaced package on the spot via a fresh ScanEngine.analyzeSingleApp, dropping the cached result first.

Intents: Intent.ACTION_PACKAGE_ADDED and ACTION_PACKAGE_REPLACED (:23) with data URI package:<name>.

onReceive (:21): extracts packageName from intent.getData().getEncodedSchemeSpecificPart(); ScanEngine.invalidateCache(packageName); ScanEngine.runOrchestrated(() -> ...) retries pm.getApplicationInfo up to 5 times with backoff, builds a fresh AIEngine+ScanEngine, calls engine.analyzeSingleApp(appInfo, pm, false). On result.isThreat(), posts a PRIORITY_MAX notification on channel hydradragon_dynamic_alert with a "Remove" action opening the system uninstall dialog.

UninstallReceiver.java — On-Uninstall Cleanup

Intents: Intent.ACTION_PACKAGE_REMOVED (:15), only when EXTRA_REPLACING is false.

onReceive (:14): purges all remembered state for the package — ScanEngine.invalidateCache(packageName), BehaviorFlags.clear(context, packageName), UserDecisions.revokeThreatAllowance(context, packageName), ThreatLogger.logThreat(..., "THREAT REMOVED (SAFE)").

SmsReceiver.java — SMS Scam/Phishing Detection

Real-time SMS scanning. Every incoming text is checked for malicious links (UrlThreatScanner.scanUrl) and scam/phishing/fake-AV/ransomware wording (same ScreenThreatKeywords lists used for on-screen text). Never aborts the broadcast — the Messages app still receives the SMS; this only raises an alert.

Intents: Telephony.Sms.Intents.SMS_RECEIVED_ACTION (:37), gated on ProtectionState.isEnabled. RECEIVE_SMS permission is opt-in from Settings, never requested at launch.

onReceive (:36): assembles message body + sender from getMessagesFromIntent; uses goAsync() + a background thread so the native scanUrl JNI call can't ANR the main thread. scanSmsBody (:74): checks URLs via UrlThreatScanner.extractUrls + scanner.scanUrl; if no URL hit, checks ScreenThreatKeywords.containsAtLeast for SMS_PHISHING/FAKE_VIRUS_WARNING/RANSOMWARE (≥2 distinct lures; ransomware unambiguous). Respects UserDecisions.isThreatAllowed(context, "smsvirus:"+from). Alert via sendAlert (:105) on channel hydradragon_sms_alert, notification id 0xA1E8.

UserActionReceiver.java — Alert Action Handler

Handles the user's actions on threat alerts: allowlist, dismiss redirect, delete a malicious file. Actions (constants :22-26):

  • ACTION_IGNORE = "com.hydradragon.antivirus.IGNORE_THREAT"UserDecisions.allowThreat(context, id) + Toast.
  • ACTION_DISMISS = "com.hydradragon.antivirus.DISMISS_REDIRECT"UserDecisions.dismissRedirect(context, id).
  • ACTION_REMOVE_FILE = "com.hydradragon.antivirus.REMOVE_FILE"new File(id).delete() + Toast.
  • EXTRA_ID = "threat_id", EXTRA_NOTIF = "notif_id".

onReceive (:29) dispatches on action, then cancels the notification via nm.cancel(notifId). Called via PendingIntent.getBroadcast from GuardService.scanDownloadedFile and DynamicAnalysisService.sendAlert.

ThreatLogger.java — Threat History (SharedPreferences)

Static helpers that persist the human-readable threat history log to a SharedPreferences store (HydraDragon_ThreatLogs, key logs), newest-first, plus export/import via SAF.

  • public static void logThreat(Context, String packageName, String appName, String action) (:20).
  • public static void logThreat(Context, ThreatResult threat, String action) (:35) — richer variant with type/level/risk/reasons/dangerous permissions.
  • public static String getLogs(Context) (:61); public static void exportLogs(Context, Uri) (:66); public static void importLogs(Context, Uri) (:75).

Receivers (root + service) — intent summary

Receiver Intents listened for What it does
BootReceiver (root) ACTION_BOOT_COMPLETED If BootAutoStart.isEnabled: pre-warms NativeScanner.init, then startForegroundService(GuardService).
AdminReceiver DeviceAdminReceiver callbacks Self-protection speed bump; no lock/wipe powers.
InstallReceiver ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REPLACED (data package:) On-install scan via fresh ScanEngine.analyzeSingleApp; notifies with uninstall action.
UninstallReceiver ACTION_PACKAGE_REMOVED (not replacing) Purges cache/flags/allowlist; logs "THREAT REMOVED (SAFE)".
SmsReceiver Telephony.Sms.Intents.SMS_RECEIVED_ACTION Native URL scan + ScreenThreatKeywords check; never aborts broadcast; alert + log.
UserActionReceiver IGNORE_THREAT, DISMISS_REDIRECT, REMOVE_FILE (explicit) Allowlist / dismiss / delete-file; cancels the source notification.

UI fragments & activities (ui/)

MainActivity.java — App Entry

Launcher AppCompatActivity — app entry, security-hardening bootstrapping, permission bootstrapping, and the bottom-nav host for the five fragments (Dashboard, Scan, Network, Threats, Settings).

Permission bootstrapping sequence (checkMandatoryPermissions, :281): (1) ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION — MANDATORY (Android R+), blocks; (2) POST_NOTIFICATIONS — optional, once; (3) Accessibility service — optional, once, checked via isAccessibilityServiceEnabled() (:595); (4) Google Play Protect — optional, checked via isPlayProtectEnabled() reading Settings.Global "package_verifier_user_consent" (:558); (5) Self-protection / Device Admin — optional, once; (6) Draw Over Other Apps (overlay) — optional, once, Settings.canDrawOverlays; (7) Web Shield (VPN) — optional, once; (8) startAppUI() (:520).

Service startup sequence (onCreate, :54): reads/migrates theme pref; instantiates SecureWindowGuard and applies FLAG_SECURE BEFORE super.onCreate/setContentView (:69); AppLifecycleTracker.register (:84); NativeScanner.init(this) (:88); instantiates StrandHoggGuard (:89); integrity check IntegrityCheck.isTampered → blocking dialog + finish() (:94); root check + warn dialog (:108); setContentView; debug-mode warning (:137); bottom nav listener (:155); auto-starts GuardService if ProtectionState.isEnabled (:183); checkMandatoryPermissions() (:188).

Other key methods. onResume (:192) starts StrandHoggGuard/SecureWindowGuard watchers; onPause (:208) stops them; startActivity/startActivityForResult overrides (:228) call stopSecurityWatchers() first to close a navigation race that produced false task-hijack positives; onActivityResult (:464): REQ_VPNonVpnReady (:452); onTaskHijackDetected/onSecureFlagLost (:266/:275) → Toast + finishAndRemoveTask().

Request codes: REQ_VPN=102, REQ_OVERLAY=103 (:37).

DashboardFragment.java

Cyberpunk dashboard: HexagonStatusView security status (secure/loading/alert), LiveNetworkChart, total-traffic/blocked/allowed counters, live threat feed, engine-status line. Binds to GuardService in onStart (:246); registers a GuardService.GuardCallback (:70); polls getNetworkMonitor().getBlockedCount()/getAllowedCount() and isEngineLoading() every 2 s; runs NetworkSecurityScanner.scanCurrentNetwork() for ARP-spoof status (:178). Unbinds in onStop.

ScanFragment.java

Scan controls (start/pause-resume/stop), progress bar + current app, scanned/threat/active-threat counts, engine-loading warning, scanner icon rotation animation, two RecyclerViews toggled by View Threats / View All Files (ThreatAdapter, ScannedFileAdapter). Binds to GuardService (:846); uses guardService.getScanEngine() for scanAllApps (:567), scanCustomFolder (:973), scanSingleFile (:1050), pause/resume/cancel, generateRuleForApp (:329). Registers UI updates via guardService.setUiScanCallback(...) (:614) — deliberately not scanEngine.setCallback. Static fields survive tab switches. Custom file/folder pickers via SAF; uriTreeToFile/uriToRealFile/resolveDocumentId resolve SAF tree URIs to real paths (:883). destroyThreat (:296) distinguishes standalone files vs installed apps, with Zero-Trust ask-signature detour; uninstallInstalledThreat (:383) handles device-admin-protected malware. onResume confirms uninstall actually went through (:457).

NetworkFragment.java

Live network chart (LiveNetworkChart), bytes in/out, blocked/allowed counts, network type, a RecyclerView of network events (NetworkEventAdapter). Export/import traffic-log JSON buttons. Binds to GuardService (:197); installs a NetworkMonitor.NetworkCallback (:136); polls stats every 2 s. Export via NetworkTrafficDB.exportTraffic; import via NetworkTrafficDB.importTraffic.

ThreatLogFragment.java

Threat history log (monospace cyan on black, in a ScrollView), with EXPORT .TXT / IMPORT .TXT buttons. Pure read of ThreatLogger.getLogs in refreshLogs (:94); export/import via ThreatLogger. No service binding.

SettingsFragment.java

Entire settings screen built programmatically in buildUI() (:134), rebuilt on onResume (:125). Sections: Language, Appearance, Protection, Detection Categories, Behavior Detection, Web Shield, Premium Features, Whitelists, Knowledge DB, System, About.

Key service interactions: realtime protection toggle → GuardService start/stop (:157); periodic scan / storage watch / AUTO_RULES toggles → restart GuardService (:207); Web Shield toggle → enableWebShield/disableWebShield driving DnsVpnService (:266, :654); Screen OCR toggle → requestScreenCapture/stopScreenCapture driving ScreenCaptureService (:365, :718); SMS toggle → requestPermissions(RECEIVE_SMS) (:373); Self-protection toggle → SelfProtection.activationIntent/deactivate (:197); native engine direct: NativeScanner.setEmulationEnabled (:251), setDetectZipBomb (:326), setScanRelevantOnly (:362), setTlshThreshold (:1094); whitelists/managers: IgnoredSignatures, WebsiteWhitelist, auto-rules manager reading getFilesDir()/hydra-scan/generated_rules/*.yar (:1177); knowledge DB via KnowledgeDatabase.exportCache/importCache (:86); bloatware: new CleanupEngine(ctx).scan(Callback) (:591); reset: ResetCategory matrix removes pref keys then restarts GuardService (:513).

Anti-tapjacking. applyObscuredTouchWarning (:806) + guardedToggleListener rate-limiter (RECENT_TOGGLE_WINDOW_MS=1200, RECENT_TOGGLE_BURST=3, :62, :843) reverts a burst of toggles.

BlockActivity.java

Full-screen HTML "site blocked" page shown instead of a malicious website, rendered as a local WebView (nothing from the malicious site loaded). Extras EXTRA_URL (:18), EXTRA_CAT (:19). onCreate (:22) enables JS, adds a JavascriptInterface "Android" with close()finish(), loads buildHtml via loadDataWithBaseURL(null, ...). Launched by DynamicAnalysisService.showBlockPageIfNotDismissed.

MalwareFoundActivity.java

Full-screen HTML "malware found" warning page (same local-WebView pattern as BlockActivity) launched by BehaviorResponse.killAndPromptUninstall the instant a scan finds something past ThreatResult.isThreat(), on top of whatever the user is doing. Extras: EXTRA_APP_NAME, EXTRA_RISK_SCORE, EXTRA_REASON, EXTRA_IS_FILE, EXTRA_PACKAGE_NAME, EXTRA_APK_PATH (:24). onCreate (:31) builds the WebView + JavascriptInterface with close() and uninstall() — the latter deletes the file (isFile) or fires Intent.ACTION_DELETE / package:<pkg>.


Views (views/)

HexagonStatusView.java

Custom View drawing a cyberpunk hexagon security-status indicator (secure=green checkmark, loading=yellow exclamation, alert=red X) with pulse animation, inner hex, corner glints.

  • public void setSecureState(boolean secure) (:67); public void setLoadingState() (:77); public void startPulseAnimation() (:86) — infinite ValueAnimator (2 s). onDetachedFromWindow (:204) cancels the animator. Called by DashboardFragment.

LiveNetworkChart.java

Custom View drawing a live cyan network-activity wave (bezier-smoothed line + gradient fill + glow + peak dot + grid).

  • public void addDataPoint(float value) (:68) — pushes a point onto the ArrayDeque (max MAX_POINTS=60), auto-scales maxValue, invalidates. onDraw (:76) draws grid, fill shader, bezier line path, glow, peak dot. Called by DashboardFragment and NetworkFragment.

Adapters (adapter/)

NetworkEventAdapter.java

RecyclerView.Adapter for NetworkMonitor.NetworkEvent rows: time, connection (ip:port [proto]), action (blocked=red/allowed=green), reason; row tap opens a detail AlertDialog. public NetworkEventAdapter(List<NetworkMonitor.NetworkEvent> events) (:26).

ScannedFileAdapter.java

RecyclerView.Adapter for ScannedFileInfo rows: name, status (threat=red/clean=green), risk score. public ScannedFileAdapter(List<ScannedFileInfo> files) (:23).

ThreatAdapter.java

RecyclerView.Adapter for ThreatResult rows: app name, package, risk score, time, reasons (Linkify'd for VirusTotal URLs), threat-level color coding (critical≥80 red, medium≥40 orange, low yellow), dynamically loaded app icon. public ThreatAdapter(List<ThreatResult> threats) (:34); public interface OnThreatClickListener (:29); public void setOnThreatClickListener(OnThreatClickListener listener) (:38).


Models (model/)

ProcessInfo.java

Immutable process descriptor built via Builder. Fields: pid, processName, appName, memoryMb, riskScore, flags (List), isSystemProcess, importance.

  • public boolean isSuspicious() (:28) → riskScore >= 30; public boolean isCritical() (:29) → riskScore >= 70. Produced by ProcessDetector; consumed by GuardService (critical → sendProcessAlert + callback.onSuspiciousProcess).

ScannedFileInfo.java

Immutable per-file scan record. Fields: filePath, packageName, appName, riskScore, md5, timestamp, threat (boolean), verdictReason. public boolean isThreat() (:32). Emitted by ScanEngine.ScanCallback.onFileScanned; consumed by ScannedFileAdapter and ScanFragment.scannedFiles.

ScanResult.java

Immutable aggregate scan result. Fields: totalScanned, threatsFound, threats (List), scannedFiles (List, null-safe to empty), scanDurationMs. public boolean isClean() (:27) → threatsFound == 0. Emitted by ScanEngine.ScanCallback.onScanComplete; consumed by ScanFragment.

ThreatResult.java

Immutable threat detection result (built via Builder). ThreatType enum: CLEAN, UNKNOWN (Zero Trust — no detector matched), SUSPICIOUS, MALWARE, SPYWARE, RANSOMWARE, ADWARE, TROJAN, BACKDOOR, PHISHING, PUA, TEST_MALWARE (EICAR). Fields: packageName, appName, apkPath, riskScore (0-100, clamped), threatType, reasons (List), dangerousPermissions (List), timestamp, standaloneFile.

  • public boolean isThreat() (:51) → riskScore >= 30; public boolean isCritical() (:52) → riskScore >= 70.
  • public String getThreatLevel() (:61) → CRITICAL/HIGH/MEDIUM/SAFE; getThreatLevelColor() (:68) → hex color; getThreatLevelResId() (:55).
  • public boolean isStandaloneFile() (:87) — true for a loose file vs an installed app (drives Delete vs Uninstall).
  • equals/hashCode keyed on packageName (:89).

Produced by ScanEngine.analyzeSingleApp/scanSingleFile; consumed everywhere (adapters, fragments, GuardService, receivers, ThreatLogger).


Security (security/)

SecureWindowGuard.java

Applies Guardsquare-recommended defenses for a sensitive screen: FLAG_SECURE (blocks screenshots/recording/mirroring), "Show taps" developer-option detection (FLAG_SECURE doesn't hide the system tap indicator overlay), and a periodic FLAG_SECURE self-check that catches runtime instrumentation (e.g. a Frida hook no-op'ing addFlags) by reading the ACTUAL live window attributes back.

  • public SecureWindowGuard(Activity activity) (:51); public void applyFlagSecure() (:56); public void removeFlagSecure() (:60).
  • public static boolean isShowTapsEnabled(Context) (:67) — reads Settings.System "show_touches".
  • public interface OnSecureFlagLost { void onSecureFlagLost(); } (:40).
  • public void startWatching(OnSecureFlagLost cb, long intervalMs) (:78) / public void stopWatching() (:85). Self-check checkSecureFlagStillSet (:90) honors the user's disable_secure_flag pref (intentional removal → stop polling).

Used by MainActivity: instantiated in onCreate (:74), applyFlagSecure() (:77), startWatching(this::onSecureFlagLost, 2000) in onResume (:198), stopWatching in onPause/before navigation. onSecureFlagLost (:275) Toasts + finishAndRemoveTask().

StrandHoggGuard.java

Detects task hijacking (StrandHogg): a malicious app inserting one of its own activities into HydraDragon's task back stack to overlay a fake prompt. Compares ActivityManager.TaskInfo.numActivities (real count, API 29+) against AppLifecycleTracker.getExpectedActivityCount() (only what HydraDragon created); if real > expected AND the current foreground is an untrusted third party, fail closed.

  • public StrandHoggGuard(Activity activity) (:43); public interface OnHijackDetected { void onHijackDetected(int expected, int actual); } (:35).
  • public void startWatching(OnHijackDetected callback, long intervalMs) (:47) / public void stopWatching() (:59).
  • private void checkOnce(OnHijackDetected) (:64) — no-op below API 29; skips screen-pinning/lock-task mode.
  • private boolean isSuspectForeignForeground() (:100) — uses DynamicAnalysisService.getForegroundPackage(); not a hijack if foreground is us or a TrustedPackages.isTrusted package.

Used by MainActivity: instantiated in onCreate (:89), startWatching(this::onTaskHijackDetected, 1500) in onResume (:195), stopWatching in onPause/before navigation. onTaskHijackDetected (:266) Toasts + finishAndRemoveTask().


App entry (root)

HydraDragonApp.java

Application subclass — the process entry point. Applies the persisted theme preference (hydra_prefs / theme_mode "dark"|"light"|"system", migrating from the old boolean dark_mode) via AppCompatDelegate.setDefaultNightMode BEFORE any Activity is created, then kicks off native engine init. public void onCreate() (:12): reads/migrates theme pref, sets night mode, super.onCreate(), then NativeScanner.init(this) (the ~70 s ClamAV/YARA load starts here in a background thread).

BootReceiver.java

public void onReceive(Context, Intent) (:10) — on ACTION_BOOT_COMPLETED, if BootAutoStart.isEnabled: pre-warms NativeScanner.init, then startForegroundService(GuardService) (O+) / startService.


Cross-cutting notes

  • Preferences: almost every config class uses hydra_prefs (MODE_PRIVATE); exceptions are BehaviorFlags (hydra_behavior_flags), UserDecisions (hydra_user_decisions), and FileCanaryGuard (hydra_canary).
  • Two ScanEngine instances in GuardService: scanEngine (foreground/user scans, UI-forwarded via uiScanCallback) and backgroundScanEngine (own scanRunning, permanent callback that logs + auto-responds even with no UI attached). ScanFragment MUST register via setUiScanCallback, never scanEngine.setCallback.
  • Notification-channel sharing: DnsVpnService deliberately reuses GuardService.CHANNEL_ID + NOTIFICATION_ID=1001 and STOP_FOREGROUND_DETACH so the Web Shield never shows a duplicate "System protected" notification.
  • hydradragon YARA-X module data flow: DnsVpnService (DNS queries + resolved IPs) and ScreenCaptureService (OCR'd screen text) both feed NetworkObservations per-package buckets; NetworkObservations.buildReportJson(pkg) is handed to the native engine at that app's next scan so hydradragon.network.dns_lookup(/re/), hydradragon.url(/re/), hydradragon.screen_text(/re/) rules match observed live behavior.
  • On-demand YARA network rules: emerging-all.yrc (13 MB, assets/scan/) is lazy-loaded by NativeScanner.enableVpnScan(true) (called only when DnsVpnService enters full-capture mode); packet scanning is throttled to 5 s and run via NativeScanner.scanPackets(json).
  • Xor filters (.xf) are native-only: domain/URL/IP membership lives entirely in Rust memory via NativeScanner.scanUrl / the native IP xor filters; the Java UrlThreatScanner is a thin wrapper plus the steamcommunity typosquat guard. Resolved-IP CIDR sinkholing uses the separate Java CidrBlacklist (exact hash-set match, no xor filter).
  • Java-side ML thresholds: ML flag counts as malicious only if DetectionCategories.ML enabled AND jaccard >= 0.55 AND anomaly >= 0.33. Permissions: >=30 → MALWARE (100), >=25 → SUSPICIOUS (40). Auto-rule-only hit → SUSPICIOUS (30). EICAR → TEST_MALWARE (50). PUA-only → PUA (50). Real signature/ML/permissions → MALWARE (100).
  • Native-side detection naming conventions (ScanEngine helpers): isPuaName = contains PUA./PUA_; isAutoGeneratedName = starts with YARA-X.auto_/YARA.auto_; isEicarName = contains eicar; isDexHeuristicName = starts with DEX/ but not DEX/Critical (dropped as too FP-prone; only DEX/Critical survives).

Clone this wiki locally