1+ /*
2+ ============================================================================
3+ Name : Aria2NetworkProfiler.java
4+ Contributors: AppDevForAll
5+ Copyright (c) 2026 AppDevForAll
6+ Description : Network profiler to determine optimal network routing (IPv4 vs IPv6).
7+ ============================================================================
8+ */
9+
10+ package org .iiab .controller ;
11+
12+ import android .os .Handler ;
13+ import android .util .Log ;
14+
15+ import java .io .BufferedReader ;
16+ import java .io .File ;
17+ import java .io .InputStreamReader ;
18+ import java .util .ArrayList ;
19+ import java .util .List ;
20+ import java .util .regex .Matcher ;
21+ import java .util .regex .Pattern ;
22+
23+ public class Aria2NetworkProfiler {
24+
25+ private static final String TAG = "IIAB-NetworkProfiler" ;
26+ private static final int TEST_DURATION_MS = 6000 ; // 6 seconds to bypass initial QoS burst
27+
28+ /**
29+ * Runs a real-world speed test by downloading the target file for a few seconds.
30+ * Returns TRUE if IPv4 proves to be more stable and faster.
31+ */
32+ public static boolean shouldForceIpv4 (File aria2Bin , File downloadDir , File dhtFile , String targetUrl , Handler mainHandler , Aria2Manager .DownloadListener listener ) {
33+ Log .i (TAG , "Starting Network Profiling (Time-Boxed Race)..." );
34+
35+ // 1. Test IPv4 (Forced)
36+ mainHandler .post (() -> listener .onProgress (0 , "Test IPv4" , "Wait" ));
37+ Log .i (TAG , "=== TEST 1: FORCED IPv4 ROUTE ===" );
38+ double speedIpv4 = runTimeBoxedTest (aria2Bin , downloadDir , dhtFile , targetUrl , true );
39+
40+ // 2. Test Default (Usually IPv6 preferred)
41+ mainHandler .post (() -> listener .onProgress (0 , "Test IPv6" , "Wait" ));
42+ Log .i (TAG , "=== TEST 2: DEFAULT ROUTE (IPv6 Preferred) ===" );
43+ double speedDefault = runTimeBoxedTest (aria2Bin , downloadDir , dhtFile , targetUrl , false );
44+
45+ Log .i (TAG , "=== FINAL PROFILING RESULTS ===" );
46+ Log .i (TAG , String .format ("Sustained Speed IPv4: %.2f KB/s" , speedIpv4 ));
47+ Log .i (TAG , String .format ("Sustained Speed Default: %.2f KB/s" , speedDefault ));
48+
49+ boolean forceIpv4 = false ;
50+
51+ // If Default/IPv6 is dead (0) or IPv4 is at least 20% faster, force IPv4.
52+ if (speedDefault == 0 || speedIpv4 > (speedDefault * 1.2 )) {
53+ Log .w (TAG , "Conclusion: QoS or routing is throttling the default route. Forcing IPv4." );
54+ forceIpv4 = true ;
55+ } else {
56+ Log .i (TAG , "Conclusion: Default route is stable. Using default configuration." );
57+ }
58+
59+ // Show the winner in the UI
60+ final String winnerText = forceIpv4 ? "✓ IPv4" : "✓ IPv6" ;
61+ mainHandler .post (() -> listener .onProgress (0 , winnerText , "Wait" ));
62+
63+ // Brief pause so the user can actually read the result before the real download overwrites it
64+ try { Thread .sleep (1000 ); } catch (InterruptedException ignored ) {}
65+
66+ return forceIpv4 ;
67+ }
68+
69+ private static double runTimeBoxedTest (File aria2Bin , File downloadDir , File dhtFile , String url , boolean forceIpv4 ) {
70+ Process process = null ;
71+ double lastRecordedSpeedKB = 0.0 ;
72+
73+ try {
74+ List <String > command = new ArrayList <>();
75+ command .add (aria2Bin .getAbsolutePath ());
76+ command .add ("--dir=" + downloadDir .getAbsolutePath ());
77+ command .add ("--continue=true" ); // CRITICAL: Do not lose downloaded data
78+ command .add ("--file-allocation=none" );
79+
80+ // Match your baseline configuration
81+ command .add ("--max-connection-per-server=4" );
82+ command .add ("--split=4" );
83+ command .add ("--enable-dht=true" );
84+ command .add ("--dht-file-path=" + dhtFile .getAbsolutePath ());
85+ command .add ("--check-certificate=false" );
86+
87+ if (forceIpv4 ) {
88+ command .add ("--disable-ipv6=true" );
89+ }
90+
91+ command .add ("--console-log-level=warn" );
92+ command .add ("--summary-interval=1" ); // Need speed output every second
93+ command .add ("--download-result=hide" );
94+ command .add (url );
95+
96+ ProcessBuilder pb = new ProcessBuilder (command );
97+ pb .redirectErrorStream (true );
98+ process = pb .start ();
99+
100+ BufferedReader reader = new BufferedReader (new InputStreamReader (process .getInputStream ()));
101+ String line ;
102+
103+ // Regex to capture speed (e.g., DL:1.5MiB or DL:750KiB)
104+ Pattern pattern = Pattern .compile ("DL:([0-9.]+)([K|M|G]?iB)" );
105+
106+ long startTime = System .currentTimeMillis ();
107+
108+ // Read output until time runs out
109+ while ((System .currentTimeMillis () - startTime ) < TEST_DURATION_MS ) {
110+ if (reader .ready ()) {
111+ line = reader .readLine ();
112+ if (line == null ) break ;
113+
114+ Log .d (TAG , "[Aria2-Test] " + line );
115+
116+ Matcher matcher = pattern .matcher (line );
117+ if (matcher .find ()) {
118+ double value = Double .parseDouble (matcher .group (1 ));
119+ String unit = matcher .group (2 );
120+
121+ // Standardize to Kilobytes per second (KB/s)
122+ if (unit .equals ("MiB" )) {
123+ lastRecordedSpeedKB = value * 1024 ;
124+ } else if (unit .equals ("GiB" )) {
125+ lastRecordedSpeedKB = value * 1024 * 1024 ;
126+ } else {
127+ lastRecordedSpeedKB = value ; // KiB
128+ }
129+ }
130+ } else {
131+ Thread .sleep (50 ); // Short pause to prevent CPU burn
132+ }
133+ }
134+
135+ } catch (Exception e ) {
136+ Log .e (TAG , "Speed test error" , e );
137+ } finally {
138+ if (process != null ) {
139+ process .destroy (); // Kill process mercilessly after 6 seconds
140+ try { process .waitFor (); } catch (InterruptedException ignored ) {}
141+ }
142+ }
143+
144+ return lastRecordedSpeedKB ;
145+ }
146+ }
0 commit comments