WAI
Status Update
Comments
ra...@google.com <ra...@google.com> #2
You are marking the Worker
as expedited, but you also need to implement the getForegroundInfoAsync
API defined in ListenableWorker
.
No update yet.
You are marking the Worker
as expedited, but you also need to implement the getForegroundInfoAsync
API defined in ListenableWorker
.
Description
Version used:2.9.0
Devices/Android versions reproduced on: pixel c api 24
If this is a bug in the library, we would appreciate if you could attach:
im using CoroutineWorker for handle download file.. and i get this error only at api 24.. i dont what causes it or how to solve this bug
this my code:
class DownloadWorkManager(context: Context, params:WorkerParameters) : CoroutineWorker(context, params) {
val TAG = "DownloadService"
var shared: SharedPreferenceHelper? = null
val notificationHelper: NotificationHelper2 = NotificationHelper2(applicationContext,
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private var lastState:DownloadState?= null
private val currentState:DownloadState
get() = checkNotNull(lastState)
var download2:Download2? = null
var fileName:String? = null
var modelItem: ModelItem? = null
private val timeLeftEstimator = TimeLeftEstimator()
private val notificationThrottler = Throttler(400)
var isModelDownload:Boolean = true
override suspend fun doWork(): Result {
setForeground(getForegroundInfo())
var job:Job?= null
val id = inputData.getString(DATA_ITEM_ID)
val url = inputData.getString(DATA_URL)!!
val path = inputData.getString(DATA_PATH)
val key = inputData.getString(DATA_KEY) ?: "download_intent"
val intentModel = inputData.getString(DATA_MODEL_ITEM)
fileName = inputData.getString(DATA_NAME) ?: url.getOnlyName(true)
isModelDownload = inputData.getBoolean("is_model", fileName!!.contains(".zip"))
val downloadIntent = Intent(key)
return try {
if (intentModel != null) {
shared = SharedPreferenceHelper(applicationContext)
modelItem = MainActivity.gson.fromJson(intentModel, ModelItem::class.java)
}
Log.e(TAG, "$tags url $url path $path key $key fileName $fileName")
val item = DownloadItem(
id = id.hashCode(),
fileName!!,
path!!,
key,
intentModel != null
)
lastState = DownloadState(item, progress = 0, isIndeterminate = true)
download2 = Download2(context = applicationContext)
download2?.setProgressListener { progress ->
Log.e(TAG, "progress $progress isStopped $isStopped")
job = ioSafe {
if (progress != null) {
if (progress != -1L && !isStopped) {
// notificationHelper?.progress(it)
publishState(
currentState.copy(
isIndeterminate = false,
progress = progress.toInt()
)
)
} else {
onDestroy(downloadIntent)
}
} else {
publishState(
currentState.copy(
isIndeterminate = true,
progress = 100
)
)
// notificationHelper?.taskEnd(NotificationHelper2.EndCause.COMPLETED, null ,fileName = fileName!!)
if (modelItem != null && shared != null) {
modelItem!!.status = ModelListState.DOWNLOADED
shared!!.add(ModelListAdapter.DOWNLOADING_FILE, modelItem!!)
}
}
}
downloadIntent.putExtra("progress", progress?.toInt())
downloadIntent.putExtra("name", modelItem?.name ?: fileName)
applicationContext.sendBroadcast(downloadIntent)
}
//
val fileSaved = File(path, fileName)
if (fileSaved.exists()) {
fileSaved.delete()
}
if (fileSaved.parentFile?.exists()?.not() == true) {
Files.createParentDirs(fileSaved)
// fileSaved.mkdir()
}
fileSaved.createNewFile()
val state = download2?.downloadFile(url, fileSaved)
downloadIntent.putExtra("state", state)
downloadIntent.putExtra("name", modelItem?.name ?: fileName)
applicationContext.sendBroadcast(downloadIntent)
Log.e(TAG, "onFinish state $state")
if(state == -1){
Result.failure(currentState.toWorkData())
}else{
Result.success(currentState.toWorkData())
}
} catch (e:CancellationException){
e.printStackTrace()
Log.e(TAG, "e ${e.message}")
withContext(NonCancellable){
onDestroy(downloadIntent = downloadIntent)
}
throw e
}catch (e:IOException){
e.printStackTrace()
Result.retry()
}catch (e:Exception){
e.printStackTrace()
Result.failure(
currentState.copy(
error = e.message,
eta = -1
).toWorkData()
)
} finally {
notificationHelper.cancel()
job?.cancel()
}
}
override suspend fun getForegroundInfo() = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
ForegroundInfo(
id.hashCode(),
createNotification(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
}else{
ForegroundInfo(
id.hashCode(),
createNotification()
)
}
var isDestroyed = false
val mutex = Mutex()
suspend fun onDestroy(downloadIntent:Intent): Unit = mutex.withLock{
if(isDestroyed.not()){
download2?.cancel()
notificationHelper.cancel()
if(currentState.item.isModel){
fileName?.let {
val file = File(ModelsHelper.getCurrentModelDirectory(context = applicationContext), it)
Log.e(TAG, "onDestroy file ${file.exists()} ${file.absolutePath}")
file.delete()
}
}
download2 = null
isDestroyed = true
main {
downloadIntent.putExtra("progress", -1)
downloadIntent.putExtra("name", modelItem?.name ?: fileName)
applicationContext.sendBroadcast(downloadIntent)
}
throw CancellationException()
}
}
/* fun createNotificationChannel(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val channel = NotificationChannel(
channelId,
"download channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}*/
suspend fun createNotification(): Notification {
return notificationHelper.create(lastState)
}
public suspend fun publishState(state: DownloadState){
val prevState = currentState
lastState = state
if(prevState.isParticularProgress && state.isParticularProgress){
timeLeftEstimator.tick(state.progress.toInt(), 100)
}else{
timeLeftEstimator.emptyTick()
notificationThrottler.reset()
}
val notification = notificationHelper.create(state)
if(state.isFinalState){
notificationManager.notify(id.toString(), id.hashCode(), notification)
}else{
notificationManager.notify(id.hashCode(), notification)
}
setProgress(state.toWorkData())
}
companion object {
private const val TAG ="Download"
private const val DATA_KEY_SUCCESS = "success"
private const val DATA_KEY_FAILED = "failed"
class Scheduler constructor(private val context: Context){
private val workManager: WorkManager
inline get() = WorkManager.getInstance(context)
suspend fun schedule(id:String, url:String, path:String, name:String, key:String, modelItem:String?=null){
val inputData = Data.Builder()
.putString(DATA_ITEM_ID, id)
.putString(DATA_URL, url)
.putString(DATA_PATH, path)
.putString(DATA_NAME, name)
.putString(DATA_KEY, key)
.putString(DATA_MODEL_ITEM, modelItem)
.build()
scheduleImpl(id, listOf(inputData))
}
fun observeWorks(): Flow<List<WorkInfo>> = workManager
.getWorkInfosByTagLiveData(TAG)
.asFlow()
suspend fun getWorkerList(): MutableList<WorkInfo> {
return workManager.getWorkInfosByTag(TAG).await()
}
suspend fun getWorkByName(name: String): MutableList<WorkInfo> {
return workManager.getWorkInfosForUniqueWork(name).await()
}
fun observeWorks(workName:String): Flow<List<WorkInfo>> = workManager
.getWorkInfosForUniqueWorkFlow(workName)
suspend fun cancel(id: UUID) {
workManager.cancelWorkById(id).await()
}
suspend fun cancel(id: String) {
workManager.cancelUniqueWork(id).await()
}
suspend fun cancelAll() {
workManager.cancelAllWorkByTag(TAG).await()
}
suspend fun delete(id: UUID) {
WorkManagerHelper(workManager).deleteWork(id)
}
suspend fun removeCompleted() {
val helper = WorkManagerHelper(workManager)
val finishedWorks = helper.getFinishedWorkInfosByTag(TAG)
helper.deleteWorks(finishedWorks.mapToSet {
}
suspend fun updateConstraints() {
val constraints = Constraints.Builder()
.setRequiresStorageNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val helper = WorkManagerHelper(workManager)
val works = helper.getWorkInfosByTag(TAG)
for (work in works) {
if (work.state.isFinished) {
continue
}
val request = OneTimeWorkRequestBuilder<DownloadWorkManager>()
.setConstraints(constraints)
.setId(
.build()
helper.updateWork(request)
}
}
private suspend fun scheduleImpl(id: String, data:Collection<Data>) {
val constraints = Constraints.Builder()
.setRequiresStorageNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val requests:List<OneTimeWorkRequest> = data.map { inputData ->
OneTimeWorkRequestBuilder<DownloadWorkManager>()
.setConstraints(constraints)
.addTag(TAG)
.keepResultsForAtLeast(7, TimeUnit.DAYS)
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
.setInputData(inputData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
}
workManager.enqueueUniqueWork(id, ExistingWorkPolicy.REPLACE, requests).await()
}
companion object {
suspend fun isWorkActive(context: Context): Boolean {
return DownloadWorkManager.Companion.Scheduler(context).getWorkerList()
.any { it.state == WorkInfo.State.RUNNING }
}
}
}
}
}
this trace error :
Work [ id=1113b5ea-7cfc-4931-97e6-de82c6003847, tags={ com.hamak.videotranslate.data.network.download.DownloadWorkManager, Download } ] failed because it threw an exception/error
java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:317)
at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
Caused by: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
at androidx.work.impl.WorkerWrapper$1.run(WorkerWrapper.java:298)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Caused by: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
at androidx.work.impl.utils.WorkForegroundRunnable$1.run(WorkForegroundRunnable.java:101)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Caused by: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
at androidx.work.ListenableWorker.getForegroundInfoAsync(ListenableWorker.java:257)
at androidx.work.impl.utils.WorkForegroundRunnable.lambda$run$0$androidx-work-impl-utils-WorkForegroundRunnable(WorkForegroundRunnable.java:86)
at androidx.work.impl.utils.WorkForegroundRunnable$$ExternalSyntheticLambda0.run(D8$$SyntheticClass)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)