mirror of
https://github.com/jrpie/Launcher.git
synced 2025-05-15 15:01:53 +02:00
add crash handler
This commit is contained in:
parent
7a874ef89f
commit
04330ff407
12 changed files with 270 additions and 7 deletions
|
@ -8,6 +8,7 @@
|
|||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES" />
|
||||
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
|
@ -19,6 +20,7 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/launcherBaseTheme"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<activity
|
||||
android:name=".ui.widgets.manage.ManageWidgetPanelsActivity"
|
||||
android:exported="false" />
|
||||
|
@ -80,6 +82,9 @@
|
|||
<activity
|
||||
android:name=".ui.LegalInfoActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.ReportCrashActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name=".actions.lock.LauncherDeviceAdmin"
|
||||
|
@ -110,5 +115,4 @@
|
|||
android:resource="@xml/accessibility_service_config" />
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -25,6 +25,7 @@ import de.jrpie.android.launcher.preferences.resetPreferences
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
const val APP_WIDGET_HOST_ID = 42;
|
||||
|
@ -106,6 +107,11 @@ class Application : android.app.Application() {
|
|||
// TODO Error: Invalid resource ID 0x00000000.
|
||||
// DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler { _, throwable ->
|
||||
sendCrashNotification(this@Application, throwable)
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.M) {
|
||||
torchManager = TorchManager(this)
|
||||
|
@ -157,6 +163,8 @@ class Application : android.app.Application() {
|
|||
removeUnusedShortcuts(this)
|
||||
}
|
||||
loadApps()
|
||||
|
||||
createNotificationChannels(this)
|
||||
}
|
||||
|
||||
fun getCustomAppNames(): HashMap<AbstractAppInfo, String> {
|
||||
|
|
|
@ -6,9 +6,6 @@ import android.app.role.RoleManager
|
|||
import android.content.ActivityNotFoundException
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.LauncherApps
|
||||
|
@ -227,3 +224,13 @@ fun copyToClipboard(context: Context, text: String) {
|
|||
val clipData = ClipData.newPlainText("Debug Info", text)
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
}
|
||||
|
||||
fun writeEmail(context: Context, to: String, subject: String, text: String) {
|
||||
val intent = Intent(Intent.ACTION_SENDTO)
|
||||
intent.setData("mailto:".toUri())
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(to))
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text)
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.send_email)))
|
||||
}
|
||||
|
||||
|
|
87
app/src/main/java/de/jrpie/android/launcher/Notifications.kt
Normal file
87
app/src/main/java/de/jrpie/android/launcher/Notifications.kt
Normal file
|
@ -0,0 +1,87 @@
|
|||
package de.jrpie.android.launcher
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import de.jrpie.android.launcher.ui.EXTRA_CRASH_LOG
|
||||
import de.jrpie.android.launcher.ui.ReportCrashActivity
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import kotlin.random.Random
|
||||
|
||||
private val NOTIFICATION_CHANNEL_CRASH = "launcher:crash"
|
||||
|
||||
fun createNotificationChannels(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationChannel = NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_CRASH,
|
||||
context.getString(R.string.notification_channel_crash),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
|
||||
val notificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestNotificationPermission(activity: Activity) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
return
|
||||
}
|
||||
|
||||
val permission =
|
||||
(activity.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED)
|
||||
|
||||
if (!permission) {
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
arrayOf( android.Manifest.permission.POST_NOTIFICATIONS ),
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendCrashNotification(context: Context, throwable: Throwable) {
|
||||
val stringWriter = StringWriter()
|
||||
val printWriter = PrintWriter(stringWriter)
|
||||
throwable.printStackTrace(printWriter)
|
||||
|
||||
val intent = Intent(context, ReportCrashActivity::class.java)
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.putExtra(EXTRA_CRASH_LOG, stringWriter.toString())
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
Random.nextInt(),
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_CRASH)
|
||||
.setSmallIcon(R.drawable.baseline_bug_report_24)
|
||||
.setContentTitle(context.getString(R.string.notification_crash_title))
|
||||
.setContentText(context.getString(R.string.notification_crash_explanation))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(false)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
try {
|
||||
notificationManager.notify(
|
||||
0,
|
||||
builder.build()
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e("Crash Notification", "Could not send notification")
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio
|
|||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion4
|
||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
||||
import de.jrpie.android.launcher.sendCrashNotification
|
||||
import de.jrpie.android.launcher.ui.HomeActivity
|
||||
import de.jrpie.android.launcher.widgets.ClockWidget
|
||||
import de.jrpie.android.launcher.widgets.DebugInfoWidget
|
||||
|
@ -76,6 +77,7 @@ fun migratePreferencesToNewVersion(context: Context) {
|
|||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to restore preferences:\n${e.stackTrace}")
|
||||
sendCrashNotification(context, e)
|
||||
resetPreferences(context)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package de.jrpie.android.launcher.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.copyToClipboard
|
||||
import de.jrpie.android.launcher.databinding.ActivityReportCrashBinding
|
||||
import de.jrpie.android.launcher.getDeviceInfo
|
||||
import de.jrpie.android.launcher.openInBrowser
|
||||
import de.jrpie.android.launcher.writeEmail
|
||||
|
||||
const val EXTRA_CRASH_LOG = "crashLog"
|
||||
|
||||
class ReportCrashActivity : AppCompatActivity() {
|
||||
// We don't know what caused the crash, so this Activity should use as little functionality as possible.
|
||||
// In particular it is not a UIObject (and hence looks quite ugly)
|
||||
private lateinit var binding: ActivityReportCrashBinding
|
||||
private var report: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Initialise layout
|
||||
binding = ActivityReportCrashBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setTitle(R.string.report_crash_title)
|
||||
setSupportActionBar(binding.reportCrashAppbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||
|
||||
report = intent.getStringExtra(EXTRA_CRASH_LOG)
|
||||
|
||||
binding.reportCrashButtonCopy.setOnClickListener {
|
||||
copyToClipboard(this,
|
||||
"Device Info:\n${getDeviceInfo()}\n\nCrash Log:\n${report}")
|
||||
}
|
||||
|
||||
binding.reportCrashButtonMail.setOnClickListener {
|
||||
writeEmail(
|
||||
this,
|
||||
getString(R.string.settings_meta_report_bug_mail),
|
||||
"Crash in μLauncher",
|
||||
"Hi!\nUnfortunately, μLauncher crashed:\n" +
|
||||
"\nDevice Info\n\n${getDeviceInfo()}\n\n" +
|
||||
"\nCrash Log\n\n${report}\n" +
|
||||
"\nAdditional Information\n\n" +
|
||||
"[Please add additional information: What did you do when the crash happened? Do you know how to trigger it? ... ]"
|
||||
)
|
||||
}
|
||||
binding.reportCrashButtonReport.setOnClickListener {
|
||||
openInBrowser(
|
||||
getString(R.string.settings_meta_report_bug_link),
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment
|
|||
import de.jrpie.android.launcher.BuildConfig.VERSION_CODE
|
||||
import de.jrpie.android.launcher.databinding.Tutorial5FinishBinding
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import de.jrpie.android.launcher.requestNotificationPermission
|
||||
import de.jrpie.android.launcher.setDefaultHomeScreen
|
||||
import de.jrpie.android.launcher.ui.UIObject
|
||||
|
||||
|
@ -31,8 +32,10 @@ class TutorialFragment5Finish : Fragment(), UIObject {
|
|||
override fun onStart() {
|
||||
super<Fragment>.onStart()
|
||||
super<UIObject>.onStart()
|
||||
requestNotificationPermission(requireActivity())
|
||||
}
|
||||
|
||||
|
||||
override fun setOnClicks() {
|
||||
super.setOnClicks()
|
||||
binding.tutorialFinishButtonStart.setOnClickListener { finishTutorial() }
|
||||
|
@ -44,6 +47,7 @@ class TutorialFragment5Finish : Fragment(), UIObject {
|
|||
LauncherPreferences.internal().startedTime(System.currentTimeMillis() / 1000L)
|
||||
}
|
||||
context?.let { setDefaultHomeScreen(it, checkDefault = true) }
|
||||
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
|
|
11
app/src/main/res/drawable/baseline_bug_report_24.xml
Normal file
11
app/src/main/res/drawable/baseline_bug_report_24.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="?android:textColor"
|
||||
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z" />
|
||||
|
||||
</vector>
|
59
app/src/main/res/layout/activity_report_crash.xml
Normal file
59
app/src/main/res/layout/activity_report_crash.xml
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".ui.ReportCrashActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:background="@null"
|
||||
app:elevation="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/report_crash_appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<de.jrpie.android.launcher.ui.util.HtmlTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/crash_info" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/report_crash_button_copy"
|
||||
android:text="@string/report_crash_button_copy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp" />
|
||||
<Button
|
||||
android:id="@+id/report_crash_button_mail"
|
||||
android:text="@string/report_crash_button_mail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
<Button
|
||||
android:id="@+id/report_crash_button_report"
|
||||
android:text="@string/report_crash_button_report"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -162,6 +162,7 @@
|
|||
-->
|
||||
<string name="settings_meta_link_github" translatable="false">https://github.com/jrpie/Launcher</string>
|
||||
<string name="settings_meta_report_bug_link" translatable="false">https://github.com/jrpie/Launcher/issues/new?template=bug_report.yaml</string>
|
||||
<string name="settings_meta_report_bug_mail" translatable="false">android-launcher-crash@jrpie.de</string>
|
||||
<string name="settings_meta_report_vulnerability_link" translatable="false">https://github.com/jrpie/Launcher/security/policy</string>
|
||||
<string name="settings_meta_fork_contact_url" translatable="false">https://s.jrpie.de/contact</string>
|
||||
<string name="settings_meta_privacy_url" translatable="false">https://s.jrpie.de/android-legal</string>
|
||||
|
|
|
@ -419,5 +419,28 @@
|
|||
<string name="list_other_open_widget_panel">Open Widget Panel</string>
|
||||
<string name="alert_widget_panel_not_found">This widget panel no longer exists.</string>
|
||||
<string name="settings_launcher_section_widgets">Widgets</string>
|
||||
|
||||
<string name="notification_crash_title">μLauncher crashed</string>
|
||||
<string name="notification_crash_explanation">Sorry! Click for more information.</string>
|
||||
<string name="crash_info"><![CDATA[
|
||||
Looks like something went wrong, sorry about that!<br><br>
|
||||
For privacy reasons, crash logs are not collected automatically.<br>
|
||||
However logs are very useful for debugging, so I would be very grateful if you could send me the attached log by mail
|
||||
or create a bug report on github.<br><br>
|
||||
Note that crash logs might contain <strong>sensitive information</strong>, e.g. the name of an app you tried to launch.
|
||||
Please <strong>redact</strong> such information before sending the report.
|
||||
<h2>What can I do now?</h2>
|
||||
If this bug appears again and again, you can try several things:
|
||||
<ul>
|
||||
<li>Force stop μLauncher</li>
|
||||
<li>Clear μLauncher\'s storage (<strong>Your settings will be lost!</strong>)</li>
|
||||
<li>Install an older version (<a href=\"https://github.com/jrpie/Launcher/releases\">GitHub</a>, <a href=\"https://f-droid.org/en/packages/de.jrpie.android.launcher\">F-Droid</a>)</li>
|
||||
</ul>
|
||||
]]>
|
||||
</string>
|
||||
<string name="report_crash_button_copy">Copy crash report to clipboard</string>
|
||||
<string name="report_crash_button_mail">Send report by mail</string>
|
||||
<string name="report_crash_button_report">Create bug report on GitHub</string>
|
||||
<string name="report_crash_title">μLauncher crashed</string>
|
||||
<string name="send_email">Send Email</string>
|
||||
<string name="notification_channel_crash">Crashes and Debug Information</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
buildscript {
|
||||
ext.kotlin_version = '2.0.0'
|
||||
ext.android_plugin_version = '8.9.2'
|
||||
ext.android_plugin_version = '8.10.0'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -10,7 +10,7 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
classpath 'com.android.tools.build:gradle:8.10.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.android.tools.build:gradle:$android_plugin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue