mirror of
https://github.com/immich-app/immich.git
synced 2025-02-15 19:36:04 +02:00
feat(mobile) Run background service after being killed (#789)
This commit is contained in:
parent
7587f858ae
commit
a3aca4acb5
@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich" xmlns:tools="http://schemas.android.com/tools">
|
||||||
<application android:label="Immich" android:name="${applicationName}" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
|
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
|
||||||
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
@ -12,12 +12,15 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
<service android:name=".AppClearedService" android:stopWithTask="false" />
|
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||||
|
<!-- Disables default WorkManager initialization to use our custom initialization -->
|
||||||
|
<provider
|
||||||
|
android:name="androidx.startup.InitializationProvider"
|
||||||
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
|
tools:node="remove">
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package app.alextran.immich
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.IBinder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Catches the event when either the system or the user kills the app
|
|
||||||
* (does not apply on force close!)
|
|
||||||
*/
|
|
||||||
class AppClearedService() : Service() {
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
|
||||||
return START_NOT_STICKY;
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTaskRemoved(rootIntent: Intent) {
|
|
||||||
ContentObserverWorker.workManagerAppClearedWorkaround(applicationContext)
|
|
||||||
stopSelf();
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import io.flutter.plugin.common.MethodChannel
|
|||||||
* Android plugin for Dart `BackgroundService`
|
* Android plugin for Dart `BackgroundService`
|
||||||
*
|
*
|
||||||
* Receives messages/method calls from the foreground Dart side to manage
|
* Receives messages/method calls from the foreground Dart side to manage
|
||||||
* the background service, e.g. start (enqueue), stop (cancel)
|
* the background service, e.g. start (enqueue), stop (cancel)
|
||||||
*/
|
*/
|
||||||
class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||||
|
|
||||||
@ -38,14 +38,15 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val ctx = context!!
|
val ctx = context!!
|
||||||
when(call.method) {
|
when (call.method) {
|
||||||
"enable" -> {
|
"enable" -> {
|
||||||
val args = call.arguments<ArrayList<*>>()!!
|
val args = call.arguments<ArrayList<*>>()!!
|
||||||
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
.edit()
|
.edit()
|
||||||
.putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args.get(0) as Long)
|
.putBoolean(ContentObserverWorker.SHARED_PREF_SERVICE_ENABLED, true)
|
||||||
.putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args.get(1) as String)
|
.putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args.get(0) as Long)
|
||||||
.apply()
|
.putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args.get(1) as String)
|
||||||
|
.apply()
|
||||||
ContentObserverWorker.enable(ctx, immediate = args.get(2) as Boolean)
|
ContentObserverWorker.enable(ctx, immediate = args.get(2) as Boolean)
|
||||||
result.success(true)
|
result.success(true)
|
||||||
}
|
}
|
||||||
@ -54,7 +55,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
val requireUnmeteredNetwork = args.get(0) as Boolean
|
val requireUnmeteredNetwork = args.get(0) as Boolean
|
||||||
val requireCharging = args.get(1) as Boolean
|
val requireCharging = args.get(1) as Boolean
|
||||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging)
|
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging)
|
||||||
result.success(true)
|
result.success(true)
|
||||||
}
|
}
|
||||||
"disable" -> {
|
"disable" -> {
|
||||||
ContentObserverWorker.disable(ctx)
|
ContentObserverWorker.disable(ctx)
|
||||||
|
@ -46,9 +46,6 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
|||||||
* @param context Android Context
|
* @param context Android Context
|
||||||
*/
|
*/
|
||||||
fun enable(context: Context, immediate: Boolean = false) {
|
fun enable(context: Context, immediate: Boolean = false) {
|
||||||
// migration to remove any old active background task
|
|
||||||
WorkManager.getInstance(context).cancelUniqueWork("immich/photoListener")
|
|
||||||
|
|
||||||
enqueueObserverWorker(context, ExistingWorkPolicy.KEEP)
|
enqueueObserverWorker(context, ExistingWorkPolicy.KEEP)
|
||||||
Log.d(TAG, "enabled ContentObserverWorker")
|
Log.d(TAG, "enabled ContentObserverWorker")
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
@ -123,8 +120,10 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
|||||||
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_OBSERVER, policy, work)
|
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_OBSERVER, policy, work)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startBackupWorker(context: Context, delayMilliseconds: Long) {
|
fun startBackupWorker(context: Context, delayMilliseconds: Long) {
|
||||||
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
if (!sp.getBoolean(SHARED_PREF_SERVICE_ENABLED, false))
|
||||||
|
return
|
||||||
val requireWifi = sp.getBoolean(SHARED_PREF_REQUIRE_WIFI, true)
|
val requireWifi = sp.getBoolean(SHARED_PREF_REQUIRE_WIFI, true)
|
||||||
val requireCharging = sp.getBoolean(SHARED_PREF_REQUIRE_CHARGING, false)
|
val requireCharging = sp.getBoolean(SHARED_PREF_REQUIRE_CHARGING, false)
|
||||||
BackupWorker.enqueueBackupWorker(context, requireWifi, requireCharging, delayMilliseconds)
|
BackupWorker.enqueueBackupWorker(context, requireWifi, requireCharging, delayMilliseconds)
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package app.alextran.immich
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.work.Configuration
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
|
||||||
|
class ImmichApp : Application() {
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
val config = Configuration.Builder().build()
|
||||||
|
WorkManager.initialize(this, config)
|
||||||
|
// always start BackupWorker after WorkManager init; this fixes the following bug:
|
||||||
|
// After the process is killed (by user or system), the first trigger (taking a new picture) is lost.
|
||||||
|
// Thus, the BackupWorker is not started. If the system kills the process after each initialization
|
||||||
|
// (because of low memory etc.), the backup is never performed.
|
||||||
|
// As a workaround, we also run a backup check when initializing the application
|
||||||
|
ContentObserverWorker.startBackupWorker(context = this, delayMilliseconds = 0)
|
||||||
|
}
|
||||||
|
}
|
@ -5,21 +5,11 @@ import io.flutter.embedding.engine.FlutterEngine
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
flutterEngine.getPlugins().add(BackgroundServicePlugin())
|
flutterEngine.plugins.add(BackgroundServicePlugin())
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
try {
|
|
||||||
startService(Intent(getBaseContext(), AppClearedService::class.java));
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// startService must not be called when app is in background (crashes app)
|
|
||||||
// there is nothing we can do
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user