@@ -21,6 +21,7 @@ import com.looker.droidify.model.ProductItem
2121import com.looker.droidify.model.Repository
2222import com.looker.droidify.network.DataSize
2323import com.looker.droidify.network.percentBy
24+ import com.looker.droidify.receivers.CopyErrorReceiver
2425import com.looker.droidify.utility.common.Constants
2526import com.looker.droidify.utility.common.SdkCheck
2627import com.looker.droidify.utility.common.createNotificationChannel
@@ -283,20 +284,59 @@ class SyncService : ConnectionService<SyncService.Binder>() {
283284 }
284285
285286 private fun showNotificationError (repository : Repository , exception : Exception ) {
286- val description = getString(
287- when (exception) {
288- is RepositoryUpdater . UpdateException -> when (exception.errorType) {
287+ val errorTypeDesc = when (exception) {
288+ is RepositoryUpdater . UpdateException -> getString(
289+ when (exception.errorType) {
289290 RepositoryUpdater .ErrorType .NETWORK -> stringRes.network_error_DESC
290291 RepositoryUpdater .ErrorType .HTTP -> stringRes.http_error_DESC
291292 RepositoryUpdater .ErrorType .VALIDATION -> stringRes.validation_index_error_DESC
292293 RepositoryUpdater .ErrorType .PARSING -> stringRes.parsing_index_error_DESC
293- }
294+ },
295+ )
296+
297+ else -> getString(stringRes.unknown_error_DESC)
298+ }
299+
300+ val description = buildString {
301+ append(errorTypeDesc)
302+ if (! exception.message.isNullOrBlank()) {
303+ append(" : " )
304+ append(exception.message)
305+ }
306+ }
307+
308+ val fullErrorDetails = buildString {
309+ appendLine(" Repository: ${repository.name} " )
310+ appendLine(" Address: ${repository.address} " )
311+ appendLine(" Error Type: $errorTypeDesc " )
312+ appendLine(" Message: ${exception.message ? : " N/A" } " )
313+ appendLine()
314+ appendLine(" Stack Trace:" )
315+ appendLine(exception.stackTraceToString())
316+ exception.cause?.let { cause ->
317+ appendLine()
318+ appendLine(" Caused by: ${cause.javaClass.name} : ${cause.message} " )
319+ appendLine(cause.stackTraceToString())
320+ }
321+ }
322+
323+ val notificationTag = " repository-${repository.id} "
324+ val copyIntent = Intent (this , CopyErrorReceiver ::class .java).apply {
325+ action = CopyErrorReceiver .ACTION_COPY_ERROR
326+ putExtra(CopyErrorReceiver .EXTRA_ERROR_DETAILS , fullErrorDetails)
327+ putExtra(CopyErrorReceiver .EXTRA_NOTIFICATION_TAG , notificationTag)
328+ putExtra(CopyErrorReceiver .EXTRA_NOTIFICATION_ID , Constants .NOTIFICATION_ID_SYNCING )
329+ }
294330
295- else -> stringRes.unknown_error_DESC
296- },
331+ val copyPendingIntent = PendingIntent .getBroadcast(
332+ this ,
333+ repository.id.toInt(),
334+ copyIntent,
335+ PendingIntent .FLAG_UPDATE_CURRENT or PendingIntent .FLAG_IMMUTABLE ,
297336 )
337+
298338 notificationManager?.notify(
299- " repository- ${repository.id} " ,
339+ notificationTag ,
300340 Constants .NOTIFICATION_ID_SYNCING ,
301341 NotificationCompat
302342 .Builder (this , Constants .NOTIFICATION_CHANNEL_SYNCING )
@@ -307,6 +347,13 @@ class SyncService : ConnectionService<SyncService.Binder>() {
307347 )
308348 .setContentTitle(getString(stringRes.could_not_sync_FORMAT, repository.name))
309349 .setContentText(description)
350+ .setStyle(NotificationCompat .BigTextStyle ().bigText(description))
351+ .addAction(
352+ 0 ,
353+ getString(stringRes.copy_error_details),
354+ copyPendingIntent,
355+ )
356+ .setAutoCancel(true )
310357 .build(),
311358 )
312359 }
@@ -403,14 +450,13 @@ class SyncService : ConnectionService<SyncService.Binder>() {
403450 stateNotificationBuilder.setWhen(System .currentTimeMillis())
404451 }
405452
406- private fun handleNextTask (hasUpdates : Boolean ) {
453+ private fun handleNextTask (isIndexModified : Boolean ) {
407454 if (currentTask != null ) return
408455 if (tasks.isEmpty()) {
409456 if (started != Started .NO ) {
410457 lifecycleScope.launch {
411458 val setting = settingsRepository.getInitial()
412459 handleUpdates(
413- hasUpdates = hasUpdates,
414460 notifyUpdates = setting.notifyUpdate,
415461 autoUpdate = setting.autoUpdate,
416462 skipSignature = setting.ignoreSignature,
@@ -421,7 +467,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
421467 }
422468 val task = tasks.removeAt(0 )
423469 val repository = Database .RepositoryAdapter .get(task.repositoryId)
424- if (repository == null || ! repository.enabled) handleNextTask(hasUpdates )
470+ if (repository == null || ! repository.enabled) handleNextTask(isIndexModified )
425471 val lastStarted = started
426472 val newStarted = if (task.manual || lastStarted == Started .MANUAL ) {
427473 Started .MANUAL
@@ -441,20 +487,20 @@ class SyncService : ConnectionService<SyncService.Binder>() {
441487 val downloadJob = downloadFile(
442488 task = task,
443489 repository = repository,
444- hasUpdates = hasUpdates ,
490+ isIndexModified = isIndexModified ,
445491 unstableUpdates = unstableUpdates,
446492 )
447- currentTask = CurrentTask (task, downloadJob, hasUpdates , initialState)
493+ currentTask = CurrentTask (task, downloadJob, isIndexModified , initialState)
448494 }
449495 }
450496
451497 private fun CoroutineScope.downloadFile (
452498 task : Task ,
453499 repository : Repository ,
454- hasUpdates : Boolean ,
500+ isIndexModified : Boolean ,
455501 unstableUpdates : Boolean ,
456502 ): CoroutinesJob = launch(Dispatchers .Default ) {
457- var passedHasUpdates = hasUpdates
503+ var isNewlyModified = isIndexModified
458504 try {
459505 val response = RepositoryUpdater .update(
460506 this @SyncService,
@@ -472,41 +518,34 @@ class SyncService : ConnectionService<SyncService.Binder>() {
472518 )
473519 }
474520 }
475- passedHasUpdates = when (response) {
521+ isNewlyModified = when (response) {
476522 is Result .Error -> {
477523 response.exception?.let {
478524 it.printStackTrace()
479525 if (task.manual) showNotificationError(repository, it as Exception )
480526 }
481- response.data == true || hasUpdates
527+ response.data == true || isIndexModified
482528 }
483529
484- is Result .Success -> response.data || hasUpdates
530+ is Result .Success -> response.data || isIndexModified
485531 }
486532 } finally {
487533 withContext(NonCancellable ) {
488534 lock.withLock { currentTask = null }
489- handleNextTask(passedHasUpdates )
535+ handleNextTask(isNewlyModified )
490536 }
491537 }
492538 }
493539
494540 suspend fun handleUpdates (
495- hasUpdates : Boolean ,
496541 notifyUpdates : Boolean ,
497542 autoUpdate : Boolean ,
498543 skipSignature : Boolean ,
499544 ) {
500545 try {
501- if (! hasUpdates) {
502- syncState.emit(State .Finish )
503- val needStop = started == Started .MANUAL
504- started = Started .NO
505- if (needStop) stopForegroundCompat()
506- return
507- }
508546 val blocked = updateNotificationBlockerFragment?.get()?.isAdded == true
509547 val updates = Database .ProductAdapter .getUpdates(skipSignature)
548+
510549 if (! blocked && updates.isNotEmpty()) {
511550 if (notifyUpdates) {
512551 notificationManager?.notify(
@@ -520,12 +559,11 @@ class SyncService : ConnectionService<SyncService.Binder>() {
520559 updateAllAppsInternal(updates)
521560 }
522561 }
523- handleUpdates(
524- hasUpdates = false ,
525- notifyUpdates = notifyUpdates,
526- autoUpdate = autoUpdate,
527- skipSignature = skipSignature,
528- )
562+
563+ syncState.emit(State .Finish )
564+ val needStop = started == Started .MANUAL
565+ started = Started .NO
566+ if (needStop) stopForegroundCompat()
529567 } finally {
530568 withContext(NonCancellable ) {
531569 lock.withLock { currentTask = null }
0 commit comments