Skip to content

Commit 74ce72a

Browse files
fix logcat not show in MIUI/HyperOS
1 parent fb6d7ba commit 74ce72a

4 files changed

Lines changed: 141 additions & 27 deletions

File tree

V2rayNG/app/src/main/java/com/v2ray/ang/ui/LogcatActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
6868
shareDir.listFiles()?.forEach { it.delete() }
6969

7070
val timestamp = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US).format(Date())
71-
val logFile = File(shareDir, "v2rayNG_logcat_$timestamp.txt")
71+
val logFile = File(shareDir, "MikuRay_logcat_$timestamp.txt")
7272
logFile.writeText(logText, Charsets.UTF_8)
7373

7474
val uri = FileProvider.getUriForFile(
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.v2ray.ang.util
2+
3+
import android.util.Log
4+
import java.text.SimpleDateFormat
5+
import java.util.Date
6+
import java.util.LinkedList
7+
import java.util.Locale
8+
9+
/**
10+
* In-process log buffer — fallback when logcat is blocked (e.g. MIUI/HyperOS).
11+
* Intercepts LogUtil calls and stores them in a bounded circular buffer.
12+
*/
13+
object InProcessLogBuffer {
14+
private const val MAX_ENTRIES = 2000
15+
private val buffer: LinkedList<String> = LinkedList()
16+
private val fmt = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
17+
18+
@Synchronized
19+
fun append(priority: Int, tag: String, message: String) {
20+
val level = when (priority) {
21+
Log.VERBOSE -> "V"
22+
Log.DEBUG -> "D"
23+
Log.INFO -> "I"
24+
Log.WARN -> "W"
25+
Log.ERROR -> "E"
26+
else -> "?"
27+
}
28+
val line = "${fmt.format(Date())} $level/$tag: $message"
29+
if (buffer.size >= MAX_ENTRIES) buffer.removeFirst()
30+
buffer.addLast(line)
31+
}
32+
33+
@Synchronized
34+
fun getAll(): List<String> = buffer.toList().reversed()
35+
36+
@Synchronized
37+
fun clear() = buffer.clear()
38+
}

V2rayNG/app/src/main/java/com/v2ray/ang/util/LogUtil.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ object LogUtil {
5555
private fun log(priority: Int, tag: String, message: String, throwable: Throwable? = null) {
5656
if (!isEnabled(priority)) return
5757

58+
val fullMessage = if (throwable != null) "$message\n${throwable.stackTraceToString()}" else message
59+
5860
when {
5961
throwable == null -> Log.println(priority, tag, message)
6062
priority >= Log.ERROR -> Log.e(tag, message, throwable)
@@ -63,6 +65,10 @@ object LogUtil {
6365
priority == Log.DEBUG -> Log.d(tag, message, throwable)
6466
else -> Log.v(tag, message, throwable)
6567
}
68+
69+
// Feed into in-process buffer as fallback for devices (e.g. MIUI/HyperOS)
70+
// where logcat exec is blocked for third-party apps.
71+
InProcessLogBuffer.append(priority, tag, fullMessage)
6672
}
6773

6874
fun d(tag: String = AppConfig.TAG, message: String) = log(Log.DEBUG, tag, message)
Lines changed: 96 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,121 @@
11
package com.v2ray.ang.viewmodel
22

3+
import android.os.Process
34
import androidx.lifecycle.ViewModel
45
import com.v2ray.ang.AppConfig
56
import com.v2ray.ang.AppConfig.ANG_PACKAGE
7+
import com.v2ray.ang.util.InProcessLogBuffer
68
import com.v2ray.ang.util.LogUtil
79
import java.io.IOException
10+
import java.util.concurrent.TimeUnit
811

912
class 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

Comments
 (0)