Merge pull request #4162 from omnivore-app/fix/android-fgbg-sync

Handle foreground/background sync on older + newer Android versions
This commit is contained in:
Jackson Harper 2024-07-09 09:08:56 +08:00 committed by GitHub
commit a01a5c22bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 53 additions and 156 deletions

View file

@ -28,8 +28,8 @@ android {
applicationId = "app.omnivore.omnivore"
minSdk = 26
targetSdk = 34
versionCode = 2200000
versionName = "0.220.0"
versionCode = 2260000
versionName = "0.226.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View file

@ -6,7 +6,6 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<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.INTERNET" />
<application
@ -62,6 +61,5 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove">
</provider>
</application>
</manifest>

View file

@ -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,76 +39,18 @@ 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"
const val NOTIFICATION_CHANNEL_NAME = "Sync library"
const val NOTIFICATION_ID = 2
}
override suspend fun doWork(): Result {
try {
setForeground(createForegroundInfo())
} catch (e: Exception) {
e.printStackTrace()
return Result.failure()
}
return withContext(Dispatchers.IO) {
try {
return try {
withContext(Dispatchers.IO) {
performItemSync()
loadUsingSearchAPI()
Log.d("LibrarySyncWorker", "Library sync completed successfully")
Result.success()
} catch (e: Exception) {
e.printStackTrace()
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 {
""
}
return NotificationCompat.Builder(applicationContext, channelId)
.setContentTitle("Syncing library items")
.setContentText("Your library is being synced")
.setSmallIcon(R.drawable.ic_notification)
.setPriority(NotificationCompat.PRIORITY_LOW)
.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 ""
} catch (e: Exception) {
Log.e("LibrarySyncWorker", "Unexpected error in LibrarySyncWorker", e)
Result.failure()
}
}
@ -130,7 +72,7 @@ class LibrarySyncWorker @AssistedInject constructor(
if (result.hasError) {
result.errorString?.let { errorString ->
println("SYNC ERROR: $errorString")
Log.e("LibrarySyncWorker", "SYNC ERROR: $errorString")
}
}

View file

@ -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
@ -36,6 +37,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
@ -133,28 +135,25 @@ class SaveSheetActivity : AppCompatActivity() {
}
private fun WorkManager.enqueueSaveWorker(context: Context, url: String) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
val saveData = workDataOf("url" to url)
val saveWork = OneTimeWorkRequestBuilder<SaveURLWorker>()
.setInputData(saveData)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build()
val syncWorkerRequest = OneTimeWorkRequest.Builder(LibrarySyncWorker::class.java)
.setConstraints(constraints)
.addTag(url)
val syncWork = OneTimeWorkRequestBuilder<LibrarySyncWorker>()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setInitialDelay(5, TimeUnit.SECONDS)
.build()
val inputData = Data.Builder()
.putString("url", url)
.build()
val saveURLWorkRequest = OneTimeWorkRequest.Builder(SaveURLWorker::class.java)
.setConstraints(constraints)
.setInputData(inputData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.addTag(url)
.build()
beginWith(saveURLWorkRequest)
.then(syncWorkerRequest)
WorkManager.getInstance(context)
.beginUniqueWork("saveAndSync", ExistingWorkPolicy.REPLACE, saveWork)
.then(syncWork)
.enqueue()
}

View file

@ -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,30 +35,27 @@ 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"
const val NOTIFICATION_CHANNEL_NAME = "URL Saver"
const val NOTIFICATION_ID = 1
}
override suspend fun doWork(): Result {
try {
setForeground(createForegroundInfo())
} catch (e: Exception) {
e.printStackTrace()
return Result.failure()
}
return withContext(Dispatchers.IO) {
val url = inputData.getString("url") ?: return@withContext Result.failure()
if (saveURL(url)) Result.success() else Result.failure()
try {
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: Exception) {
Log.e("SaveURLWorker", "Unexpected error in SaveURLWorker", e)
Result.failure()
}
}
}
@ -71,7 +69,7 @@ class SaveURLWorker @AssistedInject constructor(
val cleanedUrl = cleanUrl(url) ?: url
try {
return try {
val timezone = TimeZone.getDefault().id
val locale = Locale.current.toLanguageTag()
@ -86,11 +84,10 @@ class SaveURLWorker @AssistedInject constructor(
)
)
).execute()
return (response.data?.saveUrl?.onSaveSuccess?.url != null)
(response.data?.saveUrl?.onSaveSuccess?.url != null)
} catch (e: Exception) {
Log.d("omnivore", "FAILED TO SAVE ITEM")
e.printStackTrace()
return false
Log.e("SaveURLWorker", "Failed to save item", e)
false
}
}
@ -98,49 +95,10 @@ class SaveURLWorker @AssistedInject constructor(
val pattern = Pattern.compile("\\b(?:https?|ftp)://\\S+")
val matcher = pattern.matcher(text)
if (matcher.find()) {
return matcher.group()
}
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()
return if (matcher.find()) {
matcher.group()
} else {
""
}
return NotificationCompat.Builder(applicationContext, channelId)
.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)
.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 URL saving"
}
val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
return NOTIFICATION_CHANNEL_ID
} else {
return ""
null
}
}
}