Handle foreground/background sync on older + newer Android versions
This commit is contained in:
@ -7,6 +7,8 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:remove="android:maxSdkVersion" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
@ -63,5 +65,9 @@
|
||||
tools:node="remove">
|
||||
</provider>
|
||||
|
||||
<service
|
||||
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 ""
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user