Coverage Summary for Class: ExportWorker (com.vsevolodganin.clicktrack.export)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
ExportWorker |
0%
(0/4)
|
0%
(0/20)
|
0%
(0/63)
|
0%
(0/364)
|
ExportWorker$Companion |
0%
(0/1)
|
|
0%
(0/5)
|
0%
(0/22)
|
ExportWorker$doWork$1 |
|
ExportWorker$doWork$temporaryFile$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/20)
|
ExportWorker$InputKeys |
|
ExportWorker$NotificationGroups |
|
Total |
0%
(0/6)
|
0%
(0/20)
|
0%
(0/69)
|
0%
(0/406)
|
package com.vsevolodganin.clicktrack.export
import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.net.Uri
import android.os.Build
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.res.ResourcesCompat
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.vsevolodganin.clicktrack.BuildConfig
import com.vsevolodganin.clicktrack.R
import com.vsevolodganin.clicktrack.applicationComponent
import com.vsevolodganin.clicktrack.di.component.ExportWorkerComponent
import com.vsevolodganin.clicktrack.di.component.create
import com.vsevolodganin.clicktrack.model.ClickTrack
import com.vsevolodganin.clicktrack.model.ClickTrackId
import com.vsevolodganin.clicktrack.model.ClickTrackWithDatabaseId
import kotlinx.coroutines.flow.firstOrNull
import kotlin.math.roundToInt
import com.vsevolodganin.clicktrack.multiplatform.R as MR
class ExportWorker(private val appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {
companion object {
fun createWorkRequest(clickTrackId: ClickTrackId.Database): OneTimeWorkRequest {
return OneTimeWorkRequestBuilder<ExportWorker>()
.setInputData(
workDataOf(
InputKeys.CLICK_TRACK_ID to clickTrackId.value,
),
)
.build()
}
}
private val component = ExportWorkerComponent::class.create(appContext.applicationComponent)
// Need unique id to show multiple progress notifications and we can't use string tags
private val foregroundNotificationId = id.leastSignificantBits.toInt()
override suspend fun doWork(): Result {
val clickTrackId = inputData.getLong(InputKeys.CLICK_TRACK_ID, -1)
.takeIf { it >= 0 }
?.let(ClickTrackId::Database)
?: return Result.failure()
val clickTrack = component.clickTrackRepository.getById(clickTrackId).firstOrNull()
?: return Result.failure()
setForeground(foregroundInfo(clickTrack, 0f))
val temporaryFile = component.exportToAudioFile.export(
clickTrack = clickTrack.value,
reportProgress = {
setForeground(foregroundInfo(clickTrack, it))
},
) ?: return Result.failure()
val accessUri = component.mediaStoreAccess.addAudioFile(temporaryFile)
?: return Result.failure()
// Just speeding things up, this file will be deleted by system anyway
temporaryFile.delete()
component.notificationManager.cancel(foregroundNotificationId)
notifyFinished(clickTrack.value, accessUri)
return Result.success()
}
private fun foregroundInfo(clickTrack: ClickTrackWithDatabaseId, progress: Float): ForegroundInfo {
val tapIntent = component.intentFactory.navigateClickTrack(clickTrack.id)
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
val progressResolution = 100
return ForegroundInfo(
foregroundNotificationId,
NotificationCompat.Builder(appContext, component.notificationChannels.export)
.setContentTitle(appContext.getString(R.string.export_worker_notification_in_process, clickTrack.value.name))
.setSmallIcon(R.drawable.ic_notification)
.setColor(ResourcesCompat.getColor(appContext.resources, MR.color.debug_signature, null))
.setOngoing(true)
.setContentIntent(PendingIntent.getActivity(appContext, 0, tapIntent, pendingIntentFlags))
.addAction(
android.R.drawable.ic_delete,
appContext.getString(android.R.string.cancel),
component.workManager.createCancelPendingIntent(id),
)
.setGroup(NotificationGroups.EXPORTING)
.setProgress(progressResolution, (progress * progressResolution).roundToInt(), false)
.build(),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
} else {
0
},
)
}
private fun notifyFinished(clickTrack: ClickTrack, accessUri: Uri) {
val tapIntent = Intent.createChooser(
Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
setDataAndType(accessUri, "audio/*")
},
null,
)
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
if (ActivityCompat.checkSelfPermission(appContext, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
component.notificationManager.notify(
clickTrack.name,
R.id.notification_export_finished,
NotificationCompat.Builder(appContext, component.notificationChannels.export)
.setContentTitle(appContext.getString(R.string.export_worker_notification_finished, clickTrack.name))
.setContentText(appContext.getString(R.string.export_worker_notification_open))
.setSmallIcon(R.drawable.ic_notification)
.setColor(ResourcesCompat.getColor(appContext.resources, MR.color.debug_signature, null))
.setContentIntent(PendingIntent.getActivity(appContext, 0, tapIntent, pendingIntentFlags))
.setGroup(NotificationGroups.EXPORT_FINISHED)
.build(),
)
}
}
private object InputKeys {
const val CLICK_TRACK_ID = "click_track_id"
}
private object NotificationGroups {
const val EXPORTING = "${BuildConfig.APPLICATION_ID}.exporting"
const val EXPORT_FINISHED = "${BuildConfig.APPLICATION_ID}.export_finished"
}
}