Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
[components] Issue mozilla-mobile/android-components#1705: Add UI for…
Browse files Browse the repository at this point in the history
… listing past recorded crashes.
  • Loading branch information
pocmo committed May 8, 2020
1 parent 08465a4 commit 684e92c
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 2 deletions.
1 change: 1 addition & 0 deletions android-components/components/lib/crash/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {

implementation Dependencies.androidx_appcompat
implementation Dependencies.androidx_constraintlayout
implementation Dependencies.androidx_recyclerview

implementation project(':support-base')
implementation project(':support-ktx')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.lib.crash.ui

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.lib.crash.R

/**
* Activity for displaying the list of reported crashes.
*/
abstract class AbstractCrashListActivity : AppCompatActivity() {
abstract val crashReporter: CrashReporter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setTitle(R.string.mozac_lib_crash_activity_title)
setContentView(R.layout.mozac_lib_crash_activity_crashlist)

if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.container, CrashListFragment())
.commit()
}
}

/**
* Gets invoked whenever the user selects a crash reporting service.
*
* @param url URL pointing to the crash report for the selected crash reporting service.
*/
abstract fun onCrashServiceSelected(url: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.lib.crash.ui

import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.format.DateUtils
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.lib.crash.R
import mozilla.components.lib.crash.db.CrashWithReports
import mozilla.components.lib.crash.db.ReportEntity

/**
* RecyclerView adapter for displaying the list of crashes.
*/
internal class CrashListAdapter(
private val crashReporter: CrashReporter,
private val onSelection: (String) -> Unit
) : RecyclerView.Adapter<CrashViewHolder>() {
private var crashes: List<CrashWithReports> = emptyList()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrashViewHolder {
val view = LayoutInflater.from(
parent.context
).inflate(
R.layout.mozac_lib_crash_item_crash,
parent,
false
)

return CrashViewHolder(view)
}

override fun getItemCount(): Int {
return crashes.size
}

override fun onBindViewHolder(holder: CrashViewHolder, position: Int) {
val crashWithReports = crashes[position]

holder.idView.text = crashWithReports.crash.uuid

holder.titleView.text = crashWithReports.crash.stacktrace.lines().first()

val time = DateUtils.getRelativeDateTimeString(
holder.footerView.context,
crashWithReports.crash.createdAt,
DateUtils.MINUTE_IN_MILLIS,
DateUtils.WEEK_IN_MILLIS,
0
)

holder.footerView.text = SpannableStringBuilder(time).apply {
if (crashWithReports.reports.isNotEmpty()) {
append(" - ")
append(crashReporter, crashWithReports.reports, onSelection)
}
}
}

fun updateList(list: List<CrashWithReports>) {
crashes = list
notifyDataSetChanged()
}
}

internal class CrashViewHolder(
view: View
) : RecyclerView.ViewHolder(
view
) {
val titleView = view.findViewById<TextView>(R.id.mozac_lib_crash_title)
val idView = view.findViewById<TextView>(R.id.mozac_lib_crash_id)
val footerView = view.findViewById<TextView>(R.id.mozac_lib_crash_footer).apply {
movementMethod = LinkMovementMethod.getInstance()
}
}

private fun SpannableStringBuilder.append(
crashReporter: CrashReporter,
services: List<ReportEntity>,
onSelection: (String) -> Unit
): SpannableStringBuilder {
services.forEachIndexed { index, entity ->
val name = crashReporter.getCrashReporterServiceById(entity.serviceId).name
val url = crashReporter.getCrashReporterServiceById(entity.serviceId)
.createCrashReportUrl(entity.reportId)

if (url != null) {
append(name, object : ClickableSpan() {
override fun onClick(widget: View) {
onSelection(url)
}
}, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
} else {
append(name)
}

if (index < services.lastIndex) {
append(" ")
}
}
return this
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.lib.crash.ui

import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.lib.crash.R
import mozilla.components.lib.crash.db.CrashDatabase

/**
* Fragment displaying the list of crashes.
*/
internal class CrashListFragment : Fragment(R.layout.mozac_lib_crash_crashlist) {
private val database by lazy { CrashDatabase.get(requireContext()) }
private val reporter by lazy { (activity as AbstractCrashListActivity).crashReporter }

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val listView: RecyclerView = view.findViewById(R.id.mozac_lib_crash_list)
listView.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.VERTICAL,
false
)

val emptyView = view.findViewById<TextView>(R.id.mozac_lib_crash_empty)

val adapter = CrashListAdapter(reporter, ::onSelection)
listView.adapter = adapter

val dividerItemDecoration = DividerItemDecoration(
requireContext(),
LinearLayoutManager.VERTICAL
)
listView.addItemDecoration(dividerItemDecoration)

database.crashDao().getCrashesWithReports().observe(viewLifecycleOwner, Observer { list ->
if (list.isEmpty()) {
emptyView.visibility = View.VISIBLE
} else {
adapter.updateList(list)
}
})
}

private fun onSelection(url: String) {
(activity!! as AbstractCrashListActivity).onCrashServiceSelected(url)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

</FrameLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mozac_lib_crash_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<TextView
android:id="@+id/mozac_lib_crash_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/mozac_lib_crash_no_crashes"
android:visibility="gone" />

</FrameLayout>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- This Source Code Form is subject to the terms of the Mozilla Public
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="4dp">

<TextView
android:id="@+id/mozac_lib_crash_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:textSize="10sp"
tools:text="15b666ae-fc9d-41d1-a5c0-8af6961a22d4"
tools:ignore="SmallSp" />

<TextView
android:id="@+id/mozac_lib_crash_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:textSize="14sp"
android:textStyle="bold"
tools:text="java.lang.RuntimeException: Background crash" />

<TextView
android:id="@+id/mozac_lib_crash_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:textSize="12sp"
tools:text="12 minutes ago - Sentry Socorro"/>

</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@

<!-- Label of notification showing that the crash report service is running. %1$s will be replaced with the name of the organization (e.g. Mozilla). -->
<string name="mozac_lib_send_crash_report_in_progress">Sending crash report to %1$s</string>

<!-- Title of the activity that shows the list of past crashes (similar to about:crashes)-->
<string name="mozac_lib_crash_activity_title">Crash Reports</string>

<!-- Text shown instead of crash list if no crashes have been submitted yet -->
<string name="mozac_lib_crash_no_crashes">No crash reports have been submitted.</string>
</resources>
3 changes: 3 additions & 0 deletions android-components/samples/crash/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".CrashListActivity" android:exported="false" />

<service android:name=".CrashService"
android:process=":samples.crash.service" />
</application>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import androidx.core.content.ContextCompat
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_crash.*
import mozilla.components.lib.crash.Crash
Expand Down Expand Up @@ -41,6 +41,7 @@ class CrashActivity : AppCompatActivity(), View.OnClickListener {
fatalCrashButton.setOnClickListener(this)
crashButton.setOnClickListener(this)
fatalServiceCrashButton.setOnClickListener(this)
crashList.setOnClickListener(this)

crashReporter.recordCrashBreadcrumb(
Breadcrumb("CrashActivity onCreate", emptyMap(), "sample", Breadcrumb.Level.DEBUG,
Expand Down Expand Up @@ -115,6 +116,12 @@ class CrashActivity : AppCompatActivity(), View.OnClickListener {
startService(Intent(this, CrashService::class.java))
finish()
}

crashList -> {
startActivity(Intent(this, CrashListActivity::class.java))
}

else -> throw java.lang.RuntimeException("Unknown ID")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.samples.crash

import android.widget.Toast
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.lib.crash.ui.AbstractCrashListActivity

/**
* Activity showing list of past crashes.
*/
class CrashListActivity : AbstractCrashListActivity() {
override val crashReporter: CrashReporter
get() = (application as CrashApplication).crashReporter

override fun onCrashServiceSelected(url: String) {
Toast.makeText(this, "Go to: $url", Toast.LENGTH_SHORT).show()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
Expand All @@ -22,4 +23,10 @@
android:layout_height="wrap_content"
android:text="@string/crash_fatal_service"/>

<Button
android:id="@+id/crashList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/list_of_crashes" />

</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
<string name="crash_fatal">Crash (Fatal)</string>
<string name="crash_nonfatal">Crash (Non-Fatal)</string>
<string name="crash_fatal_service">Crash (Fatal; background service)</string>
<string name="list_of_crashes">List of crashes</string>
</resources>

0 comments on commit 684e92c

Please sign in to comment.