diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9cf1dec3..30f69c0d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -401,6 +401,9 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
Device name
Dark theme
+ Export KDE Connect logs
+ Generate a file with execution information that can help troubleshoot issues.
+
More settings
Per-device settings can be found under \'Plugin settings\' from within a device.
Show persistent notification
diff --git a/src/org/kde/kdeconnect/Helpers/CreateFileResultContract.kt b/src/org/kde/kdeconnect/Helpers/CreateFileResultContract.kt
new file mode 100644
index 00000000..05cbbbfb
--- /dev/null
+++ b/src/org/kde/kdeconnect/Helpers/CreateFileResultContract.kt
@@ -0,0 +1,27 @@
+package org.kde.kdeconnect.Helpers
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.activity.result.contract.ActivityResultContract
+
+data class CreateFileParams(
+ val fileMimeType: String,
+ val suggestedFileName: String,
+)
+
+class CreateFileResultContract : ActivityResultContract() {
+
+ override fun createIntent(context: Context, input: CreateFileParams): Intent =
+ Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ setTypeAndNormalize(input.fileMimeType)
+ putExtra(Intent.EXTRA_TITLE, input.suggestedFileName)
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): Uri? = when (resultCode) {
+ Activity.RESULT_OK -> intent?.data
+ else -> null
+ }
+}
\ No newline at end of file
diff --git a/src/org/kde/kdeconnect/UserInterface/SettingsFragment.kt b/src/org/kde/kdeconnect/UserInterface/SettingsFragment.kt
index a39be9c9..030a14b5 100644
--- a/src/org/kde/kdeconnect/UserInterface/SettingsFragment.kt
+++ b/src/org/kde/kdeconnect/UserInterface/SettingsFragment.kt
@@ -11,6 +11,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
+import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.InputFilter
@@ -20,6 +21,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
+import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.ContextCompat
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
@@ -27,14 +29,22 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.google.android.material.snackbar.Snackbar
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.apache.commons.io.IOUtils
import org.kde.kdeconnect.BackgroundService
+import org.kde.kdeconnect.Helpers.CreateFileParams
+import org.kde.kdeconnect.Helpers.CreateFileResultContract
import org.kde.kdeconnect.Helpers.DeviceHelper
import org.kde.kdeconnect.Helpers.DeviceHelper.filterName
import org.kde.kdeconnect.Helpers.DeviceHelper.getDeviceName
import org.kde.kdeconnect.Helpers.NotificationHelper
import org.kde.kdeconnect.UserInterface.ThemeUtil.applyTheme
import org.kde.kdeconnect.extensions.setupBottomPadding
+import org.kde.kdeconnect_tp.BuildConfig
import org.kde.kdeconnect_tp.R
+import java.io.InputStreamReader
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@@ -56,6 +66,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
devicesByIpPref(context),
udpBroadcastPref(context),
bluetoothSupportPref(context),
+ exportLogsPref(context),
moreSettingsPref(context),
).forEach(screen::addPreference)
@@ -217,6 +228,32 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
+ private fun exportLogsPref(context: Context) = Preference(context).apply {
+ isPersistent = false
+ setTitle(R.string.settings_export_logs)
+ setSummary(R.string.settings_export_logs_text)
+ onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ exportLogs.launch(CreateFileParams("text/plain", "kdeconnect-log.txt"))
+ true
+ }
+ }
+
+ private val exportLogs: ActivityResultLauncher = registerForActivityResult(
+ CreateFileResultContract()
+ ) { uri: Uri? ->
+ val output = uri?.let { context?.contentResolver?.openOutputStream(uri) } ?: return@registerForActivityResult
+ CoroutineScope(Dispatchers.IO).launch {
+ val pid = android.os.Process.myPid()
+ val process = Runtime.getRuntime().exec(arrayOf("logcat", "-d", "--pid=$pid"))
+ val reader = InputStreamReader(process.inputStream)
+ output.use {
+ it.write("KDE Connect ${BuildConfig.VERSION_NAME}\n".toByteArray(Charsets.UTF_8))
+ it.write("Android ${Build.VERSION.RELEASE} (${Build.MANUFACTURER} ${Build.MODEL})\n".toByteArray(Charsets.UTF_8))
+ IOUtils.copy(reader, it, Charsets.UTF_8)
+ }
+ }
+ }
+
private fun moreSettingsPref(context: Context) = Preference(context).apply {
isPersistent = false
isSelectable = false