Skip to content

Commit

Permalink
Workaround trampoline exception by using an activity instead of a bro…
Browse files Browse the repository at this point in the history
…adcast receiver. Closes #1191
  • Loading branch information
F43nd1r committed Apr 23, 2023
1 parent 74803a0 commit 9cbbe01
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.annotation.RequiresApi
import org.acra.config.CoreConfiguration
import org.acra.sender.JobSenderService
import org.acra.sender.LegacySenderService
import org.acra.sender.ReportSender
import org.acra.sender.SendingConductor
import org.acra.util.IOUtils
import org.acra.util.toPersistableBundle
Expand All @@ -43,8 +44,7 @@ open class DefaultSenderScheduler(private val context: Context, private val conf
extras.putString(LegacySenderService.EXTRA_ACRA_CONFIG, IOUtils.serialize(config))
extras.putBoolean(LegacySenderService.EXTRA_ONLY_SEND_SILENT_REPORTS, onlySendSilentReports)
configureExtras(extras)
val conductor = SendingConductor(context, config)
if (conductor.getSenderInstances(false).isNotEmpty()) {
if (ReportSender.hasBackgroundSenders(context, config)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
val scheduler = (context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler)
val builder = JobInfo.Builder(0, ComponentName(context, JobSenderService::class.java)).setExtras(extras.toPersistableBundle())
Expand All @@ -57,8 +57,8 @@ open class DefaultSenderScheduler(private val context: Context, private val conf
context.startService(intent)
}
}
if (conductor.getSenderInstances(true).isNotEmpty()) {
conductor.sendReports(true, extras)
if (ReportSender.hasForegroundSenders(context, config)) {
SendingConductor(context, config).sendReports(true, extras)
}
}

Expand Down
17 changes: 16 additions & 1 deletion acra-core/src/main/java/org/acra/sender/ReportSender.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package org.acra.sender
import android.content.Context
import android.os.Bundle
import org.acra.annotation.OpenAPI
import org.acra.config.CoreConfiguration
import org.acra.data.CrashReportData
import org.acra.sender.ReportSenderException
import org.acra.log.debug
import org.acra.plugins.loadEnabled

/**
* A simple interface for defining various crash report senders.
Expand Down Expand Up @@ -62,4 +64,17 @@ interface ReportSender {
fun requiresForeground(): Boolean {
return false
}

companion object {
fun loadSenders(context: Context, config: CoreConfiguration): List<ReportSender> {
debug { "Using PluginLoader to find ReportSender factories" }
val factories: List<ReportSenderFactory> = config.pluginLoader.loadEnabled(config)
debug { "reportSenderFactories : $factories" }
return factories.map { it.create(context, config).also { debug { "Adding reportSender : $it" } } }
}

fun hasForegroundSenders(context: Context, config: CoreConfiguration) = loadSenders(context, config).any { it.requiresForeground() }

fun hasBackgroundSenders(context: Context, config: CoreConfiguration) = loadSenders(context, config).any { !it.requiresForeground() }
}
}
12 changes: 2 additions & 10 deletions acra-core/src/main/java/org/acra/sender/SendingConductor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class SendingConductor(private val context: Context, private val config: CoreCon
fun sendReports(foreground: Boolean, extras: Bundle) {
debug { "About to start sending reports from SenderService" }
try {
val senderInstances = getSenderInstances(foreground).toMutableList()
val senderInstances = ReportSender.loadSenders(context, config).filter { it.requiresForeground() == foreground }.toMutableList()
if (senderInstances.isEmpty()) {
debug { "No ReportSenders configured - adding NullSender" }
senderInstances.add(NullSender())
Expand Down Expand Up @@ -52,7 +52,7 @@ class SendingConductor(private val context: Context, private val config: CoreCon
}
}
val toast: String? = if (reportsSentCount > 0) config.reportSendSuccessToast else config.reportSendFailureToast
if (anyNonSilent && toast != null && toast.isNotEmpty()) {
if (anyNonSilent && !toast.isNullOrEmpty()) {
debug { "About to show " + (if (reportsSentCount > 0) "success" else "failure") + " toast" }
Handler(Looper.getMainLooper()).post { sendToast(context, toast, Toast.LENGTH_LONG) }
}
Expand All @@ -61,12 +61,4 @@ class SendingConductor(private val context: Context, private val config: CoreCon
}
debug { "Finished sending reports from SenderService" }
}

fun getSenderInstances(foreground: Boolean): List<ReportSender> {
debug { "Using PluginLoader to find ReportSender factories" }
val factories: List<ReportSenderFactory> = config.pluginLoader.loadEnabled(config)
debug { "reportSenderFactories : $factories" }
return factories.map { it.create(context, config).also { debug { "Adding reportSender : $it" } } }.filter { foreground == it.requiresForeground() }
}

}
14 changes: 11 additions & 3 deletions acra-notification/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<receiver
android:name="org.acra.receiver.NotificationBroadcastReceiver"
android:exported="false"
android:process=":acra"/>
android:name="org.acra.receiver.NotificationBroadcastReceiver"
android:exported="false"
android:process=":acra" />
<activity
android:name="org.acra.receiver.NotificationActivity"
android:exported="false"
android:process=":acra"
android:noHistory="true"
android:excludeFromRecents="true"
android:enabled="@bool/acra_enable_notification_activity"
/>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ import org.acra.config.getPluginConfiguration
import org.acra.notification.R
import org.acra.plugins.HasConfigPlugin
import org.acra.prefs.SharedPreferencesFactory
import org.acra.receiver.NotificationActivity
import org.acra.receiver.NotificationBroadcastReceiver
import org.acra.sender.LegacySenderService
import org.acra.sender.LegacySenderService.Companion.EXTRA_ACRA_CONFIG
import org.acra.sender.ReportSender
import java.io.File

/**
Expand Down Expand Up @@ -107,11 +109,21 @@ class NotificationInteraction : HasConfigPlugin(NotificationConfiguration::class
}

private fun getSendIntent(context: Context, config: CoreConfiguration, reportFile: File): PendingIntent {
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
intent.action = INTENT_ACTION_SEND
intent.putExtra(LegacySenderService.EXTRA_ACRA_CONFIG, config)
intent.putExtra(EXTRA_REPORT_FILE, reportFile)
return PendingIntent.getBroadcast(context, ACTION_SEND, intent, pendingIntentFlags)
val needsForeground = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ReportSender.hasForegroundSenders(context, config)
return if (needsForeground) {
val intent = Intent(context, NotificationActivity::class.java)
intent.action = INTENT_ACTION_SEND
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra(EXTRA_ACRA_CONFIG, config)
intent.putExtra(EXTRA_REPORT_FILE, reportFile)
PendingIntent.getActivity(context, ACTION_SEND, intent, pendingIntentFlags)
} else {
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
intent.action = INTENT_ACTION_SEND
intent.putExtra(EXTRA_ACRA_CONFIG, config)
intent.putExtra(EXTRA_REPORT_FILE, reportFile)
PendingIntent.getBroadcast(context, ACTION_SEND, intent, pendingIntentFlags)
}
}

private fun getDiscardIntent(context: Context): PendingIntent {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2023
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.acra.receiver

import android.app.Activity

class NotificationActivity : Activity() {
override fun onStart() {
super.onStart()
handleNotificationIntent(this, intent)
finish()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,70 +18,13 @@ package org.acra.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.core.app.RemoteInput
import org.acra.ReportField
import org.acra.config.CoreConfiguration
import org.acra.file.BulkReportDeleter
import org.acra.file.CrashReportPersister
import org.acra.interaction.NotificationInteraction
import org.acra.log.debug
import org.acra.log.error
import org.acra.log.warn
import org.acra.scheduler.SchedulerStarter
import org.acra.sender.LegacySenderService
import org.acra.util.SystemServices.getNotificationManager
import org.acra.util.kGetSerializableExtra
import org.json.JSONException
import java.io.File
import java.io.IOException

/**
* @author F43nd1r
* @since 15.09.2017
*/
class NotificationBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
debug { "NotificationBroadcastReceiver called with $intent" }
val notificationManager = getNotificationManager(context)
notificationManager.cancel(NotificationInteraction.NOTIFICATION_ID)
debug { "notification intent action = ${intent.action}" }
if (intent.action != null) {
when (intent.action) {
NotificationInteraction.INTENT_ACTION_SEND -> {
val reportFile = intent.kGetSerializableExtra<File>(NotificationInteraction.EXTRA_REPORT_FILE)
val configObject = intent.kGetSerializableExtra<CoreConfiguration>(LegacySenderService.EXTRA_ACRA_CONFIG)
if (configObject != null && reportFile != null) {
//Grab user comment from notification intent
val remoteInput = RemoteInput.getResultsFromIntent(intent)
if (remoteInput != null) {
val comment = remoteInput.getCharSequence(NotificationInteraction.KEY_COMMENT)
if (comment != null && "" != comment.toString()) {
val persister = CrashReportPersister()
try {
debug { "Add user comment to $reportFile" }
val crashData = persister.load(reportFile)
crashData.put(ReportField.USER_COMMENT, comment.toString())
persister.store(crashData, reportFile)
} catch (e: IOException) {
warn(e) { "User comment not added: " }
} catch (e: JSONException) {
warn(e) { "User comment not added: " }
}
}
}
SchedulerStarter(context, configObject).scheduleReports(reportFile, false)
}
}

NotificationInteraction.INTENT_ACTION_DISCARD -> {
debug { "Discarding reports" }
BulkReportDeleter(context).deleteReports(false, 0)
}
}
}
} catch (t: Exception) {
error(t) { "Failed to handle notification action" }
}
handleNotificationIntent(context, intent)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2023
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.acra.receiver

import android.content.Context
import android.content.Intent
import androidx.core.app.RemoteInput
import org.acra.ReportField
import org.acra.config.CoreConfiguration
import org.acra.file.BulkReportDeleter
import org.acra.file.CrashReportPersister
import org.acra.interaction.NotificationInteraction
import org.acra.log.debug
import org.acra.log.warn
import org.acra.scheduler.SchedulerStarter
import org.acra.sender.LegacySenderService
import org.acra.util.SystemServices
import org.acra.util.kGetSerializableExtra
import org.json.JSONException
import java.io.File
import java.io.IOException

fun handleNotificationIntent(context: Context, intent: Intent) {
try {
debug { "NotificationBroadcastReceiver called with $intent" }
val notificationManager = SystemServices.getNotificationManager(context)
notificationManager.cancel(NotificationInteraction.NOTIFICATION_ID)
debug { "notification intent action = ${intent.action}" }
if (intent.action != null) {
when (intent.action) {
NotificationInteraction.INTENT_ACTION_SEND -> {
val reportFile = intent.kGetSerializableExtra<File>(NotificationInteraction.EXTRA_REPORT_FILE)
val configObject = intent.kGetSerializableExtra<CoreConfiguration>(LegacySenderService.EXTRA_ACRA_CONFIG)
if (configObject != null && reportFile != null) {
//Grab user comment from notification intent
val remoteInput = RemoteInput.getResultsFromIntent(intent)
if (remoteInput != null) {
val comment = remoteInput.getCharSequence(NotificationInteraction.KEY_COMMENT)
if (comment != null && "" != comment.toString()) {
val persister = CrashReportPersister()
try {
debug { "Add user comment to $reportFile" }
val crashData = persister.load(reportFile)
crashData.put(ReportField.USER_COMMENT, comment.toString())
persister.store(crashData, reportFile)
} catch (e: IOException) {
warn(e) { "User comment not added: " }
} catch (e: JSONException) {
warn(e) { "User comment not added: " }
}
}
}
SchedulerStarter(context, configObject).scheduleReports(reportFile, false)
}
}

NotificationInteraction.INTENT_ACTION_DISCARD -> {
debug { "Discarding reports" }
BulkReportDeleter(context).deleteReports(false, 0)
}
}
}
} catch (t: Exception) {
org.acra.log.error(t) { "Failed to handle notification action" }
}
}
20 changes: 20 additions & 0 deletions acra-notification/src/main/res/values-v31/values.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<resources>
<bool name="acra_enable_notification_activity">true</bool>
</resources>
20 changes: 20 additions & 0 deletions acra-notification/src/main/res/values/values.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<resources>
<bool name="acra_enable_notification_activity">false</bool>
</resources>

0 comments on commit 9cbbe01

Please sign in to comment.