android 14: use work manager instead of foreground service as datasync

This commit is contained in:
Thibault Deckers 2023-05-08 23:06:31 +02:00
parent c94fc7245e
commit 01d2e21369
41 changed files with 214 additions and 377 deletions

View file

@ -204,6 +204,7 @@ dependencies {
implementation 'androidx.media:media:1.6.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation 'androidx.work:work-runtime-ktx:2.8.1'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.commonsware.cwac:document:0.5.0'

View file

@ -40,11 +40,6 @@ This change eventually prevents building the app with Flutter v3.7.11.
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- to analyze media in a service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--
TODO TLAD [Android 14 (API 34)] request FOREGROUND_SERVICE_DATA_SYNC permission
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
cf https://developer.android.com/about/versions/14/changes/fgs-types-required
-->
<!-- to fetch map tiles -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- from Android 12 (API 31), users can optionally grant access to the media management special permission -->
@ -251,12 +246,6 @@ This change eventually prevents building the app with Flutter v3.7.11.
</intent-filter>
</service>
<service
android:name=".AnalysisService"
android:description="@string/analysis_service_description"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".ScreenSaverService"
android:exported="true"

View file

@ -1,258 +0,0 @@
package deckers.thibault.aves
import android.app.Notification
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.*
import android.util.Log
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.MainActivity.Companion.OPEN_FROM_ANALYSIS_SERVICE
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.LogUtils
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.runBlocking
class AnalysisService : Service() {
private var flutterEngine: FlutterEngine? = null
private var backgroundChannel: MethodChannel? = null
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
private val analysisServiceBinder = AnalysisServiceBinder()
override fun onCreate() {
Log.i(LOG_TAG, "Create analysis service")
runBlocking {
FlutterUtils.initFlutterEngine(this@AnalysisService, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) {
flutterEngine = it
}
}
try {
initChannels(this)
HandlerThread("Analysis service handler", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
serviceHandler = ServiceHandler(looper)
}
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to initialize service", e)
}
}
override fun onDestroy() {
Log.i(LOG_TAG, "Destroy analysis service")
}
override fun onBind(intent: Intent) = analysisServiceBinder
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val channel = NotificationChannelCompat.Builder(CHANNEL_ANALYSIS, NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getText(R.string.analysis_channel_name))
.setShowBadge(false)
.build()
NotificationManagerCompat.from(this).createNotificationChannel(channel)
startForeground(NOTIFICATION_ID, buildNotification())
val msgData = Bundle()
intent?.extras?.let {
msgData.putAll(it)
}
serviceHandler?.obtainMessage()?.let { msg ->
msg.arg1 = startId
msg.data = msgData
serviceHandler?.sendMessage(msg)
}
return START_NOT_STICKY
}
private fun detachAndStop() {
analysisServiceBinder.detach()
stopSelf()
}
private fun initChannels(context: Context) {
val engine = flutterEngine
engine ?: throw Exception("Flutter engine is not initialized")
val messenger = engine.dartExecutor
// channels for analysis
// dart -> platform -> dart
// - need Context
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(context))
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(context))
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(context))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(context))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(context))
// result streaming: dart -> platform ->->-> dart
// - need Context
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(context, args) }
StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(context, args) }
// channel for service management
backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL).apply {
setMethodCallHandler { call, result -> onMethodCall(call, result) }
}
}
private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"initialized" -> {
Log.d(LOG_TAG, "background channel is ready")
result.success(null)
}
"updateNotification" -> {
val title = call.argument<String>("title")
val message = call.argument<String>("message")
val notification = buildNotification(title, message)
NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, notification)
result.success(null)
}
"refreshApp" -> {
analysisServiceBinder.refreshApp()
result.success(null)
}
"stop" -> {
detachAndStop()
result.success(null)
}
else -> result.notImplemented()
}
}
private fun buildNotification(title: String? = null, message: String? = null): Notification {
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
val stopServiceIntent = Intent(this, AnalysisService::class.java).let {
it.putExtra(KEY_COMMAND, COMMAND_STOP)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PendingIntent.getForegroundService(this, STOP_SERVICE_REQUEST, it, pendingIntentFlags)
} else {
PendingIntent.getService(this, STOP_SERVICE_REQUEST, it, pendingIntentFlags)
}
}
val openAppIntent = Intent(this, MainActivity::class.java).let {
PendingIntent.getActivity(this, OPEN_FROM_ANALYSIS_SERVICE, it, pendingIntentFlags)
}
val stopAction = NotificationCompat.Action.Builder(
R.drawable.ic_outline_stop_24,
getString(R.string.analysis_notification_action_stop),
stopServiceIntent
).build()
val icon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) R.drawable.ic_notification else R.mipmap.ic_launcher_round
return NotificationCompat.Builder(this, CHANNEL_ANALYSIS)
.setContentTitle(title ?: getText(R.string.analysis_notification_default_title))
.setContentText(message)
.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
.setSmallIcon(icon)
.setContentIntent(openAppIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.addAction(stopAction)
.build()
}
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
val data = msg.data
when (data.getString(KEY_COMMAND)) {
COMMAND_START -> {
runBlocking {
FlutterUtils.runOnUiThread {
val entryIds = data.getIntArray(KEY_ENTRY_IDS)?.toList()
backgroundChannel?.invokeMethod(
"start", hashMapOf(
"entryIds" to entryIds,
"force" to data.getBoolean(KEY_FORCE),
)
)
}
}
}
COMMAND_STOP -> {
// unconditionally stop the service
runBlocking {
FlutterUtils.runOnUiThread {
backgroundChannel?.invokeMethod("stop", null)
}
}
detachAndStop()
}
else -> {
}
}
}
}
companion object {
private val LOG_TAG = LogUtils.createTag<AnalysisService>()
private const val BACKGROUND_CHANNEL = "deckers.thibault/aves/analysis_service_background"
const val SHARED_PREFERENCES_KEY = "analysis_service"
const val CALLBACK_HANDLE_KEY = "callback_handle"
const val NOTIFICATION_ID = 1
const val STOP_SERVICE_REQUEST = 1
const val CHANNEL_ANALYSIS = "analysis"
const val KEY_COMMAND = "command"
const val COMMAND_START = "start"
const val COMMAND_STOP = "stop"
const val KEY_ENTRY_IDS = "entry_ids"
const val KEY_FORCE = "force"
}
}
class AnalysisServiceBinder : Binder() {
private val listeners = hashSetOf<AnalysisServiceListener>()
fun startListening(listener: AnalysisServiceListener) = listeners.add(listener)
fun stopListening(listener: AnalysisServiceListener) = listeners.remove(listener)
fun refreshApp() {
val localListeners = listeners.toSet()
for (listener in localListeners) {
try {
listener.refreshApp()
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to notify listener=$listener", e)
}
}
}
fun detach() {
val localListeners = listeners.toSet()
for (listener in localListeners) {
try {
listener.detachFromActivity()
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to detach listener=$listener", e)
}
}
}
companion object {
private val LOG_TAG = LogUtils.createTag<AnalysisServiceBinder>()
}
}
interface AnalysisServiceListener {
fun refreshApp()
fun detachFromActivity()
}

View file

@ -0,0 +1,173 @@
package deckers.thibault.aves
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.GeocodingHandler
import deckers.thibault.aves.channel.calls.MediaStoreHandler
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
import deckers.thibault.aves.channel.calls.StorageHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.LogUtils
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class AnalysisWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) {
private var workCont: Continuation<Any?>? = null
private var flutterEngine: FlutterEngine? = null
private var backgroundChannel: MethodChannel? = null
override suspend fun doWork(): Result {
createNotificationChannel()
setForeground(createForegroundInfo())
suspendCoroutine { cont ->
workCont = cont
onStart()
}
return Result.success()
}
private fun onStart() {
Log.i(LOG_TAG, "Start analysis worker")
runBlocking {
FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) {
flutterEngine = it
}
}
try {
initChannels(applicationContext)
runBlocking {
FlutterUtils.runOnUiThread {
backgroundChannel?.invokeMethod(
"start", hashMapOf(
"entryIds" to inputData.getIntArray(KEY_ENTRY_IDS)?.toList(),
"force" to inputData.getBoolean(KEY_FORCE, false),
)
)
}
}
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to initialize worker", e)
workCont?.resumeWithException(e)
}
}
private fun initChannels(context: Context) {
val engine = flutterEngine
engine ?: throw Exception("Flutter engine is not initialized")
val messenger = engine.dartExecutor
// channels for analysis
// dart -> platform -> dart
// - need Context
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(context))
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(context))
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(context))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(context))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(context))
// result streaming: dart -> platform ->->-> dart
// - need Context
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(context, args) }
StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(context, args) }
// channel for service management
backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL).apply {
setMethodCallHandler { call, result -> onMethodCall(call, result) }
}
}
private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"initialized" -> {
Log.d(LOG_TAG, "Analysis background channel is ready")
result.success(null)
}
"updateNotification" -> {
val title = call.argument<String>("title")
val message = call.argument<String>("message")
setForegroundAsync(createForegroundInfo(title, message))
result.success(null)
}
"stop" -> {
workCont?.resume(null)
result.success(null)
}
else -> result.notImplemented()
}
}
private fun createNotificationChannel() {
val channel = NotificationChannelCompat.Builder(NOTIFICATION_CHANNEL, NotificationManagerCompat.IMPORTANCE_LOW)
.setName(applicationContext.getText(R.string.analysis_channel_name))
.setShowBadge(false)
.build()
NotificationManagerCompat.from(applicationContext).createNotificationChannel(channel)
}
private fun createForegroundInfo(title: String? = null, message: String? = null): ForegroundInfo {
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
val openAppIntent = Intent(applicationContext, MainActivity::class.java).let {
PendingIntent.getActivity(applicationContext, MainActivity.OPEN_FROM_ANALYSIS_SERVICE, it, pendingIntentFlags)
}
val stopAction = NotificationCompat.Action.Builder(
R.drawable.ic_outline_stop_24,
applicationContext.getString(R.string.analysis_notification_action_stop),
WorkManager.getInstance(applicationContext).createCancelPendingIntent(id)
).build()
val icon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) R.drawable.ic_notification else R.mipmap.ic_launcher_round
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL)
.setContentTitle(title ?: applicationContext.getText(R.string.analysis_notification_default_title))
.setContentText(message)
.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
.setSmallIcon(icon)
.setContentIntent(openAppIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.addAction(stopAction)
.build()
return ForegroundInfo(NOTIFICATION_ID, notification)
}
companion object {
private val LOG_TAG = LogUtils.createTag<AnalysisWorker>()
private const val BACKGROUND_CHANNEL = "deckers.thibault/aves/analysis_service_background"
const val SHARED_PREFERENCES_KEY = "analysis_service"
const val CALLBACK_HANDLE_KEY = "callback_handle"
const val NOTIFICATION_CHANNEL = "analysis"
const val NOTIFICATION_ID = 1
const val KEY_ENTRY_IDS = "entry_ids"
const val KEY_FORCE = "force"
}
}

View file

@ -170,7 +170,6 @@ open class MainActivity : FlutterFragmentActivity() {
override fun onStop() {
Log.i(LOG_TAG, "onStop")
analysisHandler.detachFromActivity()
super.onStop()
}

View file

@ -1,32 +1,30 @@
package deckers.thibault.aves.channel.calls
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Build
import android.os.IBinder
import android.util.Log
import deckers.thibault.aves.AnalysisService
import deckers.thibault.aves.AnalysisServiceBinder
import deckers.thibault.aves.AnalysisServiceListener
import deckers.thibault.aves.utils.ContextUtils.isMyServiceRunning
import deckers.thibault.aves.utils.LogUtils
import androidx.core.app.ComponentActivity
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.workDataOf
import deckers.thibault.aves.AnalysisWorker
import deckers.thibault.aves.utils.FlutterUtils
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class AnalysisHandler(private val activity: Activity, private val onAnalysisCompleted: () -> Unit) : MethodChannel.MethodCallHandler, AnalysisServiceListener {
class AnalysisHandler(private val activity: ComponentActivity, private val onAnalysisCompleted: () -> Unit) : MethodChannel.MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"registerCallback" -> ioScope.launch { Coresult.safe(call, result, ::registerCallback) }
"startService" -> Coresult.safe(call, result, ::startAnalysis)
"startAnalysis" -> Coresult.safe(call, result, ::startAnalysis)
else -> result.notImplemented()
}
}
@ -38,9 +36,9 @@ class AnalysisHandler(private val activity: Activity, private val onAnalysisComp
return
}
activity.getSharedPreferences(AnalysisService.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
.edit()
.putLong(AnalysisService.CALLBACK_HANDLE_KEY, callbackHandle)
.putLong(AnalysisWorker.CALLBACK_HANDLE_KEY, callbackHandle)
.apply()
result.success(true)
}
@ -55,20 +53,18 @@ class AnalysisHandler(private val activity: Activity, private val onAnalysisComp
// can be null or empty
val entryIds = call.argument<List<Int>>("entryIds")
if (!activity.isMyServiceRunning(AnalysisService::class.java)) {
val intent = Intent(activity, AnalysisService::class.java)
.putExtra(AnalysisService.KEY_COMMAND, AnalysisService.COMMAND_START)
.putExtra(AnalysisService.KEY_ENTRY_IDS, entryIds?.toIntArray())
.putExtra(AnalysisService.KEY_FORCE, force)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Foreground services cannot start from background, but the service here may start fine
// while the current lifecycle state (via `ProcessLifecycleOwner.get().lifecycle.currentState`)
// is only `INITIALIZED`, so we should not preemptively return when the state is below `STARTED`.
activity.startForegroundService(intent)
} else {
activity.startService(intent)
}
}
WorkManager.getInstance(activity).enqueueUniqueWork(
ANALYSIS_WORK_NAME,
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<AnalysisWorker>().apply {
setInputData(
workDataOf(
AnalysisWorker.KEY_ENTRY_IDS to entryIds?.toIntArray(),
AnalysisWorker.KEY_FORCE to force,
)
)
}.build()
)
attachToActivity()
result.success(null)
}
@ -76,44 +72,22 @@ class AnalysisHandler(private val activity: Activity, private val onAnalysisComp
private var attached = false
fun attachToActivity() {
if (activity.isMyServiceRunning(AnalysisService::class.java)) {
val intent = Intent(activity, AnalysisService::class.java)
activity.bindService(intent, connection, Context.BIND_AUTO_CREATE)
if (!attached) {
attached = true
}
}
override fun detachFromActivity() {
if (attached) {
attached = false
activity.unbindService(connection)
}
}
override fun refreshApp() {
if (attached) {
onAnalysisCompleted()
}
}
private val connection = object : ServiceConnection {
var binder: AnalysisServiceBinder? = null
override fun onServiceConnected(name: ComponentName, service: IBinder) {
Log.i(LOG_TAG, "Analysis service connected")
binder = service as AnalysisServiceBinder
binder?.startListening(this@AnalysisHandler)
}
override fun onServiceDisconnected(name: ComponentName) {
Log.i(LOG_TAG, "Analysis service disconnected")
binder?.stopListening(this@AnalysisHandler)
binder = null
WorkManager.getInstance(activity).getWorkInfosForUniqueWorkLiveData(ANALYSIS_WORK_NAME).observe(activity) { list ->
if (list.any { it.state == WorkInfo.State.SUCCEEDED }) {
runBlocking {
FlutterUtils.runOnUiThread {
onAnalysisCompleted()
}
}
}
}
}
}
companion object {
private val LOG_TAG = LogUtils.createTag<AnalysisHandler>()
const val CHANNEL = "deckers.thibault/aves/analysis"
private const val ANALYSIS_WORK_NAME = "analysis_work"
}
}

View file

@ -5,7 +5,6 @@
<string name="search_shortcut_short_label">البحث</string>
<string name="videos_shortcut_short_label">الفيديوهات</string>
<string name="analysis_channel_name">فحص الوسائط</string>
<string name="analysis_service_description">فحص الصور والفيديوهات</string>
<string name="analysis_notification_default_title">يتم فحص الوسائط</string>
<string name="analysis_notification_action_stop">إيقاف</string>
</resources>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">گەڕان</string>
<string name="videos_shortcut_short_label">ڤیدیۆ</string>
<string name="analysis_channel_name">گەڕان بۆ فایل</string>
<string name="analysis_service_description">گەڕان بۆ وێنە و ڤیدیۆ</string>
<string name="analysis_notification_default_title">گەڕان بۆ فایلەکان</string>
<string name="analysis_notification_action_stop">وەستاندن</string>
</resources>

View file

@ -5,7 +5,6 @@
<string name="search_shortcut_short_label">Hledat</string>
<string name="videos_shortcut_short_label">Videa</string>
<string name="analysis_channel_name">Prohledat média</string>
<string name="analysis_service_description">Prohledat obrázky a videa</string>
<string name="analysis_notification_default_title">Prohledávání médií</string>
<string name="analysis_notification_action_stop">Zastavit</string>
<string name="app_widget_label">Fotorámeček</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Suche</string>
<string name="videos_shortcut_short_label">Videos</string>
<string name="analysis_channel_name">Analyse von Medien</string>
<string name="analysis_service_description">Bilder &amp; Videos scannen</string>
<string name="analysis_notification_default_title">Medien scannen</string>
<string name="analysis_notification_action_stop">Abbrechen</string>
<string name="safe_mode_shortcut_short_label">Sicherer Modus</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Αναζήτηση</string>
<string name="videos_shortcut_short_label">Βίντεο</string>
<string name="analysis_channel_name">Σάρωση πολυμέσων</string>
<string name="analysis_service_description">Σάρωση εικόνων &amp; Βίντεο</string>
<string name="analysis_notification_default_title">Σάρωση στοιχείων</string>
<string name="analysis_notification_action_stop">Διακοπή</string>
</resources>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Búsqueda</string>
<string name="videos_shortcut_short_label">Vídeos</string>
<string name="analysis_channel_name">Explorar medios</string>
<string name="analysis_service_description">Explorar imágenes &amp; videos</string>
<string name="analysis_notification_default_title">Explorando medios</string>
<string name="analysis_notification_action_stop">Anular</string>
<string name="safe_mode_shortcut_short_label">Modo seguro</string>

View file

@ -3,7 +3,6 @@
<string name="search_shortcut_short_label">Bilatu</string>
<string name="videos_shortcut_short_label">Bideoak</string>
<string name="app_widget_label">Argazki-markoa</string>
<string name="analysis_service_description">Irudiak eta bideoak eskaneatu</string>
<string name="wallpaper">Horma-papera</string>
<string name="analysis_channel_name">Media eskaneatu</string>
<string name="analysis_notification_action_stop">Gelditu</string>

View file

@ -2,7 +2,6 @@
<resources>
<string name="videos_shortcut_short_label">ویدئو ها</string>
<string name="analysis_channel_name">کنکاش رسانه</string>
<string name="analysis_service_description">کنکاش تصاویر و ویدئو ها</string>
<string name="search_shortcut_short_label">جستجو</string>
<string name="wallpaper">کاغذدیواری</string>
<string name="analysis_notification_default_title">در حال کنکاش رسانه‌ها</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Recherche</string>
<string name="videos_shortcut_short_label">Vidéos</string>
<string name="analysis_channel_name">Analyse des images</string>
<string name="analysis_service_description">Analyse des images &amp; vidéos</string>
<string name="analysis_notification_default_title">Analyse des images</string>
<string name="analysis_notification_action_stop">Annuler</string>
<string name="safe_mode_shortcut_short_label">Mode sans échec</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Procura</string>
<string name="videos_shortcut_short_label">Vídeos</string>
<string name="analysis_channel_name">Escaneo multimedia</string>
<string name="analysis_service_description">Escanealas imaxes e os vídeos</string>
<string name="analysis_notification_default_title">Escaneando medios</string>
<string name="analysis_notification_action_stop">Pare</string>
</resources>

View file

@ -8,5 +8,4 @@
<string name="analysis_channel_name">मीडिया जाँचे</string>
<string name="app_name">ऐवीज</string>
<string name="videos_shortcut_short_label">वीडियो</string>
<string name="analysis_service_description">छवि &amp; वीडियो जाँचे</string>
</resources>

View file

@ -8,6 +8,5 @@
<string name="app_widget_label">Fotó keret</string>
<string name="safe_mode_shortcut_short_label">Biztonsági üzemmód</string>
<string name="analysis_channel_name">Tartalom keresése</string>
<string name="analysis_service_description">Képek és videók keresése</string>
<string name="analysis_notification_default_title">Média beolvasása</string>
</resources>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Cari</string>
<string name="videos_shortcut_short_label">Video</string>
<string name="analysis_channel_name">Pindai media</string>
<string name="analysis_service_description">Pindai gambar &amp; video</string>
<string name="analysis_notification_default_title">Memindai media</string>
<string name="analysis_notification_action_stop">Berhenti</string>
<string name="safe_mode_shortcut_short_label">Mode aman</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Ricerca</string>
<string name="videos_shortcut_short_label">Video</string>
<string name="analysis_channel_name">Scansione media</string>
<string name="analysis_service_description">Scansione immagini &amp; videos</string>
<string name="analysis_notification_default_title">Scansione in corso</string>
<string name="analysis_notification_action_stop">Annulla</string>
<string name="safe_mode_shortcut_short_label">Modalità provvisoria</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">חיפוש</string>
<string name="videos_shortcut_short_label">סרטים</string>
<string name="analysis_channel_name">סריקת מדיה</string>
<string name="analysis_service_description">סרוק תמונות וסרטים</string>
<string name="analysis_notification_default_title">סורק מדיה</string>
<string name="analysis_notification_action_stop">הפסק</string>
</resources>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">検索</string>
<string name="videos_shortcut_short_label">動画</string>
<string name="analysis_channel_name">メディアスキャン</string>
<string name="analysis_service_description">画像と動画をスキャン</string>
<string name="analysis_notification_default_title">メディアをスキャン中</string>
<string name="analysis_notification_action_stop">停止</string>
<string name="safe_mode_shortcut_short_label">セーフモード</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">검색</string>
<string name="videos_shortcut_short_label">동영상</string>
<string name="analysis_channel_name">미디어 분석</string>
<string name="analysis_service_description">사진과 동영상 분석</string>
<string name="analysis_notification_default_title">미디어 분석</string>
<string name="analysis_notification_action_stop">취소</string>
<string name="safe_mode_shortcut_short_label">안전 모드</string>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="analysis_service_description">Nuskaityti paveikslėlius ir vaizdo įrašus</string>
<string name="wallpaper">Ekrano paveikslėlis</string>
<string name="videos_shortcut_short_label">Vaizdo įrašai</string>
<string name="app_name">Aves</string>

View file

@ -3,7 +3,6 @@
<string name="app_name">Aves</string>
<string name="videos_shortcut_short_label">Videoer</string>
<string name="analysis_channel_name">Mediaskanning</string>
<string name="analysis_service_description">Skann bilder og videoer</string>
<string name="analysis_notification_default_title">Skanning av media</string>
<string name="app_widget_label">Bilderamme</string>
<string name="wallpaper">Bakgrunnsbilde</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Zoeken</string>
<string name="videos_shortcut_short_label">Videos</string>
<string name="analysis_channel_name">Media indexeren</string>
<string name="analysis_service_description">Indexeren van afdbeeldingen &amp; videos</string>
<string name="analysis_notification_default_title">Indexeren van media</string>
<string name="analysis_notification_action_stop">Stop</string>
</resources>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Søk</string>
<string name="videos_shortcut_short_label">Videoar</string>
<string name="analysis_channel_name">Mediasøking</string>
<string name="analysis_service_description">Søk igjennom bilete og videoar</string>
<string name="analysis_notification_default_title">Søkjer igjennom media</string>
<string name="analysis_notification_action_stop">Stogg</string>
</resources>

View file

@ -4,7 +4,6 @@
<string name="search_shortcut_short_label">Szukaj</string>
<string name="videos_shortcut_short_label">Wideo</string>
<string name="analysis_channel_name">Przeskanuj multimedia</string>
<string name="analysis_service_description">Przeskanuj obrazy oraz wideo</string>
<string name="analysis_notification_default_title">Skanowanie multimediów</string>
<string name="analysis_notification_action_stop">Zatrzymaj</string>
<string name="app_name">Aves</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Procurar</string>
<string name="videos_shortcut_short_label">Vídeos</string>
<string name="analysis_channel_name">Digitalização de mídia</string>
<string name="analysis_service_description">Digitalizar imagens &amp; vídeos</string>
<string name="analysis_notification_default_title">Digitalizando mídia</string>
<string name="analysis_notification_action_stop">Pare</string>
</resources>

View file

@ -5,7 +5,6 @@
<string name="wallpaper">Tapet</string>
<string name="videos_shortcut_short_label">Videoclipuri</string>
<string name="analysis_channel_name">Scanare media</string>
<string name="analysis_service_description">Scanați imagini și videoclipuri</string>
<string name="analysis_notification_default_title">Scanarea suporturilor</string>
<string name="analysis_notification_action_stop">Stop</string>
<string name="search_shortcut_short_label">Căutare</string>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Поиск</string>
<string name="videos_shortcut_short_label">Видео</string>
<string name="analysis_channel_name">Сканировать медия</string>
<string name="analysis_service_description">Сканировать изображения и видео</string>
<string name="analysis_notification_default_title">Сканирование медиа</string>
<string name="analysis_notification_action_stop">Стоп</string>
<string name="safe_mode_shortcut_short_label">Безопасный режим</string>

View file

@ -7,6 +7,5 @@
<string name="videos_shortcut_short_label">Videá</string>
<string name="analysis_notification_action_stop">Zastaviť</string>
<string name="analysis_channel_name">Skenovanie médií</string>
<string name="analysis_service_description">Skenovanie obrázkov &amp; videí</string>
<string name="analysis_notification_default_title">Skenovanie média</string>
</resources>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">ค้นหา</string>
<string name="videos_shortcut_short_label">วิดีโอ</string>
<string name="analysis_channel_name">สแกนสื่อบันเทิง</string>
<string name="analysis_service_description">สแกนรูปภาพและวิดีโอ</string>
<string name="analysis_notification_default_title">กำลังสแกนสื่อบันเทิง</string>
<string name="analysis_notification_action_stop">หยุด</string>
</resources>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">Arama</string>
<string name="videos_shortcut_short_label">Videolar</string>
<string name="analysis_channel_name">Medya tarama</string>
<string name="analysis_service_description">Görüntüleri ve videoları tarayın</string>
<string name="analysis_notification_default_title">Medya taranıyor</string>
<string name="analysis_notification_action_stop">Durdur</string>
</resources>

View file

@ -5,7 +5,6 @@
<string name="search_shortcut_short_label">Пошук</string>
<string name="videos_shortcut_short_label">Відео</string>
<string name="analysis_channel_name">Сканувати медіа</string>
<string name="analysis_service_description">Сканувати зображення та відео</string>
<string name="analysis_notification_action_stop">Стоп</string>
<string name="app_widget_label">Фоторамка</string>
<string name="analysis_notification_default_title">Сканування медіа</string>

View file

@ -7,6 +7,5 @@
<string name="app_widget_label">相框</string>
<string name="search_shortcut_short_label">搜尋</string>
<string name="analysis_channel_name">媒體掃描</string>
<string name="analysis_service_description">掃描圖片和影片</string>
<string name="analysis_notification_action_stop">停止</string>
</resources>

View file

@ -6,7 +6,6 @@
<string name="search_shortcut_short_label">搜索</string>
<string name="videos_shortcut_short_label">视频</string>
<string name="analysis_channel_name">媒体扫描</string>
<string name="analysis_service_description">扫描图像 &amp; 视频</string>
<string name="analysis_notification_default_title">正在扫描媒体库</string>
<string name="analysis_notification_action_stop">停止</string>
</resources>

View file

@ -7,7 +7,6 @@
<string name="search_shortcut_short_label">Search</string>
<string name="videos_shortcut_short_label">Videos</string>
<string name="analysis_channel_name">Media scan</string>
<string name="analysis_service_description">Scan images &amp; videos</string>
<string name="analysis_notification_default_title">Scanning media</string>
<string name="analysis_notification_action_stop">Stop</string>
</resources>

View file

@ -1,6 +1,6 @@
buildscript {
ext {
kotlin_version = '1.8.0'
kotlin_version = '1.8.21'
agp_version = '7.4.2'
glide_version = '4.15.1'
huawei_agconnect_version = '1.8.0.300'

View file

@ -31,7 +31,7 @@ class AnalysisService {
static Future<void> startService({required bool force, List<int>? entryIds}) async {
await reportService.log('Start analysis service${entryIds != null ? ' for ${entryIds.length} items' : ''}');
try {
await _platform.invokeMethod('startService', <String, dynamic>{
await _platform.invokeMethod('startAnalysis', <String, dynamic>{
'entryIds': entryIds,
'force': force,
});
@ -155,7 +155,6 @@ class Analyzer {
void _onSourceStateChanged() {
if (_source.isReady) {
_refreshApp();
_serviceStateNotifier.value = AnalyzerState.stopping;
}
}
@ -179,14 +178,6 @@ class Analyzer {
}
}
Future<void> _refreshApp() async {
try {
await _channel.invokeMethod('refreshApp');
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
}
Future<void> _stopPlatformService() async {
try {
await _channel.invokeMethod('stop');

View file

@ -2,7 +2,7 @@ group 'deckers.thibault.aves.aves_screen_state'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.8.0'
ext.kotlin_version = '1.8.21'
repositories {
google()
mavenCentral()