Fix crash in SendTootService

This commit is contained in:
Adolfo Santiago 2022-05-28 10:31:50 +02:00
parent a3791a16f7
commit 751cf89b0c
No known key found for this signature in database
GPG key ID: 244D6F9A317B4A65

View file

@ -8,44 +8,59 @@ import android.content.ClipData
import android.content.ClipDescription import android.content.ClipDescription
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.* import android.os.Build
import android.os.IBinder
import android.os.Parcelable
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.ChatMessageDeliveredEvent
import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.* import com.keylesspalace.tusky.appstore.StatusComposedEvent
import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.appstore.StatusPreviewEvent
import com.keylesspalace.tusky.appstore.StatusScheduledEvent
import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.components.drafts.DraftHelper
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.* import com.keylesspalace.tusky.entity.ChatMessage
import com.keylesspalace.tusky.entity.NewChatMessage
import com.keylesspalace.tusky.entity.NewPoll
import com.keylesspalace.tusky.entity.NewStatus
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.Either import com.keylesspalace.tusky.util.Either
import com.keylesspalace.tusky.util.SaveTootHelper import com.keylesspalace.tusky.util.SaveTootHelper
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class SendTootService : Service(), Injectable { class SendTootService : Service(), Injectable {
@Inject @Inject
lateinit var mastodonApi: MastodonApi lateinit var mastodonApi: MastodonApi
@Inject @Inject
lateinit var accountManager: AccountManager lateinit var accountManager: AccountManager
@Inject @Inject
lateinit var eventHub: EventHub lateinit var eventHub: EventHub
@Inject @Inject
lateinit var database: AppDatabase lateinit var database: AppDatabase
@Inject @Inject
lateinit var draftHelper: DraftHelper lateinit var draftHelper: DraftHelper
@Inject @Inject
lateinit var saveTootHelper: SaveTootHelper lateinit var saveTootHelper: SaveTootHelper
@ -66,17 +81,21 @@ class SendTootService : Service(), Injectable {
} }
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
if (intent.hasExtra(KEY_CANCEL)) { if(intent.hasExtra(KEY_CANCEL)) {
cancelSending(intent.getIntExtra(KEY_CANCEL, 0)) cancelSending(intent.getIntExtra(KEY_CANCEL, 0))
return START_NOT_STICKY return START_NOT_STICKY
} }
val postToSend : PostToSend = (intent.getParcelableExtra<TootToSend>(KEY_TOOT) val postToSend: PostToSend = (intent.getParcelableExtra<TootToSend>(KEY_TOOT)
?: intent.getParcelableExtra<MessageToSend>(KEY_CHATMSG)) as PostToSend? ?: intent.getParcelableExtra<MessageToSend>(KEY_CHATMSG)) as PostToSend?
?: throw IllegalStateException("SendTootService started without $KEY_CHATMSG or $KEY_TOOT extra") ?: throw IllegalStateException("SendTootService started without $KEY_CHATMSG or $KEY_TOOT extra")
if (NotificationHelper.NOTIFICATION_USE_CHANNELS) { if(NotificationHelper.NOTIFICATION_USE_CHANNELS) {
val channel = NotificationChannel(CHANNEL_ID, getString(R.string.send_toot_notification_channel_name), NotificationManager.IMPORTANCE_LOW) val channel = NotificationChannel(
CHANNEL_ID,
getString(R.string.send_toot_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
@ -87,9 +106,13 @@ class SendTootService : Service(), Injectable {
.setProgress(1, 0, true) .setProgress(1, 0, true)
.setOngoing(true) .setOngoing(true)
.setColor(ContextCompat.getColor(this, R.color.tusky_blue)) .setColor(ContextCompat.getColor(this, R.color.tusky_blue))
.addAction(0, getString(android.R.string.cancel), cancelSendingIntent(sendingNotificationId)) .addAction(
0,
getString(android.R.string.cancel),
cancelSendingIntent(sendingNotificationId)
)
if (tootsToSend.size == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if(tootsToSend.size == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH) ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH)
startForeground(sendingNotificationId, builder.build()) startForeground(sendingNotificationId, builder.build())
} else { } else {
@ -103,14 +126,13 @@ class SendTootService : Service(), Injectable {
} }
private fun sendToot(tootId: Int) { private fun sendToot(tootId: Int) {
// when tootToSend == null, sending has been canceled // when tootToSend == null, sending has been canceled
val postToSend = tootsToSend[tootId] ?: return val postToSend = tootsToSend[tootId] ?: return
// when account == null, user has logged out, cancel sending // when account == null, user has logged out, cancel sending
val account = accountManager.getAccountById(postToSend.getAccountId()) val account = accountManager.getAccountById(postToSend.getAccountId())
if (account == null) { if(account == null) {
tootsToSend.remove(tootId) tootsToSend.remove(tootId)
notificationManager.cancel(tootId) notificationManager.cancel(tootId)
stopSelfWhenDone() stopSelfWhenDone()
@ -120,8 +142,9 @@ class SendTootService : Service(), Injectable {
postToSend.incrementRetries() postToSend.incrementRetries()
if(postToSend is TootToSend) { if(postToSend is TootToSend) {
val contentType : String? = if(postToSend.formattingSyntax.isNotEmpty()) postToSend.formattingSyntax else null val contentType: String? =
val preview : Boolean? = if(postToSend.preview) true else null if(postToSend.formattingSyntax.isNotEmpty()) postToSend.formattingSyntax else null
val preview: Boolean? = if(postToSend.preview) true else null
val newStatus = NewStatus( val newStatus = NewStatus(
postToSend.text, postToSend.text,
@ -149,20 +172,23 @@ class SendTootService : Service(), Injectable {
val scheduled = !postToSend.scheduledAt.isNullOrEmpty() val scheduled = !postToSend.scheduledAt.isNullOrEmpty()
tootsToSend.remove(tootId) tootsToSend.remove(tootId)
if (response.isSuccessful) { if(response.isSuccessful) {
// If the status was loaded from a draft, delete the draft and associated media files. // If the status was loaded from a draft, delete the draft and associated media files.
if (postToSend.savedTootUid != 0) { if(postToSend.savedTootUid != 0) {
saveTootHelper.deleteDraft(postToSend.savedTootUid) saveTootHelper.deleteDraft(postToSend.savedTootUid)
} }
if (postToSend.draftId != 0) { if(postToSend.draftId != 0) {
draftHelper.deleteDraftAndAttachments(postToSend.draftId) draftHelper.deleteDraftAndAttachments(postToSend.draftId)
.subscribe() .subscribe()
} }
when { when {
postToSend.preview -> response.body()?.let(::StatusPreviewEvent)?.let(eventHub::dispatch) postToSend.preview -> response.body()?.let(::StatusPreviewEvent)
scheduled -> response.body()?.let(::StatusScheduledEvent)?.let(eventHub::dispatch) ?.let(eventHub::dispatch)
else -> response.body()?.let(::StatusComposedEvent)?.let(eventHub::dispatch) scheduled -> response.body()?.let(::StatusScheduledEvent)
?.let(eventHub::dispatch)
else -> response.body()?.let(::StatusComposedEvent)
?.let(eventHub::dispatch)
} }
notificationManager.cancel(tootId) notificationManager.cancel(tootId)
@ -174,7 +200,12 @@ class SendTootService : Service(), Injectable {
.setSmallIcon(R.drawable.ic_notify) .setSmallIcon(R.drawable.ic_notify)
.setContentTitle(getString(R.string.send_toot_notification_error_title)) .setContentTitle(getString(R.string.send_toot_notification_error_title))
.setContentText(getString(R.string.send_toot_notification_saved_content)) .setContentText(getString(R.string.send_toot_notification_saved_content))
.setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) .setColor(
ContextCompat.getColor(
this@SendTootService,
R.color.tusky_blue
)
)
notificationManager.cancel(tootId) notificationManager.cancel(tootId)
notificationManager.notify(errorNotificationId--, builder.build()) notificationManager.notify(errorNotificationId--, builder.build())
@ -187,7 +218,7 @@ class SendTootService : Service(), Injectable {
override fun onFailure(call: Call<Status>, t: Throwable) { override fun onFailure(call: Call<Status>, t: Throwable) {
var backoff = TimeUnit.SECONDS.toMillis(postToSend.retries.toLong()) var backoff = TimeUnit.SECONDS.toMillis(postToSend.retries.toLong())
if (backoff > MAX_RETRY_INTERVAL) { if(backoff > MAX_RETRY_INTERVAL) {
backoff = MAX_RETRY_INTERVAL backoff = MAX_RETRY_INTERVAL
} }
@ -215,7 +246,7 @@ class SendTootService : Service(), Injectable {
override fun onResponse(call: Call<ChatMessage>, response: Response<ChatMessage>) { override fun onResponse(call: Call<ChatMessage>, response: Response<ChatMessage>) {
tootsToSend.remove(tootId) tootsToSend.remove(tootId)
if (response.isSuccessful) { if(response.isSuccessful) {
notificationManager.cancel(tootId) notificationManager.cancel(tootId)
eventHub.dispatch(ChatMessageDeliveredEvent(response.body()!!)) eventHub.dispatch(ChatMessageDeliveredEvent(response.body()!!))
@ -224,7 +255,12 @@ class SendTootService : Service(), Injectable {
.setSmallIcon(R.drawable.ic_notify) .setSmallIcon(R.drawable.ic_notify)
.setContentTitle(getString(R.string.send_toot_notification_error_title)) .setContentTitle(getString(R.string.send_toot_notification_error_title))
.setContentText(getString(R.string.send_toot_notification_saved_content)) .setContentText(getString(R.string.send_toot_notification_saved_content))
.setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) .setColor(
ContextCompat.getColor(
this@SendTootService,
R.color.tusky_blue
)
)
notificationManager.cancel(tootId) notificationManager.cancel(tootId)
notificationManager.notify(errorNotificationId--, builder.build()) notificationManager.notify(errorNotificationId--, builder.build())
@ -235,7 +271,7 @@ class SendTootService : Service(), Injectable {
override fun onFailure(call: Call<ChatMessage>, t: Throwable) { override fun onFailure(call: Call<ChatMessage>, t: Throwable) {
var backoff = TimeUnit.SECONDS.toMillis(postToSend.retries.toLong()) var backoff = TimeUnit.SECONDS.toMillis(postToSend.retries.toLong())
if (backoff > MAX_RETRY_INTERVAL) { if(backoff > MAX_RETRY_INTERVAL) {
backoff = MAX_RETRY_INTERVAL backoff = MAX_RETRY_INTERVAL
} }
@ -253,8 +289,7 @@ class SendTootService : Service(), Injectable {
} }
private fun stopSelfWhenDone() { private fun stopSelfWhenDone() {
if(tootsToSend.isEmpty()) {
if (tootsToSend.isEmpty()) {
ServiceCompat.stopForeground(this@SendTootService, ServiceCompat.STOP_FOREGROUND_REMOVE) ServiceCompat.stopForeground(this@SendTootService, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf() stopSelf()
} }
@ -262,7 +297,7 @@ class SendTootService : Service(), Injectable {
private fun cancelSending(tootId: Int) { private fun cancelSending(tootId: Int) {
val tootToCancel = tootsToSend.remove(tootId) val tootToCancel = tootsToSend.remove(tootId)
if (tootToCancel != null) { if(tootToCancel != null) {
val sendCall = sendCalls.remove(tootId) val sendCall = sendCalls.remove(tootId)
sendCall?.let { sendCall?.let {
@ -296,7 +331,6 @@ class SendTootService : Service(), Injectable {
} }
private fun saveTootToDrafts(toot: TootToSend) { private fun saveTootToDrafts(toot: TootToSend) {
draftHelper.saveDraft( draftHelper.saveDraft(
draftId = toot.draftId, draftId = toot.draftId,
accountId = toot.getAccountId(), accountId = toot.getAccountId(),
@ -314,14 +348,21 @@ class SendTootService : Service(), Injectable {
} }
private fun cancelSendingIntent(tootId: Int): PendingIntent { private fun cancelSendingIntent(tootId: Int): PendingIntent {
val intent = Intent(this, SendTootService::class.java) val intent = Intent(this, SendTootService::class.java)
intent.putExtra(KEY_CANCEL, tootId) intent.putExtra(KEY_CANCEL, tootId)
val flags =
return PendingIntent.getService(this, tootId, intent, PendingIntent.FLAG_UPDATE_CURRENT) if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
return PendingIntent.getService(
this,
tootId,
intent,
flags
)
} }
companion object { companion object {
@ -333,7 +374,8 @@ class SendTootService : Service(), Injectable {
private val MAX_RETRY_INTERVAL = TimeUnit.MINUTES.toMillis(1) private val MAX_RETRY_INTERVAL = TimeUnit.MINUTES.toMillis(1)
private var sendingNotificationId = -1 // use negative ids to not clash with other notis private var sendingNotificationId = -1 // use negative ids to not clash with other notis
private var errorNotificationId = Int.MIN_VALUE // use even more negative ids to not clash with other notis private var errorNotificationId =
Int.MIN_VALUE // use even more negative ids to not clash with other notis
private fun Intent.forwardUriPermissions(mediaUris: List<String>) { private fun Intent.forwardUriPermissions(mediaUris: List<String>) {
if(mediaUris.isEmpty()) if(mediaUris.isEmpty())
@ -372,8 +414,8 @@ class SendTootService : Service(), Injectable {
} }
interface PostToSend { interface PostToSend {
fun getAccountId() : Long fun getAccountId(): Long
fun getNotificationText() : String fun getNotificationText(): String
fun incrementRetries() fun incrementRetries()
} }
@ -390,7 +432,7 @@ data class MessageToSend(
return accountId return accountId
} }
override fun getNotificationText() : String { override fun getNotificationText(): String {
return text return text
} }
@ -421,7 +463,7 @@ data class TootToSend(
val idempotencyKey: String, val idempotencyKey: String,
var retries: Int var retries: Int
) : Parcelable, PostToSend { ) : Parcelable, PostToSend {
override fun getNotificationText() : String { override fun getNotificationText(): String {
return if(warningText.isBlank()) text else warningText return if(warningText.isBlank()) text else warningText
} }