diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml index 9d0492483..720138673 100644 --- a/android/Omnivore/app/src/main/AndroidManifest.xml +++ b/android/Omnivore/app/src/main/AndroidManifest.xml @@ -7,6 +7,8 @@ + + + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibrarySyncWorker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibrarySyncWorker.kt index 04fe65c34..69810c10e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibrarySyncWorker.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibrarySyncWorker.kt @@ -4,6 +4,7 @@ import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context +import android.content.pm.ServiceInfo import android.os.Build import android.util.Log import androidx.compose.ui.text.intl.Locale @@ -31,7 +32,6 @@ import java.time.Instant import java.util.TimeZone import java.util.UUID import java.util.regex.Pattern - @HiltWorker class LibrarySyncWorker @AssistedInject constructor( @Assisted appContext: Context, @@ -39,12 +39,6 @@ class LibrarySyncWorker @AssistedInject constructor( private val libraryRepository: LibraryRepository, private val datastoreRepository: DatastoreRepository, ) : CoroutineWorker(appContext, workerParams) { - override suspend fun getForegroundInfo(): ForegroundInfo { - return ForegroundInfo( - NOTIFICATION_ID, - createNotification() - ) - } companion object { const val NOTIFICATION_CHANNEL_ID = "LIBRARY_SYNC_WORKER_CHANNEL" @@ -52,64 +46,73 @@ class LibrarySyncWorker @AssistedInject constructor( const val NOTIFICATION_ID = 2 } - override suspend fun doWork(): Result { - try { - setForeground(createForegroundInfo()) - } catch (e: Exception) { - e.printStackTrace() - return Result.failure() + override suspend fun getForegroundInfo(): ForegroundInfo { + val notification = createNotification() + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForegroundInfo( + NOTIFICATION_ID, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC + ) + } else { + ForegroundInfo(NOTIFICATION_ID, notification) } + } - return withContext(Dispatchers.IO) { + override suspend fun doWork(): Result { + return try { + // Start foreground service immediately + setForeground(getForegroundInfo()) + + withContext(Dispatchers.IO) { + performItemSync() + loadUsingSearchAPI() + Log.d("LibrarySyncWorker", "Library sync completed successfully") + Result.success() + } + } catch (e: IllegalStateException) { + Log.w("LibrarySyncWorker", "Couldn't start foreground service", e) + // Continue with the work without the foreground service try { performItemSync() loadUsingSearchAPI() + Log.d("LibrarySyncWorker", "Library sync completed without foreground service") Result.success() } catch (e: Exception) { - e.printStackTrace() + Log.e("LibrarySyncWorker", "Failed to sync library without foreground service", e) Result.failure() } + } catch (e: Exception) { + Log.e("LibrarySyncWorker", "Unexpected error in LibrarySyncWorker", e) + Result.failure() } } - private fun createForegroundInfo(): ForegroundInfo { - val notification = createNotification() - return ForegroundInfo(NOTIFICATION_ID, notification) - } - private fun createNotification(): Notification { - val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannel() - } else { - "" - } + val channelId = createNotificationChannel() return NotificationCompat.Builder(applicationContext, channelId) .setContentTitle("Syncing library items") .setContentText("Your library is being synced") .setSmallIcon(R.drawable.ic_notification) - .setPriority(NotificationCompat.PRIORITY_LOW) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setCategory(NotificationCompat.CATEGORY_SERVICE) .build() } private fun createNotificationChannel(): String { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channelName = NOTIFICATION_CHANNEL_NAME - val channel = NotificationChannel( - NOTIFICATION_CHANNEL_ID, - channelName, - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "Notification channel for library syncing" - } - - val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationManager.createNotificationChannel(channel) - - return NOTIFICATION_CHANNEL_ID - } else { - return "" + val channel = NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Notification channel for library syncing" } + + val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + + return NOTIFICATION_CHANNEL_ID } private suspend fun performItemSync( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveSheetActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveSheetActivity.kt index 3dcaa2c1b..4fbbed9f3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveSheetActivity.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveSheetActivity.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp import androidx.work.Constraints import androidx.work.Data +import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequestBuilder @@ -137,11 +138,6 @@ class SaveSheetActivity : AppCompatActivity() { .setRequiredNetworkType(NetworkType.CONNECTED) .build() - val syncWorkerRequest = OneTimeWorkRequest.Builder(LibrarySyncWorker::class.java) - .setConstraints(constraints) - .addTag(url) - .build() - val inputData = Data.Builder() .putString("url", url) .build() @@ -153,9 +149,8 @@ class SaveSheetActivity : AppCompatActivity() { .addTag(url) .build() - beginWith(saveURLWorkRequest) - .then(syncWorkerRequest) - .enqueue() + WorkManager.getInstance(context) + .enqueueUniqueWork("saveUrl", ExistingWorkPolicy.REPLACE, saveURLWorkRequest) } @Composable diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveURLWorker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveURLWorker.kt index 192839b1a..0d2646a4a 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveURLWorker.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveURLWorker.kt @@ -4,6 +4,7 @@ import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context +import android.content.pm.ServiceInfo import android.os.Build import android.util.Log import androidx.compose.ui.text.intl.Locale @@ -34,12 +35,6 @@ class SaveURLWorker @AssistedInject constructor( @Assisted workerParams: WorkerParameters, private val datastoreRepository: DatastoreRepository, ) : CoroutineWorker(appContext, workerParams) { - override suspend fun getForegroundInfo(): ForegroundInfo { - return ForegroundInfo( - NOTIFICATION_ID, - createNotification() - ) - } companion object { const val NOTIFICATION_CHANNEL_ID = "SAVE_URL_WORKER_CHANNEL" @@ -47,17 +42,53 @@ class SaveURLWorker @AssistedInject constructor( const val NOTIFICATION_ID = 1 } - override suspend fun doWork(): Result { - try { - setForeground(createForegroundInfo()) - } catch (e: Exception) { - e.printStackTrace() - return Result.failure() + override suspend fun getForegroundInfo(): ForegroundInfo { + val notification = createNotification() + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForegroundInfo( + NOTIFICATION_ID, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC + ) + } else { + ForegroundInfo(NOTIFICATION_ID, notification) } + } - return withContext(Dispatchers.IO) { - val url = inputData.getString("url") ?: return@withContext Result.failure() - if (saveURL(url)) Result.success() else Result.failure() + override suspend fun doWork(): Result { + return try { + // Start foreground service immediately + setForeground(getForegroundInfo()) + + withContext(Dispatchers.IO) { + val url = inputData.getString("url") + if (url == null) { + Log.e("SaveURLWorker", "No URL provided") + return@withContext Result.failure() + } + + if (saveURL(url)) { + Log.d("SaveURLWorker", "URL saved successfully") + Result.success() + } else { + Log.e("SaveURLWorker", "Failed to save URL") + Result.failure() + } + } + } catch (e: IllegalStateException) { + Log.w("SaveURLWorker", "Couldn't start foreground service", e) + // Continue with the work without the foreground service + val url = inputData.getString("url") + if (url != null && saveURL(url)) { + Log.d("SaveURLWorker", "URL saved successfully without foreground service") + Result.success() + } else { + Log.e("SaveURLWorker", "Failed to save URL without foreground service") + Result.failure() + } + } catch (e: Exception) { + Log.e("SaveURLWorker", "Unexpected error in SaveURLWorker", e) + Result.failure() } } @@ -104,11 +135,6 @@ class SaveURLWorker @AssistedInject constructor( return null } - private fun createForegroundInfo(): ForegroundInfo { - val notification = createNotification() - return ForegroundInfo(NOTIFICATION_ID, notification) - } - private fun createNotification(): Notification { val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannel() @@ -120,17 +146,16 @@ class SaveURLWorker @AssistedInject constructor( .setContentTitle("Saving URL") .setContentText("Your URL is being saved in the background.") .setSmallIcon(R.drawable.ic_notification) // Ensure this icon is valid - .setPriority(NotificationCompat.PRIORITY_LOW) + .setPriority(NotificationCompat.PRIORITY_HIGH) .build() } private fun createNotificationChannel(): String { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channelName = NOTIFICATION_CHANNEL_NAME + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( NOTIFICATION_CHANNEL_ID, - channelName, - NotificationManager.IMPORTANCE_LOW + NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH // Changed from LOW to HIGH ).apply { description = "Notification channel for URL saving" } @@ -138,9 +163,9 @@ class SaveURLWorker @AssistedInject constructor( val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) - return NOTIFICATION_CHANNEL_ID + NOTIFICATION_CHANNEL_ID } else { - return "" + "" } } }