[Android] Gather the logs for the remote access feedback
Nicolas Pomepuy
git at videolan.org
Thu Apr 24 14:06:53 UTC 2025
vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Thu Apr 10 09:07:01 2025 +0200| [2b7636b658d9cac544a04b29a7e77492811630ad] | committer: Nicolas Pomepuy
Gather the logs for the remote access feedback
> https://code.videolan.org/videolan/vlc-android/commit/2b7636b658d9cac544a04b29a7e77492811630ad
---
.../vlc/remoteaccessserver/RemoteAccessRouting.kt | 27 +++++--
.../vlc/remoteaccessserver/RemoteAccessServer.kt | 93 ++++++++++++++++++++++
2 files changed, 114 insertions(+), 6 deletions(-)
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
index 348cf61a6d..baf5004e99 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
@@ -292,18 +292,33 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
var zipFile: String? = null
+ val externalPath = RemoteAccessServer.getInstance(appContext).downloadFolder
+ val logcatZipPath = "$externalPath/logcat.zip"
+ // generate logs
+ if (includeLogs) {
+ File(externalPath).mkdirs()
+ RemoteAccessServer.getInstance(appContext).gatherLogs(logcatZipPath)
+ }
+ val dbPath = "$externalPath${Medialibrary.VLC_MEDIA_DB_NAME}"
+
+ //generate ML
if (includeML) {
- val externalPath = RemoteAccessServer.getInstance(appContext).downloadFolder
File(externalPath).mkdirs()
- val dbPath = "$externalPath/${Medialibrary.VLC_MEDIA_DB_NAME}"
- val dbZipPath = "$externalPath/db.zip"
val db = File(appContext.getDir("db", Context.MODE_PRIVATE).toString() + Medialibrary.VLC_MEDIA_DB_NAME)
val dbFile = File(dbPath)
FileUtils.copyFile(db, dbFile)
- FileUtils.zip(arrayOf(dbPath), dbZipPath)
- FileUtils.deleteFile(dbFile)
- zipFile = "db.zip"
+ }
+
+ //Zip needed files
+ if (File(logcatZipPath).exists() || File(dbPath).exists()) {
+ zipFile = "feedback_report.zip"
+ val dbZipPath = "$externalPath/$zipFile"
+ val filesToZip = mutableListOf<String>()
+ if (File(logcatZipPath).exists()) filesToZip.add(logcatZipPath)
+ if (File(dbPath).exists()) filesToZip.add(dbPath)
+ FileUtils.zip(filesToZip.toTypedArray(), dbZipPath)
+ filesToZip.forEach { FileUtils.deleteFile(it) }
}
val completeMessage = "$message\r\n\r\n${FeedbackUtil.generateUsefulInfo(appContext)}"
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessServer.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessServer.kt
index f575d71289..4de80e452f 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessServer.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessServer.kt
@@ -97,10 +97,12 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.slf4j.LoggerFactory
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.interfaces.IMedia
+import org.videolan.libvlc.util.AndroidUtil
import org.videolan.libvlc.util.MediaBrowser
import org.videolan.medialibrary.MLServiceLocator
import org.videolan.medialibrary.interfaces.media.MediaWrapper
import org.videolan.medialibrary.media.MediaLibraryItem
+import org.videolan.resources.AppContextProvider
import org.videolan.resources.VLCInstance
import org.videolan.tools.AppScope
import org.videolan.tools.KEYSTORE_PASSWORD
@@ -109,6 +111,7 @@ import org.videolan.tools.REMOTE_ACCESS_NETWORK_BROWSER_CONTENT
import org.videolan.tools.Settings
import org.videolan.tools.SingletonHolder
import org.videolan.tools.putSingle
+import org.videolan.vlc.DebugLogService
import org.videolan.vlc.PlaybackService
import org.videolan.vlc.PlaybackService.Companion.playerSleepTime
import org.videolan.vlc.gui.DialogActivity
@@ -117,12 +120,14 @@ import org.videolan.vlc.remoteaccessserver.ssl.SecretGenerator
import org.videolan.vlc.remoteaccessserver.websockets.RemoteAccessWebSockets
import org.videolan.vlc.remoteaccessserver.websockets.RemoteAccessWebSockets.setupWebSockets
import org.videolan.vlc.util.FileUtils
+import org.videolan.vlc.util.Permissions
import org.videolan.vlc.util.isSchemeSMB
import org.videolan.vlc.viewmodels.CallBackDelegate
import org.videolan.vlc.viewmodels.ICallBackHandler
import org.videolan.vlc.viewmodels.browser.IPathOperationDelegate
import org.videolan.vlc.viewmodels.browser.PathOperationDelegate
import java.io.File
+import java.io.IOException
import java.math.BigInteger
import java.net.InetAddress
import java.net.NetworkInterface
@@ -164,6 +169,9 @@ class RemoteAccessServer(private val context: Context) : PlaybackService.Callbac
private val otgDevice = context.getString(org.videolan.vlc.R.string.otg_device_title)
+ var client: DebugLogService.Client? = null
+
+
private val miniPlayerObserver = androidx.lifecycle.Observer<Boolean> { playing ->
AppScope.launch {
val isPlaying = service?.isPlaying == true || playing
@@ -324,6 +332,7 @@ class RemoteAccessServer(private val context: Context) : PlaybackService.Callbac
withContext(Dispatchers.IO) {
RemoteAccessWebSockets.closeAllSessions()
if (::engine.isInitialized) engine.stop()
+ client?.release()
}
}
@@ -901,6 +910,90 @@ class RemoteAccessServer(private val context: Context) : PlaybackService.Callbac
return list
}
+ suspend fun gatherLogs(logcatZipPath: String) {
+ var waitForClient = true
+ var started = false
+ val gatheringLogsStart = System.currentTimeMillis()
+ //initiate a log to wait for
+ val logMessage = "Starting collecting logs at ${System.currentTimeMillis()}"
+
+ client = DebugLogService.Client(context, object : DebugLogService.Client.Callback {
+ override fun onStarted(logList: List<String>) {
+ started = true
+ Log.d("LogsGathering", logMessage)
+ }
+
+ override fun onStopped() {
+ }
+
+ override fun onLog(msg: String) {
+ //Wait for the log to initiate a save to avoid ANR
+ if (msg.contains(logMessage)) {
+ if (AndroidUtil.isOOrLater && !Permissions.canWriteStorage())
+ waitForClient = false
+ else
+ client?.save()
+ }
+ }
+
+ override fun onSaved(success: Boolean, path: String) {
+ if (!success) {
+ client?.stop()
+ waitForClient = false
+ return
+ }
+ client?.stop()
+ val filesToAdd = mutableListOf(path)
+ //add previous crash logs
+ try {
+ AppContextProvider.appContext.getExternalFilesDir(null)?.absolutePath?.let { folder ->
+ File(folder).listFiles()?.forEach {
+ if (it.isFile && (it.name.contains("crash_") || it.name.contains("logcat_"))) filesToAdd.add(it.path)
+ }
+ }
+ } catch (exception: IOException) {
+ Log.w("LogsGathering", exception.message, exception)
+ client?.stop()
+ return
+ }
+
+ if (!FileUtils.zip(filesToAdd.toTypedArray(), logcatZipPath)) {
+ client?.stop()
+ return
+ }
+ try {
+ filesToAdd.forEach { FileUtils.deleteFile(it) }
+ } catch (exception: IOException) {
+ Log.w("LogsGathering", exception.message, exception)
+ client?.stop()
+ return
+ }
+
+ waitForClient = false
+
+ }
+
+ })
+ while (!started) {
+ delay(100)
+ if (System.currentTimeMillis() > gatheringLogsStart + 4000) {
+ Log.w("LogsGathering", "Failed to start log gathering")
+ started = true
+ waitForClient = false
+ }
+ client?.start() == true
+ }
+ while (waitForClient) {
+ delay(100)
+ if (System.currentTimeMillis() > gatheringLogsStart + 20000) {
+ Log.w("LogsGathering", "Cannot complete log gathering in time")
+ waitForClient = false
+ }
+ }
+ client?.release()
+ client = null
+ }
+
abstract class WSMessage(val type: WSMessageType)
data class NowPlaying(val title: String, val artist: String, val playing: Boolean, val isVideoPlaying: Boolean, val progress: Long,
val duration: Long, val id: Long, val artworkURL: String, val uri: String, val volume: Int, val speed: Float,
More information about the Android
mailing list