[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