aboutsummaryrefslogtreecommitdiffstats
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt13
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt3
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsModel.kt15
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt6
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsViewModel.kt28
-rw-r--r--app/src/main/res/values-it/strings.xml4
-rw-r--r--app/src/main/res/values/strings.xml4
7 files changed, 72 insertions, 1 deletions
diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt
index c3809b36..05dc6b65 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt
@@ -38,6 +38,7 @@ import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect
+import androidx.lifecycle.lifecycleScope
import app.passwordstore.BuildConfig
import app.passwordstore.R
import app.passwordstore.ui.compose.theme.SpacingLarge
@@ -52,6 +53,7 @@ import app.passwordstore.util.services.PasswordExportService
import app.passwordstore.util.settings.PreferenceKeys
import com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel
import com.github.androidpasswordstore.autofillparser.getInstalledBrowsersWithAutofillSupportLevel
+import kotlinx.coroutines.launch
private enum class SettingsDialog {
Autofill,
@@ -125,6 +127,16 @@ fun SettingsSectionDestination(
)
}
+ val exportLog =
+ rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
+ if (uri != null)
+ activity.lifecycleScope.launch {
+ val message =
+ if (viewModel.exportLog(uri)) R.string.log_exported else R.string.log_export_failed
+ activity.snackbar(message = activity.getString(message))
+ }
+ }
+
// One screen lock prompt at a time: a double tap must not open two. A recreation resets it
// (remember), and the old activity's prompt is gone with it.
var prompting by remember { mutableStateOf(false) }
@@ -206,6 +218,7 @@ fun SettingsSectionDestination(
SettingsAction.ClearSecret -> dialog = SettingsDialog.ClearSecret
SettingsAction.DeleteRepository -> dialog = SettingsDialog.DeleteRepository
SettingsAction.Export -> export.launch(null)
+ SettingsAction.ExportLog -> exportLog.launch("hardenedpass-log.txt")
else -> onOpen(action)
}
},
diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt
index ba4d158c..e45166e0 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt
@@ -70,7 +70,8 @@ fun NavGraphBuilder.settingsGraph(activity: MainActivity, nav: NavHostController
// Handled by the destination itself.
SettingsAction.ClearSecret,
SettingsAction.DeleteRepository,
- SettingsAction.Export -> Unit
+ SettingsAction.Export,
+ SettingsAction.ExportLog -> Unit
}
},
onDeleted = {
diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsModel.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsModel.kt
index eca6770c..54250f74 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsModel.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsModel.kt
@@ -9,6 +9,7 @@ import app.passwordstore.util.extensions.hash
import app.passwordstore.util.extensions.time
import app.passwordstore.util.git.GitCommit
import java.io.IOException
+import java.io.OutputStream
import logcat.LogPriority.ERROR
import logcat.asLog
import logcat.logcat
@@ -77,3 +78,17 @@ fun gitLog(repo: Repository?): List<GitCommit> {
emptyList()
}
}
+
+/**
+ * Writes [header], then what logcat holds for this app: Android shows an app only its own lines,
+ * and those of its earlier runs and crashes until the buffer rotates.
+ */
+fun writeAppLog(header: String, out: OutputStream) {
+ out.write(header.toByteArray())
+ val logcat =
+ ProcessBuilder("logcat", "-d", "-b", "main,crash", "-v", "threadtime")
+ .redirectErrorStream(true)
+ .start()
+ logcat.inputStream.use { it.copyTo(out) }
+ logcat.waitFor()
+}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt
index a7bbe30a..2e7840ad 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt
@@ -63,6 +63,7 @@ enum class SettingsAction {
ClearSecret,
DeleteRepository,
Export,
+ ExportLog,
}
/** Top bar with [title] and back, then [content] in one grouped surface, scrolling. */
@@ -324,6 +325,11 @@ fun SettingsSectionScreen(
onCheckedChange = { onBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, it) },
summary = stringResource(R.string.pref_debug_logging_summary),
)
+ PreferenceAction(
+ title = stringResource(R.string.pref_export_log_title),
+ summary = stringResource(R.string.pref_export_log_summary),
+ onClick = { onAction(SettingsAction.ExportLog) },
+ )
}
SettingsSection.Pgp -> {
PreferenceAction(
diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsViewModel.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsViewModel.kt
index cfc576ae..90aecb2a 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsViewModel.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsViewModel.kt
@@ -8,6 +8,8 @@ package app.passwordstore.ui.settings
import android.app.Application
import android.content.SharedPreferences
import android.content.pm.ShortcutManager
+import android.net.Uri
+import android.os.Build
import androidx.annotation.StringRes
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -16,6 +18,7 @@ import androidx.core.content.edit
import androidx.core.content.getSystemService
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
+import app.passwordstore.BuildConfig
import app.passwordstore.R
import app.passwordstore.data.crypto.PGPPassphraseCache
import app.passwordstore.data.repo.PasswordRepository
@@ -31,12 +34,16 @@ import app.passwordstore.util.settings.GitSettings
import app.passwordstore.util.settings.PasswordSortOrder
import app.passwordstore.util.settings.PreferenceKeys
import dagger.hilt.android.lifecycle.HiltViewModel
+import java.io.IOException
import java.nio.file.Files
import java.nio.file.LinkOption
import javax.inject.Inject
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import logcat.LogPriority.ERROR
+import logcat.asLog
+import logcat.logcat
/** What the settings sections show: stored values, and the state rows depend on. */
data class SettingsState(
@@ -111,6 +118,27 @@ constructor(
state = read()
}
+ /** Saves the app's log to [uri]; false when that failed. */
+ suspend fun exportLog(uri: Uri): Boolean =
+ withContext(dispatcherProvider.io()) {
+ val header =
+ "HardenedPass ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}), " +
+ "Android ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}), ${Build.MODEL}\n\n"
+ try {
+ val out =
+ getApplication<Application>().contentResolver.openOutputStream(uri)
+ ?: throw IOException("No stream for the chosen file")
+ out.use { writeAppLog(header, it) }
+ true
+ } catch (e: IOException) {
+ logcat(ERROR) { e.asLog() }
+ false
+ } catch (e: SecurityException) {
+ logcat(ERROR) { e.asLog() }
+ false
+ }
+ }
+
fun putBoolean(key: String, value: Boolean) {
prefs.edit { putBoolean(key, value) }
}
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index b4569746..fed8adec 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -288,6 +288,10 @@
<string name="pref_rebase_on_pull_title">Rebase al pull</string>
<string name="pref_rebase_on_pull_summary">Durante pull o sincronizzazione, crea un merge commit con le modifiche del remote</string>
<string name="pref_rebase_on_pull_summary_on">Durante pull o sincronizzazione, esegui il rebase dei commit che non sono sul remote</string>
+ <string name="pref_export_log_title">Esporta log</string>
+ <string name="pref_export_log_summary">Salva in un file i log di questa app. Possono mostrare nomi delle voci, siti, app e il tuo server git: leggi il file prima di condividerlo</string>
+ <string name="log_exported">Log salvato</string>
+ <string name="log_export_failed">Impossibile salvare il log</string>
<string name="pref_push_after_commit_title">Push dopo ogni modifica</string>
<string name="pref_push_after_commit_summary">Esegui il push sul repository remoto dopo aver salvato, spostato o eliminato nell\'app</string>
<string name="git_push_after_commit_failed">Salvato sul dispositivo, ma push non riuscito. Sincronizza per riprovare</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fa9fb0f3..a415e95f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -126,6 +126,10 @@
<string name="pref_rebase_on_pull_title">Rebase on pull</string>
<string name="pref_rebase_on_pull_summary">When pulling or syncing, create a merge commit with upstream changes</string>
<string name="pref_rebase_on_pull_summary_on">When pulling or syncing, rebase commits that are not present in the remote repository</string>
+ <string name="pref_export_log_title">Export log</string>
+ <string name="pref_export_log_summary">Save this app\'s logs to a file. They can show entry names, websites, apps and your git server, so read the file before sharing it</string>
+ <string name="log_exported">Log saved</string>
+ <string name="log_export_failed">Could not save the log</string>
<string name="pref_push_after_commit_title">Push after each change</string>
<string name="pref_push_after_commit_summary">Push to the remote repository after saving, moving or deleting in the app</string>
<string name="git_push_after_commit_failed">Saved on this device, but not pushed. Sync to try again</string>