11package com.v2ray.ang.viewmodel
22
3+ import android.os.Process
34import androidx.lifecycle.ViewModel
45import com.v2ray.ang.AppConfig
56import com.v2ray.ang.AppConfig.ANG_PACKAGE
7+ import com.v2ray.ang.util.InProcessLogBuffer
68import com.v2ray.ang.util.LogUtil
79import java.io.IOException
10+ import java.util.concurrent.TimeUnit
811
912class LogcatViewModel : ViewModel () {
1013 private val logsetsAll: MutableList <String > = mutableListOf ()
1114 private var filteredLogs: List <String > = emptyList()
1215 private var currentFilter: String = " "
1316
17+ /* * True if last load succeeded via logcat process; false = fell back to in-process buffer. */
18+ var usedFallback: Boolean = false
19+ private set
20+
1421 fun getAll (): List <String > = filteredLogs
1522
1623 fun loadLogcat () {
17- try {
18- val lst = LinkedHashSet <String >()
19- lst.add(" logcat" )
20- lst.add(" -d" )
21- lst.add(" -v" )
22- lst.add(" time" )
23- lst.add(" -s" )
24- lst.add(" GoLog,${ANG_PACKAGE } ,AndroidRuntime,System.err" )
25- val process = Runtime .getRuntime().exec(lst.toTypedArray())
26- val allText = process.inputStream.bufferedReader().use { it.readLines() }.reversed()
27-
28- logsetsAll.clear()
29- logsetsAll.addAll(allText)
30- applyFilter()
24+ val lines = tryLogcatProcessBuilder()
25+ ? : tryLogcatPidOnly()
26+ ? : useInProcessBuffer()
27+
28+ logsetsAll.clear()
29+ logsetsAll.addAll(lines)
30+ applyFilter()
31+ }
32+
33+ /* *
34+ * Strategy 1: ProcessBuilder with stderr merged.
35+ * More reliable than Runtime.exec on MIUI — avoids shell escaping issues
36+ * and correctly captures GoLog output on stderr.
37+ */
38+ private fun tryLogcatProcessBuilder (): List <String >? {
39+ return try {
40+ val process = ProcessBuilder (
41+ " logcat" , " -d" , " -v" , " time" ,
42+ " -s" , " GoLog,$ANG_PACKAGE ,AndroidRuntime,System.err"
43+ )
44+ .redirectErrorStream(true )
45+ .start()
46+
47+ val exited = process.waitFor(5 , TimeUnit .SECONDS )
48+ if (! exited) {
49+ process.destroyForcibly()
50+ return null
51+ }
52+
53+ val lines = process.inputStream.bufferedReader().readLines()
54+ if (lines.isEmpty()) null
55+ else {
56+ usedFallback = false
57+ lines.reversed()
58+ }
3159 } catch (e: IOException ) {
32- LogUtil .e(AppConfig .TAG , " Failed to get logcat" , e)
60+ LogUtil .w(AppConfig .TAG , " logcat ProcessBuilder failed: ${e.message} " )
61+ null
62+ } catch (e: SecurityException ) {
63+ LogUtil .w(AppConfig .TAG , " logcat ProcessBuilder blocked: ${e.message} " )
64+ null
3365 }
3466 }
3567
68+ /* *
69+ * Strategy 2: Filter by own PID only.
70+ * MIUI/HyperOS sometimes allows reading own-process logs even when
71+ * broad logcat access is restricted.
72+ */
73+ private fun tryLogcatPidOnly (): List <String >? {
74+ return try {
75+ val pid = Process .myPid().toString()
76+ val process = ProcessBuilder (" logcat" , " -d" , " -v" , " time" , " --pid=$pid " )
77+ .redirectErrorStream(true )
78+ .start()
79+
80+ val exited = process.waitFor(5 , TimeUnit .SECONDS )
81+ if (! exited) {
82+ process.destroyForcibly()
83+ return null
84+ }
85+
86+ val lines = process.inputStream.bufferedReader().readLines()
87+ if (lines.isEmpty()) null
88+ else {
89+ usedFallback = false
90+ lines.reversed()
91+ }
92+ } catch (e: Exception ) {
93+ LogUtil .w(AppConfig .TAG , " logcat --pid fallback failed: ${e.message} " )
94+ null
95+ }
96+ }
97+
98+ /* *
99+ * Strategy 3: In-process buffer captured by LogUtil.
100+ * Always works regardless of OS restrictions.
101+ */
102+ private fun useInProcessBuffer (): List <String > {
103+ usedFallback = true
104+ return InProcessLogBuffer .getAll()
105+ }
106+
36107 fun clearLogcat () {
37108 try {
38- val lst = LinkedHashSet <String >()
39- lst.add(" logcat" )
40- lst.add(" -c" )
41- val process = Runtime .getRuntime().exec(lst.toTypedArray())
42- process.waitFor()
43-
44- logsetsAll.clear()
45- filteredLogs = emptyList()
46- } catch (e: IOException ) {
47- LogUtil .e(AppConfig .TAG , " Failed to clear logcat" , e)
109+ val process = ProcessBuilder (" logcat" , " -c" )
110+ .redirectErrorStream(true )
111+ .start()
112+ process.waitFor(3 , TimeUnit .SECONDS )
113+ } catch (e: Exception ) {
114+ LogUtil .w(AppConfig .TAG , " logcat clear failed: ${e.message} " )
48115 }
116+ InProcessLogBuffer .clear()
117+ logsetsAll.clear()
118+ filteredLogs = emptyList()
49119 }
50120
51121 fun filter (content : String? ) {
@@ -57,7 +127,7 @@ class LogcatViewModel : ViewModel() {
57127 filteredLogs = if (currentFilter.isEmpty()) {
58128 logsetsAll.toList()
59129 } else {
60- logsetsAll.filter { it.contains(currentFilter) }
130+ logsetsAll.filter { it.contains(currentFilter, ignoreCase = true ) }
61131 }
62132 }
63133}
0 commit comments