diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index a6f86b8537..96d2db23f5 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -90,7 +90,7 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" - implementation "androidx.work:work-runtime:$work_version" + implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.concurrent:concurrent-futures:$concurrent_version" implementation "com.google.guava:guava:$guava_version" implementation "com.github.bumptech.glide:glide:$glide_version" diff --git a/mobile/android/app/src/main/kotlin/com/example/mobile/BackgroundServicePlugin.kt b/mobile/android/app/src/main/kotlin/com/example/mobile/BackgroundServicePlugin.kt index 1d23c5665c..6541ad5755 100644 --- a/mobile/android/app/src/main/kotlin/com/example/mobile/BackgroundServicePlugin.kt +++ b/mobile/android/app/src/main/kotlin/com/example/mobile/BackgroundServicePlugin.kt @@ -52,7 +52,6 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler { .putBoolean(ContentObserverWorker.SHARED_PREF_SERVICE_ENABLED, true) .putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args.get(0) as Long) .putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args.get(1) as String) - .putString(BackupWorker.SHARED_PREF_SERVER_URL, args.get(3) as String) .apply() ContentObserverWorker.enable(ctx, immediate = args.get(2) as Boolean) result.success(true) diff --git a/mobile/android/app/src/main/kotlin/com/example/mobile/BackupWorker.kt b/mobile/android/app/src/main/kotlin/com/example/mobile/BackupWorker.kt index dc7c4a9c37..660e1d55ba 100644 --- a/mobile/android/app/src/main/kotlin/com/example/mobile/BackupWorker.kt +++ b/mobile/android/app/src/main/kotlin/com/example/mobile/BackupWorker.kt @@ -11,8 +11,8 @@ import android.os.PowerManager import android.os.SystemClock import android.util.Log import androidx.annotation.RequiresApi -import androidx.concurrent.futures.CallbackToFutureAdapter import androidx.core.app.NotificationCompat +import androidx.concurrent.futures.ResolvableFuture import androidx.work.BackoffPolicy import androidx.work.Constraints import androidx.work.ForegroundInfo @@ -30,16 +30,6 @@ import io.flutter.embedding.engine.loader.FlutterLoader import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.view.FlutterCallbackInformation -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull -import java.io.IOException -import java.net.HttpURLConnection -import java.net.InetAddress -import java.net.URL import java.util.concurrent.TimeUnit /** @@ -52,6 +42,7 @@ import java.util.concurrent.TimeUnit */ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ctx, params), MethodChannel.MethodCallHandler { + private val resolvableFuture = ResolvableFuture.create() private var engine: FlutterEngine? = null private lateinit var backgroundChannel: MethodChannel private val notificationManager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager @@ -61,80 +52,35 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct private var notificationDetailBuilder: NotificationCompat.Builder? = null private var fgFuture: ListenableFuture? = null - private val job = Job() - private lateinit var completer: CallbackToFutureAdapter.Completer - private val resolvableFuture = CallbackToFutureAdapter.getFuture { completer -> - this.completer = completer - null - } - - init { - resolvableFuture.addListener( - Runnable { - if (resolvableFuture.isCancelled) { - job.cancel() - } - }, - taskExecutor.serialTaskExecutor - ) - } - override fun startWork(): ListenableFuture { + Log.d(TAG, "startWork") val ctx = applicationContext - val prefs = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) - prefs.getString(SHARED_PREF_SERVER_URL, null) - ?.takeIf { it.isNotEmpty() } - ?.let { serverUrl -> doCoroutineWork(serverUrl) } - ?: doWork() - return resolvableFuture - } - - /** - * This function is used to check if server URL is reachable before starting the backup work. - * Check must be done in a background to avoid blocking the main thread. - */ - private fun doCoroutineWork(serverUrl : String) { - CoroutineScope(Dispatchers.Default + job).launch { - val isReachable = isUrlReachableHttp(serverUrl) - withContext(Dispatchers.Main) { - if (isReachable) { - doWork() - } else { - // Fail when the URL is not reachable - completer.set(Result.failure()) - } - } + if (!flutterLoader.initialized()) { + flutterLoader.startInitialization(ctx) } - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // Create a Notification channel if necessary + createChannel() + } + if (isIgnoringBatteryOptimizations) { + // normal background services can only up to 10 minutes + // foreground services are allowed to run indefinitely + // requires battery optimizations to be disabled (either manually by the user + // or by the system learning that immich is important to the user) + val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!! + showInfo(getInfoBuilder(title, indeterminate=true).build()) + } + engine = FlutterEngine(ctx) - private fun doWork() { - Log.d(TAG, "doWork") - val ctx = applicationContext + flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) { + runDart() + } - if (!flutterLoader.initialized()) { - flutterLoader.startInitialization(ctx) - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // Create a Notification channel if necessary - createChannel() - } - if (isIgnoringBatteryOptimizations) { - // normal background services can only up to 10 minutes - // foreground services are allowed to run indefinitely - // requires battery optimizations to be disabled (either manually by the user - // or by the system learning that immich is important to the user) - val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) - .getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!! - showInfo(getInfoBuilder(title, indeterminate=true).build()) - } - engine = FlutterEngine(ctx) - - flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) { - runDart() - } + return resolvableFuture } /** @@ -193,7 +139,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct engine = null if (result != null) { Log.d(TAG, "stopEngine result=${result}") - this.completer.set(result) + resolvableFuture.set(result) } waitOnSetForegroundAsync() } @@ -324,14 +270,13 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct const val SHARED_PREF_CALLBACK_KEY = "callbackDispatcherHandle" const val SHARED_PREF_NOTIFICATION_TITLE = "notificationTitle" const val SHARED_PREF_LAST_CHANGE = "lastChange" - const val SHARED_PREF_SERVER_URL = "serverUrl" private const val TASK_NAME_BACKUP = "immich/BackupWorker" private const val NOTIFICATION_CHANNEL_ID = "immich/backgroundService" private const val NOTIFICATION_CHANNEL_ERROR_ID = "immich/backgroundServiceError" private const val NOTIFICATION_DEFAULT_TITLE = "Immich" private const val NOTIFICATION_ID = 1 - private const val NOTIFICATION_ERROR_ID = 2 + private const val NOTIFICATION_ERROR_ID = 2 private const val NOTIFICATION_DETAIL_ID = 3 private const val ONE_MINUTE = 60000L @@ -359,7 +304,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct val workInfoList = workInfoFuture.get(1000, TimeUnit.MILLISECONDS) if (workInfoList != null) { for (workInfo in workInfoList) { - if (workInfo.state == WorkInfo.State.ENQUEUED) { + if (workInfo.getState() == WorkInfo.State.ENQUEUED) { val workRequest = buildWorkRequest(requireWifi, requireCharging) wm.enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.REPLACE, workRequest) Log.d(TAG, "updateBackupWorker updated BackupWorker constraints") @@ -414,27 +359,4 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct } } -private const val TAG = "BackupWorker" - -/** - * Check if the given URL is reachable via HTTP - */ -suspend fun isUrlReachableHttp(url: String, timeoutMillis: Long = 5000L): Boolean { - return withTimeoutOrNull(timeoutMillis) { - var httpURLConnection: HttpURLConnection? = null - try { - httpURLConnection = (URL(url).openConnection() as HttpURLConnection).apply { - requestMethod = "HEAD" - connectTimeout = timeoutMillis.toInt() - readTimeout = timeoutMillis.toInt() - } - httpURLConnection.connect() - httpURLConnection.responseCode == HttpURLConnection.HTTP_OK - } catch (e: Exception) { - Log.e(TAG, "Failed to reach server URL: $e") - false - } finally { - httpURLConnection?.disconnect() - } - } == true -} +private const val TAG = "BackupWorker" \ No newline at end of file diff --git a/mobile/android/build.gradle b/mobile/android/build.gradle index e9c271b2c5..4dacde5a9d 100644 --- a/mobile/android/build.gradle +++ b/mobile/android/build.gradle @@ -1,7 +1,7 @@ buildscript { - ext.kotlin_version = '1.8.22' + ext.kotlin_version = '1.8.20' ext.kotlin_coroutines_version = '1.7.1' - ext.work_version = '2.9.0' + ext.work_version = '2.7.1' ext.concurrent_version = '1.1.0' ext.guava_version = '33.0.0-android' ext.glide_version = '4.14.2' diff --git a/mobile/lib/modules/backup/background_service/background.service.dart b/mobile/lib/modules/backup/background_service/background.service.dart index 8358043894..cbee121105 100644 --- a/mobile/lib/modules/backup/background_service/background.service.dart +++ b/mobile/lib/modules/backup/background_service/background.service.dart @@ -20,7 +20,6 @@ import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/services/api.service.dart'; import 'package:immich_mobile/utils/backup_progress.dart'; import 'package:immich_mobile/utils/diff.dart'; -import 'package:immich_mobile/utils/url_helper.dart'; import 'package:isar/isar.dart'; import 'package:path_provider_ios/path_provider_ios.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -69,10 +68,8 @@ class BackgroundService { final callback = PluginUtilities.getCallbackHandle(_nativeEntry)!; final String title = "backup_background_service_default_notification".tr(); - final bool ok = await _foregroundChannel.invokeMethod( - 'enable', - [callback.toRawHandle(), title, immediate, getServerUrl()], - ); + final bool ok = await _foregroundChannel + .invokeMethod('enable', [callback.toRawHandle(), title, immediate]); return ok; } catch (error) { return false;