mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
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:
commit
a01a5c22bb
5 changed files with 53 additions and 156 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue