-
Notifications
You must be signed in to change notification settings - Fork 1
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.
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 SQLitewhitelist_packages.dbasset and callsNativeScanner.init(context). - Holds
AIEngine aiEngine,CodeAnalyzer codeAnalyzer,AntiFnCache antiFnCache(:96-98).
Major responsibilities:
-
Driving the scan —
scanAllApps/scanCustomFoldersubmit work to a static boundedscanExecutor(newFixedThreadPool(NATIVE_PARALLELISM),NATIVE_PARALLELISM = max(1, min(2, cores/2))at:109-119). AscanRunningAtomicBoolean 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). -
Calling into NativeScanner (JNI) — every native call goes through
runNativeInterruptible(Callable<NativeScanner.Verdict>)(:325): submits tonativeCallExecutor, pollscancelRequestedevery 150 ms, returnsnullon cancel (abandoning the in-flight JNI call rather than killing the thread).PackageManagercalls go throughrunPkgInfoInterruptible(Callable<PackageInfo>)(:306) with a 5 s timeout that flipsapkPkgAnalyzerBrokenon Vivo ROM deadlocks. -
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 inIgnoredSignatures. Anti-FN up-sell via TLSH similarity inupsellAntiFnDetections(:637) and cache update viaupdateAntiFnCache(:671). Verdict categories are gated byDetectionCategories.isEnabled: real malware (risk 100, MALWARE), EICAR (50, TEST_MALWARE), PUA (50, PUA), auto-rule only (30, SUSPICIOUS), ML (only ifjaccard>=0.55 && anomaly>=0.33), dangerous permissions (>=30MALWARE,>=25SUSPICIOUS). -
Whitelist loading —
loadPackageWhitelist()(:492) copiesassets/scan/whitelist_packages.dbtonoBackupFilesDirand loads thewhitelist_package.keycolumn intowhitelistPackagesHashSet. Auto-clear requires BOTHisFromStore(trusted installer: Vending/Samsung/Xiaomi/Huawei/HeyTap/Oppo market,:1628) ANDisPackageWhitelisted(:1646). Hash whitelist is native (xor filter) viaNativeScanner.isHashWhitelisted/isHashWhitelistedForFile(:523). -
Signature DB management — deferred to native; Java only manages the auto-generated rules dir.
saveGeneratedRule(Verdict)(:600) writes the yarGen-style rule tofilesDir/hydra-scan/generated_rules/auto_<md5>.yar(gated bySaveAutoRules.isEnabled) and hot-loads it viaNativeScanner.learnRuleso the current session benefits.generateRuleForApp(String apkPath, String packageName)(:364) is the manual, user-requested, non-gated variant (forceszero_trust=true). -
Scheduling — no internal scheduler; periodic scans are driven by
GuardServiceusingScanScheduleintervals. ScanEngine exposessetBackgroundScan(boolean)(:236),setBackgroundPriority(boolean)(:117), pause/resume/cancel:cancelScan()(:261),pauseScan()(:273),resumeScan()(:278),isCancelled()(:283),isPaused()(:286). -
Relationship to ScanFragment — ScanFragment registers a
ScanCallback(setCallback:693, interface at:383withonProgress/onThreatFound/onScanComplete/onError/onFileScanned).scanAllApps(boolean isFullScan)(:746) returnstrueiff it actually started a scan (race-free contract ScanFragment must check).scanCustomFolder(File)(:881) andscanSingleFile(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.
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. MethodsisError():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).
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 ifaiScore>50 && activeFeatures>=3, else clean. Classifies athreatType(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).
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) — returnsAnalysisResult(riskScore, findings, threatType);isMalicious = riskScore>=60. Feeds 8 boolean feature flags intoAIEngine.predictMalwareProbability. -
public AnalysisResult analyzeInstalledApp(String packageName)(:153) — resolvessourceDirthenanalyzeApk. -
public static class AnalysisResult(:39) —riskScore,findings,isMalicious,threatType.
Called by ScanEngine.analyzeApp (:1778).
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/createsnoBackupFilesDir/anti_fn_cache.db, tablemalicious_tlsh(tlsh TEXT PK, detection_name TEXT, added_at INTEGER).enabled= prefanti_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, callsNativeScanner.tlshDiff(tlsh, cached)for each; returns the first detection name withinthreshold, else null. -
public static int getTlshThreshold(Context context)(:80) — prefanti_fn_tlsh_threshold, default 40. -
public void clean(long maxAgeMs)(:85);public void clear()(:96).
"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; walksActivityManager.getRunningAppProcesses, filters by importance, skips own package +SAFEprefixes (:20), measures PSS, emitsonFoundper app thenonDone(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.
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) — alsoaddBehaviorFlag(pkg, "REMOVAL_RESISTANCE") -
void reportLauncherChange(String pkg, boolean changed, String method, boolean suspicious)(:228) — alsoaddBehaviorFlag(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).
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, computesstorageState(FLAG_READ_WRITE=1, FLAG_ALL_FILES=2 viaMANAGE_EXTERNAL_STORAGEAppOps). Only a state CHANGE after a baseline counts as a grant. On a new grant, recordsgrantedAtand whether it was All-Files; if All-Files, callsFileCanaryGuard.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 withinGRANT_WINDOW_MS=15min. Counts renames inRENAME_BURST_WINDOW_MS=1min; triggers atRENAME_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).
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 withinFRESH_INSTALL_WINDOW_MS=24h. Writes the canary into Downloads/Documents/Pictures/DCIM (targetDirs:92) and attaches aFileObserver(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, thencleanupNow. - Traps auto-delete after
TRAP_LIFETIME_MS=24hviascheduleCleanup(:242).
Gated by BehaviorDetectionSettings.isEnabled(c, FILE_CANARY). Called by RansomwareBehaviorGuard.onForegroundPermissionCheck (:98).
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, recordspendingTarget=prevPkg+pendingOpenedAt. On the next switch, ifpendingConfirmed && elapsed <= KICK_MAX_MS=1500ms && returns to pendingTarget, callsrecordKick. -
confirmSensitiveScreen(String kind)(:80) — called when on-screen text matches an uninstall/device-admin prompt. -
recordKick(:87) — counts kicks per pkg inKICK_WINDOW_MS=5min; reports to HIPS each time; triggers atKICK_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).
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) — walksgetRunningAppProcesses, analyzes each (analyzeProcess:115: name match +80, hidden name +20, high mem +15, unknown pkg +30), thenscanProcFilesystem(:186, cached 5 min viaPROC_SCAN_TTL_MS=300000) reading/proc/<pid>/command/proc/<pid>/status. Hidden name in /proc +90, unreadable name +25. -
public interface ProcessCallback(:58) —onSuspiciousProcess/onProcessListUpdated.
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 byRemovalResistanceGuard). -
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 leastminDISTINCT phrases match (used for SMS_PHISHING/FAKE_VIRUS_WARNING/RANSOMWARE where a single hit is too weak).
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, incrementstorHitCountsand callsevaluate. -
evaluate(:104) —netScore = min(50, torHits*20); +25 if installed withinFRESH_INSTALL_WINDOW_MS=1hr; +25 overlay+accessibility; +20 SMS+Internet; +10 device-admin; +10 REQUEST_INSTALL_PACKAGES. Triggers atSCORE_THRESHOLD=70. - On trigger:
BehaviorFlags.flag,ThreatLogger.logThreat,alert,BehaviorResponse.killAndPromptUninstall.
Gated by BehaviorDetectionSettings.isEnabled(c, DYNAMIC_RISK). Called by DnsVpnService.
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_DELETEintent. -
public static void killAndPromptUninstall(Context context, ThreatResult threat)(:87) — routes installed-app →killAndPromptUninstall(pkg), standalone file →promptDeleteFile, thenshowMalwareFoundScreen(full-screenMalwareFoundActivityover whatever's in foreground; relies on declaredSYSTEM_ALERT_WINDOW). -
public static void autoDeleteThreat(Context context, ThreatResult threat)(:129) — theAutoDeleteMalware-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 viaUserActionReceiver),isPackageInstalled:153.
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).
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>.
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 asActivityLifecycleCallbacks.onActivityCreatedincrementscreatedCount,onActivityDestroyeddecrements.
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, thenUrlThreatScanner.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) — lazyrefreshStatscached 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 toeventLogcap 1000, firesonSuspiciousActivityONLY 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.
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.
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) — walksAndroidCAStorealiases;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.
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.
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).
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":[...]}fromNetworkMonitor.getEventLogStatic(). -
public static int importTraffic(Context context, Uri uri)(:58) — reads JSON, reconstructsNetworkEvents, caps log at 1000. Returns count.
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) — regexhttps?://...over arbitrary text. -
public String scanUrl(String url)(:65) — returns category e.g. "PHISHING_URL"/"STEAM_PHISHING" or null; runsisSteamFakefirst, thenNativeScanner.scanUrl. -
public static boolean isSteamFake(String url)(:74) — regexSTEAM_FAKE:28; whitelists genuinesteamcommunity.com/subdomains first.
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 anyPREFIXESentry (:14:com.google.,com.android.,com.whatsapp,com.facebook.,com.miui.,com.samsung.,org.fdroid., etc.) OR is aFLAG_SYSTEM/FLAG_UPDATED_SYSTEM_APPapp.
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 orcidrContains:91.
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 clearsBehaviorFlagsfor that id),boolean isThreatAllowed(Context, String):60,void revokeThreatAllowance(Context, String):61. - Redirects:
void dismissRedirect(Context, String):64,boolean isRedirectDismissed(Context, String):65.
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).
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) — readsnoBackupFilesDir/anti_fn_cache.dbtablemalicious_tlshinto{"version":1,"exported_at":...,"anti_fn":[...]}. -
public static int importCache(Context context, Uri uri)(:67) — reads JSON, (re)creates the table,INSERT OR IGNOREeach row. Returns count.
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).
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 aGET_SIGNATURESfallback for minSdk 26/27.
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) — checksSU_PATHS(:13:/system/bin/su,/system/xbin/su,/sbin/su,/data/adb/magisk,/data/adb/ksu,/data/adb/ap,Superuser.apk, etc.) for existence, plusBuild.TAGScontains "test-keys".
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) — readsSettings.Global.ADB_ENABLED.
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). Prefprotection_enabled.
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).
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). Prefauto_rule_generation.
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). Prefsave_auto_rules.
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). Prefask_signature_on_remove.
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). Prefauto_delete_malware_enabled.
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 toMIN_INTERVAL_MIN).
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).
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).
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). Prefzero_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.
Toggle for a faster-scan mode. Default OFF. Pref fast_scan_mode_enabled. isEnabled/setEnabled.
Whether GuardService should silently start itself on device boot (via BootReceiver). Default ON. Pref boot_auto_start. isEnabled/setEnabled.
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.
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 onscanEnginedirectly. -
public ScanEngine getScanEngine()— used by ScanFragment forscanAllApps,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).
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 staticsForegroundPackage; used byScreenCaptureService.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=30withinSPAM_WINDOW_MS=8000, restricted toTYPE_VIEW_CLICKED/TYPE_WINDOW_STATE_CHANGED. - Notification spam:
checkNotificationSpam(:382),NOTIF_THRESHOLD=20withinNOTIF_WINDOW_MS=10000. - Screen-text:
checkNodesForSuspiciousKeywords(:223) matchesScreenThreatKeywords.RANSOMWARE(≥2),DEVICE_ADMIN,UNINSTALL,SMS_PHISHING(≥2),FAKE_VIRUS_WARNING(≥2); URL scan only in browsers (BROWSERSset: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, opensMainActivitywith alert extras, logs toThreatLogger.showBlockPageIfNotDismissed(:500) launchesBlockActivity.redirectIfNotDismissed/redirectToApp(:519) bringMainActivityto foreground instead of home.
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.
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.
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.
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)").
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.
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.
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).
| 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. |
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_VPN → onVpnReady (:452); onTaskHijackDetected/onSecureFlagLost (:266/:275) → Toast + finishAndRemoveTask().
Request codes: REQ_VPN=102, REQ_OVERLAY=103 (:37).
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.
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).
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.
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.
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.
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.
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>.
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) — infiniteValueAnimator(2 s).onDetachedFromWindow(:204) cancels the animator. Called byDashboardFragment.
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 theArrayDeque(maxMAX_POINTS=60), auto-scalesmaxValue, invalidates.onDraw(:76) draws grid, fill shader, bezier line path, glow, peak dot. Called byDashboardFragmentandNetworkFragment.
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).
RecyclerView.Adapter for ScannedFileInfo rows: name, status (threat=red/clean=green), risk score. public ScannedFileAdapter(List<ScannedFileInfo> files) (:23).
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).
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 byProcessDetector; consumed byGuardService(critical →sendProcessAlert+callback.onSuspiciousProcess).
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.
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.
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/hashCodekeyed onpackageName(:89).
Produced by ScanEngine.analyzeSingleApp/scanSingleFile; consumed everywhere (adapters, fragments, GuardService, receivers, ThreatLogger).
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) — readsSettings.System "show_touches". -
public interface OnSecureFlagLost { void onSecureFlagLost(); }(:40). -
public void startWatching(OnSecureFlagLost cb, long intervalMs)(:78) /public void stopWatching()(:85). Self-checkcheckSecureFlagStillSet(:90) honors the user'sdisable_secure_flagpref (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().
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) — usesDynamicAnalysisService.getForegroundPackage(); not a hijack if foreground is us or aTrustedPackages.isTrustedpackage.
Used by MainActivity: instantiated in onCreate (:89), startWatching(this::onTaskHijackDetected, 1500) in onResume (:195), stopWatching in onPause/before navigation. onTaskHijackDetected (:266) Toasts + finishAndRemoveTask().
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).
public void onReceive(Context, Intent) (:10) — on ACTION_BOOT_COMPLETED, if BootAutoStart.isEnabled: pre-warms NativeScanner.init, then startForegroundService(GuardService) (O+) / startService.
-
Preferences: almost every config class uses
hydra_prefs(MODE_PRIVATE); exceptions areBehaviorFlags(hydra_behavior_flags),UserDecisions(hydra_user_decisions), andFileCanaryGuard(hydra_canary). -
Two
ScanEngineinstances inGuardService:scanEngine(foreground/user scans, UI-forwarded viauiScanCallback) andbackgroundScanEngine(ownscanRunning, permanent callback that logs + auto-responds even with no UI attached).ScanFragmentMUST register viasetUiScanCallback, neverscanEngine.setCallback. -
Notification-channel sharing:
DnsVpnServicedeliberately reusesGuardService.CHANNEL_ID+NOTIFICATION_ID=1001andSTOP_FOREGROUND_DETACHso the Web Shield never shows a duplicate "System protected" notification. -
hydradragon YARA-X module data flow:
DnsVpnService(DNS queries + resolved IPs) andScreenCaptureService(OCR'd screen text) both feedNetworkObservationsper-package buckets;NetworkObservations.buildReportJson(pkg)is handed to the native engine at that app's next scan sohydradragon.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 byNativeScanner.enableVpnScan(true)(called only when DnsVpnService enters full-capture mode); packet scanning is throttled to 5 s and run viaNativeScanner.scanPackets(json). -
Xor filters (
.xf) are native-only: domain/URL/IP membership lives entirely in Rust memory viaNativeScanner.scanUrl/ the native IP xor filters; the JavaUrlThreatScanneris a thin wrapper plus the steamcommunity typosquat guard. Resolved-IP CIDR sinkholing uses the separate JavaCidrBlacklist(exact hash-set match, no xor filter). -
Java-side ML thresholds: ML flag counts as malicious only if
DetectionCategories.MLenabled ANDjaccard >= 0.55ANDanomaly >= 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 (
ScanEnginehelpers):isPuaName= containsPUA./PUA_;isAutoGeneratedName= starts withYARA-X.auto_/YARA.auto_;isEicarName= containseicar;isDexHeuristicName= starts withDEX/but notDEX/Critical(dropped as too FP-prone; onlyDEX/Criticalsurvives).