11"""
22KingWatch Pro v17 - core/network.py
3- Hybrid band detection + ping via background thread.
4- Returns keys: dl, ul, sig, ping, arc
3+
4+ ROOT CAUSE OF SIGNAL STUCK AT 'Detecting...':
5+ Chain of failures confirmed by bytecode + screenshot analysis:
6+ 1. getDataNetworkType() -> SecurityException on OEM ROM (MIUI/OXY etc)
7+ 2. getLinkDownstreamBandwidthKbps() -> returns 0 on locked ROMs
8+ 3. _heuristic() -> needs 3+ speed samples, returns '' on first calls
9+ 4. _fallback() -> sysfs operstate not accessible -> returns ''
10+ 5. _signal_loop: `if s:` where s='' -> _signal never updated
11+ Result: _signal stays 'Detecting...' forever
12+
13+ FIX: _detect() NEVER returns ''.
14+ Layer 4 (new): Classify from _dl (already measured by TrafficStats).
15+ Layer 5 (new): Always return 'Mobile' as final guaranteed fallback.
16+ The _dl EMA value is available immediately - no permissions needed.
17+
18+ PING STATUS: Bytecode correct. POP_JUMP_IF_TRUE on res is right:
19+ res=0 (connected) -> falsy -> falls through -> returns ms OK
20+ res!=0 (blocked) -> truthy -> jumps to next host OK
521"""
622import time as _time
723import glob
1531_ul = 0.0
1632_EMA = 0.4
1733_band_bps = 10.0 * 125000
18- _spdhist = [] # speed history for heuristic
34+ _spdhist = []
1935
2036
2137# -- Ping ------------------------------------------------------------------
2238def _ping_once ():
23- """TCP connect ping - all via getattr to avoid Python 3.11 cache bug."""
2439 _sk = __import__ ('socket' )
2540 _AF_INET = getattr (_sk , 'AF_INET' )
2641 _SOCK_STREAM = getattr (_sk , 'SOCK_STREAM' )
@@ -37,7 +52,8 @@ def _ping_once():
3752 res = _cx ((ip , port ))
3853 ms = round ((_now () - t0 ) * 1000 )
3954 _cl ()
40- if not res : # res == 0 means connected
55+ # res=0 means connected -> falsy -> return ms
56+ if not res :
4157 return str (ms ) + "ms"
4258 except Exception :
4359 pass
@@ -53,7 +69,7 @@ def _ping_loop():
5369 _sleep (8 )
5470
5571
56- # -- Band detection ---------------------------------------------------------
72+ # -- Band helpers - ---------------------------------------------------------
5773def _quality (dbm ):
5874 if - 50 < dbm : return "Excellent"
5975 if - 60 < dbm : return "Good"
@@ -62,20 +78,37 @@ def _quality(dbm):
6278 return "Poor"
6379
6480
65- def _heuristic (dl_bps ):
66- """Layer 3: classify band from measured speed median."""
81+ def _band_from_speed (bps ):
82+ """
83+ Layer 4: classify band from current measured speed.
84+ Uses _dl EMA - available after first TrafficStats read.
85+ No permissions needed. Always works.
86+ """
87+ global _band_bps
88+ mbps = bps / 125000.0
89+ if 50.0 < mbps : _band_bps = 500.0 * 125000 ; return "5G"
90+ if 10.0 < mbps : _band_bps = 50.0 * 125000 ; return "4G LTE"
91+ if 1.0 < mbps : _band_bps = 14.0 * 125000 ; return "3G"
92+ if 0.05 < mbps : _band_bps = 2.0 * 125000 ; return "2G"
93+ # Speed too low to classify - keep existing _band_bps
94+ return ""
95+
96+
97+ def _heuristic ():
98+ """Layer 3: median of recent speed history."""
6799 global _band_bps
68100 if len (_spdhist ) < 3 :
69101 return ""
70102 med = sorted (_spdhist )[len (_spdhist ) // 2 ]
71103 mbps = med / 125000.0
72- if 50.0 < mbps : _band_bps = 500.0 * 125000 ; return "5G (speed) "
73- if 10.0 < mbps : _band_bps = 50.0 * 125000 ; return "4G LTE (speed) "
74- if 1.0 < mbps : _band_bps = 14.0 * 125000 ; return "3G (speed) "
75- if 0.05 < mbps : _band_bps = 2.0 * 125000 ; return "2G (speed) "
104+ if 50.0 < mbps : _band_bps = 500.0 * 125000 ; return "5G"
105+ if 10.0 < mbps : _band_bps = 50.0 * 125000 ; return "4G LTE"
106+ if 1.0 < mbps : _band_bps = 14.0 * 125000 ; return "3G"
107+ if 0.05 < mbps : _band_bps = 2.0 * 125000 ; return "2G"
76108 return ""
77109
78110
111+ # -- Signal detection ------------------------------------------------------
79112def _detect ():
80113 global _band_bps
81114 try :
@@ -86,10 +119,10 @@ def _detect():
86119 cm = getattr (ctx , 'getSystemService' )(getattr (Ctx , 'CONNECTIVITY_SERVICE' ))
87120 net = getattr (cm , 'getActiveNetwork' )()
88121 if net is None :
89- return _fallback ()
122+ return _safe_fallback ()
90123 caps = getattr (cm , 'getNetworkCapabilities' )(net )
91124 if caps is None :
92- return _fallback ()
125+ return _safe_fallback ()
93126 _hT = getattr (caps , 'hasTransport' )
94127
95128 # WiFi
@@ -107,81 +140,107 @@ def _detect():
107140 q = _quality (rssi )
108141 return "WiFi " + band + " " + q + " " + str (rssi ) + "dBm " + str (spd ) + "Mbps"
109142 except Exception :
110- _band_bps = 100.0 * 125000 ; return "WiFi"
143+ _band_bps = 100.0 * 125000
144+ return "WiFi"
111145
112- # Cellular - Layer 1: TelephonyManager
146+ # Cellular
113147 if _hT (0 ):
148+ # Layer 1: TelephonyManager (may fail on locked ROMs)
114149 try :
115150 TM = autoclass ("android.telephony.TelephonyManager" )
116151 tm = getattr (ctx , 'getSystemService' )(getattr (Ctx , 'TELEPHONY_SERVICE' ))
117152 nt = getattr (tm , 'getDataNetworkType' )()
118- if nt in (20 ,): _band_bps = 500.0 * 125000 ; return "5G NR Max 500Mbps"
119- if nt in (13 ,): _band_bps = 50.0 * 125000 ; return "4G LTE Max 50Mbps"
120- if nt in (19 ,): _band_bps = 150.0 * 125000 ; return "4G LTE-CA Max 150Mbps"
121- if nt in (15 ,): _band_bps = 42.0 * 125000 ; return "4G HSPA+ Max 42Mbps"
153+ if nt in (20 ,): _band_bps = 500.0 * 125000 ; return "5G NR Max 500Mbps"
154+ if nt in (13 ,): _band_bps = 50.0 * 125000 ; return "4G LTE Max 50Mbps"
155+ if nt in (19 ,): _band_bps = 150.0 * 125000 ; return "4G LTE-CA Max 150Mbps"
156+ if nt in (15 ,): _band_bps = 42.0 * 125000 ; return "4G HSPA+ Max 42Mbps"
122157 if nt in (8 ,9 ,10 ,3 ,5 ,6 ,12 ,14 ):
123158 _band_bps = 14.0 * 125000 ; return "3G Max 14Mbps"
124159 if nt in (1 ,2 ,4 ,7 ,11 ,16 ):
125160 _band_bps = 0.2 * 125000 ; return "2G Max 0.2Mbps"
126- # nt==0 UNKNOWN - try Layer 2
127161 except Exception :
128- pass
162+ pass # locked ROM - try next layer
129163
130- # Layer 2: bandwidth from NetworkCapabilities
164+ # Layer 2: NetworkCapabilities bandwidth (no permission)
131165 try :
132- _gDL = getattr (caps , 'getLinkDownstreamBandwidthKbps' )
133- dl = _gDL ()
134- if 0 < dl :
135- if 50000 < dl : _band_bps = 500.0 * 125000 ; return "5G ~" + str (dl // 1000 ) + "Mbps"
136- if 5000 < dl : _band_bps = 50.0 * 125000 ; return "4G LTE ~" + str (dl // 1000 ) + "Mbps"
137- if 1000 < dl : _band_bps = 20.0 * 125000 ; return "4G ~" + str (dl // 1000 ) + "Mbps"
138- if 200 < dl : _band_bps = 14.0 * 125000 ; return "3G ~" + str (dl // 1000 ) + "Mbps"
139- _band_bps = 0.2 * 125000 ; return "2G ~" + str (dl ) + "Kbps"
166+ dl_kbps = getattr (caps , 'getLinkDownstreamBandwidthKbps' )()
167+ if 0 < dl_kbps :
168+ if 50000 < dl_kbps :
169+ _band_bps = 500.0 * 125000 ; return "5G ~" + str (dl_kbps // 1000 ) + "Mbps"
170+ if 5000 < dl_kbps :
171+ _band_bps = 50.0 * 125000 ; return "4G LTE ~" + str (dl_kbps // 1000 ) + "Mbps"
172+ if 1000 < dl_kbps :
173+ _band_bps = 20.0 * 125000 ; return "4G ~" + str (dl_kbps // 1000 ) + "Mbps"
174+ if 200 < dl_kbps :
175+ _band_bps = 14.0 * 125000 ; return "3G ~" + str (dl_kbps // 1000 ) + "Mbps"
176+ _band_bps = 0.2 * 125000 ; return "2G ~" + str (dl_kbps ) + "Kbps"
140177 except Exception :
141178 pass
142179
143- # Layer 3: speed heuristic
144- h = _heuristic (_dl )
180+ # Layer 3: speed history heuristic
181+ h = _heuristic ()
145182 if h :
146- return h
183+ return h + " (speed)"
147184
148- _band_bps = 10.0 * 125000 ; return "Mobile"
185+ # Layer 4: current _dl EMA - always available
186+ b = _band_from_speed (_dl )
187+ if b :
188+ return b + " (live)"
189+
190+ # Layer 5: guaranteed non-empty fallback
191+ _band_bps = 10.0 * 125000
192+ return "Mobile"
149193
150194 if _hT (3 ):
151- _band_bps = 1000.0 * 125000 ; return "Ethernet Max 1000Mbps"
195+ _band_bps = 1000.0 * 125000
196+ return "Ethernet"
152197
153198 return "Connected"
199+
154200 except Exception :
155201 pass
156- return _fallback ()
202+ return _safe_fallback ()
157203
158204
159- def _fallback ():
205+ def _safe_fallback ():
206+ """
207+ Always returns a non-empty string.
208+ Checks sysfs first, then returns 'Mobile' guaranteed.
209+ """
160210 global _band_bps
161211 for p in glob .glob ("/sys/class/net/wlan*/operstate" ):
162212 try :
163213 with open (p ) as f :
164214 if f .read ().strip () == "up" :
165- _band_bps = 100.0 * 125000 ; return "WiFi"
215+ _band_bps = 100.0 * 125000
216+ return "WiFi"
166217 except Exception :
167218 pass
168219 for p in glob .glob ("/sys/class/net/rmnet*/operstate" ):
169220 try :
170221 with open (p ) as f :
171222 if f .read ().strip () == "up" :
172- _band_bps = 10.0 * 125000 ; return "Mobile"
223+ _band_bps = 10.0 * 125000
224+ return "Mobile"
173225 except Exception :
174226 pass
175- return ""
227+
228+ # Layer 4: classify from current speed
229+ b = _band_from_speed (_dl )
230+ if b :
231+ return b
232+
233+ # Layer 5: absolute guaranteed minimum
234+ return "Mobile"
176235
177236
178237def _signal_loop ():
179238 global _signal
180239 _sleep = getattr (_time , 'sleep' )
181240 while True :
182241 s = _detect ()
183- if s :
184- _signal = s
242+ # s is ALWAYS non-empty now - no more stuck at Detecting...
243+ _signal = s
185244 _sleep (5 )
186245
187246
@@ -220,7 +279,6 @@ def _fmt(b):
220279
221280# -- Public API -------------------------------------------------------------
222281def get_network ():
223- # Returns keys: dl, ul, sig, ping, arc
224282 global _started , _dl , _ul
225283 if not _started :
226284 _started = True
@@ -236,15 +294,15 @@ def get_network():
236294
237295 if not _bw :
238296 _bw .update ({"rx" : rx , "tx" : tx , "t" : now })
239- return {"dl" : "0 KB/s" , "ul" : "0 KB/s" , "sig" : sig , "ping" : ping , "arc" : 0.0 }
297+ return {"dl" :"0 KB/s" ,"ul" :"0 KB/s" ,"sig" :sig ,"ping" :ping ,"arc" :0.0 }
240298
241299 dt = now - _bw ["t" ]
242300 if dt < 0.3 :
243301 if 0 < _band_bps :
244302 arc = min (100.0 , _dl / _band_bps * 100 )
245303 else :
246304 arc = 0.0
247- return {"dl" : _fmt (_dl ), "ul" : _fmt (_ul ), "sig" : sig , "ping" : ping , "arc" : arc }
305+ return {"dl" :_fmt (_dl ),"ul" :_fmt (_ul ),"sig" :sig ,"ping" :ping ,"arc" :arc }
248306
249307 prx = _bw ["rx" ]
250308 ptx = _bw ["tx" ]
@@ -271,4 +329,4 @@ def get_network():
271329 else :
272330 arc = 0.0
273331
274- return {"dl" : _fmt (_dl ), "ul" : _fmt (_ul ), "sig" : sig , "ping" : ping , "arc" : arc }
332+ return {"dl" :_fmt (_dl ),"ul" :_fmt (_ul ),"sig" :sig ,"ping" :ping ,"arc" :arc }
0 commit comments