diff --git a/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.kt b/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.kt index 7aab4dd2f7..a2557afa4b 100644 --- a/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.kt +++ b/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.kt @@ -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 @@ -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()) @@ -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) } } diff --git a/acra-core/src/main/java/org/acra/sender/ReportSender.kt b/acra-core/src/main/java/org/acra/sender/ReportSender.kt index 68d0f605bf..db3316c27b 100644 --- a/acra-core/src/main/java/org/acra/sender/ReportSender.kt +++ b/acra-core/src/main/java/org/acra/sender/ReportSender.kt @@ -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. @@ -62,4 +64,17 @@ interface ReportSender { fun requiresForeground(): Boolean { return false } + + companion object { + fun loadSenders(context: Context, config: CoreConfiguration): List { + debug { "Using PluginLoader to find ReportSender factories" } + val factories: List = 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() } + } } \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/SendingConductor.kt b/acra-core/src/main/java/org/acra/sender/SendingConductor.kt index 03bea1590a..bc3f7f41da 100644 --- a/acra-core/src/main/java/org/acra/sender/SendingConductor.kt +++ b/acra-core/src/main/java/org/acra/sender/SendingConductor.kt @@ -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()) @@ -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) } } @@ -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 { - debug { "Using PluginLoader to find ReportSender factories" } - val factories: List = config.pluginLoader.loadEnabled(config) - debug { "reportSenderFactories : $factories" } - return factories.map { it.create(context, config).also { debug { "Adding reportSender : $it" } } }.filter { foreground == it.requiresForeground() } - } - } \ No newline at end of file diff --git a/acra-notification/src/main/AndroidManifest.xml b/acra-notification/src/main/AndroidManifest.xml index 38812566df..23c0f577a8 100644 --- a/acra-notification/src/main/AndroidManifest.xml +++ b/acra-notification/src/main/AndroidManifest.xml @@ -19,8 +19,16 @@ + android:name="org.acra.receiver.NotificationBroadcastReceiver" + android:exported="false" + android:process=":acra" /> + diff --git a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.kt b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.kt index 2728df2c79..e356475ce1 100644 --- a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.kt +++ b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.kt @@ -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 /** @@ -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 { diff --git a/acra-notification/src/main/java/org/acra/receiver/NotificationActivity.kt b/acra-notification/src/main/java/org/acra/receiver/NotificationActivity.kt new file mode 100644 index 0000000000..de6bef76ae --- /dev/null +++ b/acra-notification/src/main/java/org/acra/receiver/NotificationActivity.kt @@ -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() + } +} \ No newline at end of file diff --git a/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.kt b/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.kt index f9c48e0f59..794684e041 100644 --- a/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.kt +++ b/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.kt @@ -18,22 +18,6 @@ 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 @@ -41,47 +25,6 @@ import java.io.IOException */ 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(NotificationInteraction.EXTRA_REPORT_FILE) - val configObject = intent.kGetSerializableExtra(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) } } \ No newline at end of file diff --git a/acra-notification/src/main/java/org/acra/receiver/handleNotificationIntent.kt b/acra-notification/src/main/java/org/acra/receiver/handleNotificationIntent.kt new file mode 100644 index 0000000000..2e96057627 --- /dev/null +++ b/acra-notification/src/main/java/org/acra/receiver/handleNotificationIntent.kt @@ -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(NotificationInteraction.EXTRA_REPORT_FILE) + val configObject = intent.kGetSerializableExtra(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" } + } +} \ No newline at end of file diff --git a/acra-notification/src/main/res/values-v31/values.xml b/acra-notification/src/main/res/values-v31/values.xml new file mode 100644 index 0000000000..b67a992398 --- /dev/null +++ b/acra-notification/src/main/res/values-v31/values.xml @@ -0,0 +1,20 @@ + + + + + true + \ No newline at end of file diff --git a/acra-notification/src/main/res/values/values.xml b/acra-notification/src/main/res/values/values.xml new file mode 100644 index 0000000000..ef581cdf86 --- /dev/null +++ b/acra-notification/src/main/res/values/values.xml @@ -0,0 +1,20 @@ + + + + + false + \ No newline at end of file