Skip to content

Commit 6557e39

Browse files
author
Hà Tiến Sáng
committed
feat: save crash logs to downloads
1 parent 60104e9 commit 6557e39

4 files changed

Lines changed: 79 additions & 10 deletions

File tree

‎app/src/main/kotlin/org/draken/usagi/core/ui/AppCrashActivity.kt‎

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@ package org.draken.usagi.core.ui
22

33
import android.animation.ObjectAnimator
44
import android.animation.ValueAnimator
5+
import android.content.ContentValues
56
import android.content.Intent
67
import android.os.Build
78
import android.os.Bundle
9+
import android.os.Environment
810
import android.os.Handler
911
import android.os.Looper
1012
import android.os.Process.killProcess
1113
import android.os.Process.myPid
14+
import android.provider.MediaStore
1215
import android.util.TypedValue
1316
import android.view.View
1417
import android.widget.TextView
18+
import android.widget.Toast
19+
import androidx.activity.result.contract.ActivityResultContracts
1520
import androidx.appcompat.app.AppCompatDelegate
1621
import androidx.core.graphics.ColorUtils
1722
import androidx.core.view.WindowInsetsCompat
@@ -26,21 +31,26 @@ import com.google.android.material.R as materialR
2631
class AppCrashActivity : BaseActivity<ActivityCrashBinding>() {
2732
private var headingRunnable: Runnable? = null
2833
private val handler = Handler(Looper.getMainLooper())
34+
private var crashReport = NO_TRACE
35+
private val saveCrashLogLauncher =
36+
registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
37+
if (uri != null) writeCrashReport(uri)
38+
}
2939

3040
override fun onCreate(savedInstanceState: Bundle?) {
3141
applyTheme()
3242
super.onCreate(savedInstanceState)
3343
setContentView(ActivityCrashBinding.inflate(layoutInflater))
3444
val stackTrace = intent.getStringExtra(EXTRA_STACK_TRACE) ?: NO_TRACE
35-
val report =
45+
crashReport =
3646
buildString {
3747
appendLine("Android: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})")
3848
appendLine("Device: ${Build.MANUFACTURER} ${Build.MODEL}")
3949
appendLine("Version: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
4050
appendLine("-------------------------------------")
4151
append(stackTrace)
4252
}
43-
viewBinding.crashTextView.text = report
53+
viewBinding.crashTextView.text = crashReport
4454
viewBinding.buttonReport.setOnClickListener {
4555
runCatching {
4656
val intent =
@@ -50,11 +60,12 @@ class AppCrashActivity : BaseActivity<ActivityCrashBinding>() {
5060
Intent.EXTRA_SUBJECT,
5161
"Usagi v${BuildConfig.VERSION_NAME} (${Build.MANUFACTURER} ${Build.MODEL}) w/ ${Build.VERSION.RELEASE}",
5262
)
53-
putExtra(Intent.EXTRA_TEXT, report)
63+
putExtra(Intent.EXTRA_TEXT, crashReport)
5464
}
5565
startActivity(Intent.createChooser(intent, getString(R.string.report)))
5666
}
5767
}
68+
viewBinding.buttonSaveCrashLog.setOnClickListener { saveCrashReport() }
5869
viewBinding.buttonRestart.setOnClickListener {
5970
startActivity(
6071
Intent(this, MainActivity::class.java)
@@ -81,6 +92,50 @@ class AppCrashActivity : BaseActivity<ActivityCrashBinding>() {
8192
if (isFinishing) killProcess(myPid())
8293
}
8394

95+
private fun saveCrashReport() {
96+
val fileName = "usagi-crash-${System.currentTimeMillis()}.txt"
97+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
98+
val values =
99+
ContentValues().apply {
100+
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
101+
put(MediaStore.Downloads.MIME_TYPE, "text/plain")
102+
put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
103+
put(MediaStore.Downloads.IS_PENDING, 1)
104+
}
105+
val uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
106+
if (uri == null) {
107+
showCrashLogSaveResult(false)
108+
return
109+
}
110+
runCatching {
111+
contentResolver.openOutputStream(uri)?.bufferedWriter(Charsets.UTF_8)?.use { it.write(crashReport) }
112+
?: error("Unable to open crash log output stream")
113+
contentResolver.update(uri, ContentValues().apply { put(MediaStore.Downloads.IS_PENDING, 0) }, null, null)
114+
}.onFailure {
115+
contentResolver.delete(uri, null, null)
116+
}.onSuccess { showCrashLogSaveResult(true) }
117+
} else {
118+
saveCrashLogLauncher.launch(fileName)
119+
}
120+
}
121+
122+
private fun writeCrashReport(uri: android.net.Uri) {
123+
runCatching {
124+
contentResolver.openOutputStream(uri)?.bufferedWriter(Charsets.UTF_8)?.use { it.write(crashReport) }
125+
?: error("Unable to open crash log output stream")
126+
}.onSuccess { showCrashLogSaveResult(true) }
127+
.onFailure { showCrashLogSaveResult(false) }
128+
}
129+
130+
private fun showCrashLogSaveResult(isSuccess: Boolean) {
131+
Toast
132+
.makeText(
133+
this,
134+
getString(if (isSuccess) R.string.crash_log_saved else R.string.crash_log_save_failed),
135+
Toast.LENGTH_SHORT,
136+
).show()
137+
}
138+
84139
private fun applyTheme() {
85140
val nightMode = intent.getIntExtra(EXTRA_THEME_NIGHT_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
86141
AppCompatDelegate.setDefaultNightMode(nightMode)

‎app/src/main/res/layout/activity_crash.xml‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,21 @@
104104
android:layout_height="wrap_content"
105105
android:text="@string/report" />
106106

107-
<Button
108-
android:id="@+id/button_restart"
109-
style="?materialButtonOutlinedStyle"
110-
android:layout_width="match_parent"
111-
android:layout_height="wrap_content"
112-
android:layout_marginTop="8dp"
113-
android:text="@string/restart_app" />
107+
<Button
108+
android:id="@+id/button_save_crash_log"
109+
style="?materialButtonOutlinedStyle"
110+
android:layout_width="match_parent"
111+
android:layout_height="wrap_content"
112+
android:layout_marginTop="8dp"
113+
android:text="@string/save_crash_log" />
114+
115+
<Button
116+
android:id="@+id/button_restart"
117+
style="?materialButtonOutlinedStyle"
118+
android:layout_width="match_parent"
119+
android:layout_height="wrap_content"
120+
android:layout_marginTop="8dp"
121+
android:text="@string/restart_app" />
114122

115123
</LinearLayout>
116124

‎app/src/main/res/values-vi/strings.xml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@
289289
<string name="status_re_reading">Đọc lại</string>
290290
<string name="no_chapters">Không có chương nào</string>
291291
<string name="crash_text">Có gì đó không đúng. Vui lòng gửi một báo cáo lỗi cho các nhà phát triển để giúp chúng tôi sửa nó.</string>
292+
<string name="save_crash_log">Lưu nhật ký crash</string>
293+
<string name="crash_log_saved">Đã lưu nhật ký crash vào Downloads</string>
294+
<string name="crash_log_save_failed">Không thể lưu nhật ký crash</string>
292295
<string name="status_on_hold">Tạm ngưng</string>
293296
<string name="status_dropped">Đã ngưng</string>
294297
<string name="appwidget_shelf_description">Manga từ các mục yêu thích của bạn</string>

‎app/src/main/res/values/strings.xml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@
275275
<string name="disable_battery_optimization">Disable battery optimization</string>
276276
<string name="disable_battery_optimization_summary">Helps with background updates checks</string>
277277
<string name="crash_text">Something went wrong. Please submit a bug report to the developers to help us fix it.</string>
278+
<string name="save_crash_log">Save crash log</string>
279+
<string name="crash_log_saved">Crash log saved to Downloads</string>
280+
<string name="crash_log_save_failed">Could not save crash log</string>
278281
<string name="crash_oops">Whoops!</string>
279282
<string name="restart_app">Restart the application</string>
280283
<string name="send">Send</string>

0 commit comments

Comments
 (0)