feat: Move over the service/background service API
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<application>
|
||||
<provider
|
||||
android:name="org.moxxy.moxxy_native.content.MoxxyFileProvider"
|
||||
android:authorities="org.moxxy.moxxyv2.fileprovider"
|
||||
android:authorities="org.moxxy.moxxyv2.fileprovider2"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
@@ -12,8 +12,36 @@
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:name="org.moxxy.moxxy_native.service.BackgroundService"
|
||||
/>
|
||||
|
||||
<receiver
|
||||
android:name="org.moxxy.moxxy_native.service.WatchdogReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
/>
|
||||
|
||||
<receiver android:name="org.moxxy.moxxy_native.service.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.moxxy.moxxy_native.notifications.NotificationReceiver" />
|
||||
</application>
|
||||
|
||||
<!-- Foreground service -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<!-- Notifications -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
</manifest>
|
||||
|
||||
@@ -37,3 +37,16 @@ const val SHARED_PREFERENCES_AVATAR_KEY = "avatar_path"
|
||||
const val PICK_FILE_REQUEST = 42
|
||||
const val PICK_FILES_REQUEST = 43
|
||||
const val PICK_FILE_WITH_DATA_REQUEST = 44
|
||||
|
||||
// Service
|
||||
const val SERVICE_SHARED_PREFERENCES_KEY = "me.polynom.moxplatform_android"
|
||||
const val SERVICE_ENTRYPOINT_KEY = "entrypoint_handle"
|
||||
const val SERVICE_EXTRA_DATA_KEY = "extra_data"
|
||||
const val SERVICE_START_AT_BOOT_KEY = "auto_start_at_boot"
|
||||
const val SERVICE_MANUALLY_STOPPED_KEY = "manually_stopped"
|
||||
|
||||
// https://github.com/ekasetiawans/flutter_background_service/blob/e427f3b70138ec26f9671c2617f9061f25eade6f/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/BootReceiver.java#L20
|
||||
const val SERVICE_WAKELOCK_DURATION = 10 * 60 * 1000L
|
||||
const val SERVICE_DEFAULT_TITLE = "Moxxy"
|
||||
const val SERVICE_DEFAULT_BODY = "Preparing..."
|
||||
const val SERVICE_BACKGROUND_METHOD_CHANNEL_KEY = "org.moxxy.moxxy_native/background"
|
||||
|
||||
@@ -10,6 +10,8 @@ import androidx.annotation.NonNull
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.embedding.engine.plugins.service.ServiceAware
|
||||
import io.flutter.embedding.engine.plugins.service.ServicePluginBinding
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import org.moxxy.moxxy_native.contacts.ContactsImplementation
|
||||
import org.moxxy.moxxy_native.contacts.MoxxyContactsApi
|
||||
@@ -25,6 +27,10 @@ import org.moxxy.moxxy_native.picker.MoxxyPickerApi
|
||||
import org.moxxy.moxxy_native.picker.PickerResultListener
|
||||
import org.moxxy.moxxy_native.platform.MoxxyPlatformApi
|
||||
import org.moxxy.moxxy_native.platform.PlatformImplementation
|
||||
import org.moxxy.moxxy_native.service.BackgroundService
|
||||
import org.moxxy.moxxy_native.service.MoxxyServiceApi
|
||||
import org.moxxy.moxxy_native.service.PluginTracker
|
||||
import org.moxxy.moxxy_native.service.ServiceImplementation
|
||||
|
||||
object MoxxyEventChannels {
|
||||
var notificationChannel: EventChannel? = null
|
||||
@@ -50,7 +56,7 @@ object NotificationCache {
|
||||
var lastEvent: NotificationEvent? = null
|
||||
}
|
||||
|
||||
class MoxxyNativePlugin : FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
||||
class MoxxyNativePlugin : FlutterPlugin, ActivityAware, ServiceAware, MoxxyPickerApi {
|
||||
private var context: Context? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var pickerListener: PickerResultListener
|
||||
@@ -59,12 +65,20 @@ class MoxxyNativePlugin : FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
||||
private lateinit var platformImplementation: PlatformImplementation
|
||||
private val mediaImplementation = MediaImplementation()
|
||||
private lateinit var notificationsImplementation: NotificationsImplementation
|
||||
private lateinit var serviceImplementation: ServiceImplementation
|
||||
|
||||
var service: BackgroundService? = null
|
||||
|
||||
init {
|
||||
PluginTracker.instances.add(this)
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
context = flutterPluginBinding.applicationContext
|
||||
contactsImplementation = ContactsImplementation(context!!)
|
||||
platformImplementation = PlatformImplementation(context!!)
|
||||
notificationsImplementation = NotificationsImplementation(context!!)
|
||||
serviceImplementation = ServiceImplementation(context!!)
|
||||
|
||||
// Register the pigeon handlers
|
||||
MoxxyPickerApi.setUp(flutterPluginBinding.binaryMessenger, this)
|
||||
@@ -73,6 +87,7 @@ class MoxxyNativePlugin : FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
||||
MoxxyContactsApi.setUp(flutterPluginBinding.binaryMessenger, contactsImplementation)
|
||||
MoxxyPlatformApi.setUp(flutterPluginBinding.binaryMessenger, platformImplementation)
|
||||
MoxxyMediaApi.setUp(flutterPluginBinding.binaryMessenger, mediaImplementation)
|
||||
MoxxyServiceApi.setUp(flutterPluginBinding.binaryMessenger, serviceImplementation)
|
||||
|
||||
// Register the picker handler
|
||||
pickerListener = PickerResultListener(context!!)
|
||||
@@ -103,6 +118,16 @@ class MoxxyNativePlugin : FlutterPlugin, ActivityAware, MoxxyPickerApi {
|
||||
Log.d(TAG, "Detached from activity")
|
||||
}
|
||||
|
||||
override fun onAttachedToService(binding: ServicePluginBinding) {
|
||||
Log.d(TAG, "Attached to service")
|
||||
service = binding.getService() as BackgroundService
|
||||
}
|
||||
|
||||
override fun onDetachedFromService() {
|
||||
Log.d(TAG, "Detached from service")
|
||||
service = null
|
||||
}
|
||||
|
||||
override fun pickFiles(
|
||||
type: FilePickerType,
|
||||
multiple: Boolean,
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
package org.moxxy.moxxy_native.service
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import android.os.PowerManager.WakeLock
|
||||
import android.util.Log
|
||||
import androidx.core.app.AlarmManagerCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import io.flutter.FlutterInjector
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.dart.DartExecutor
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.view.FlutterCallbackInformation
|
||||
import org.moxxy.moxxy_native.R
|
||||
import org.moxxy.moxxy_native.SERVICE_BACKGROUND_METHOD_CHANNEL_KEY
|
||||
import org.moxxy.moxxy_native.SERVICE_DEFAULT_BODY
|
||||
import org.moxxy.moxxy_native.SERVICE_DEFAULT_TITLE
|
||||
import org.moxxy.moxxy_native.SERVICE_ENTRYPOINT_KEY
|
||||
import org.moxxy.moxxy_native.SERVICE_EXTRA_DATA_KEY
|
||||
import org.moxxy.moxxy_native.SERVICE_MANUALLY_STOPPED_KEY
|
||||
import org.moxxy.moxxy_native.SERVICE_SHARED_PREFERENCES_KEY
|
||||
import org.moxxy.moxxy_native.SERVICE_START_AT_BOOT_KEY
|
||||
import org.moxxy.moxxy_native.SERVICE_WAKELOCK_DURATION
|
||||
import org.moxxy.moxxy_native.TAG
|
||||
import org.moxxy.moxxy_native.service.background.MoxxyBackgroundServiceApi
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
object BackgroundServiceStatic {
|
||||
@Volatile
|
||||
var wakeLock: WakeLock? = null
|
||||
|
||||
fun acquireWakeLock(context: Context): WakeLock {
|
||||
if (wakeLock == null) {
|
||||
val manager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
wakeLock =
|
||||
manager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "${this.javaClass.name}.class")
|
||||
wakeLock!!.setReferenceCounted(true)
|
||||
}
|
||||
|
||||
return wakeLock!!
|
||||
}
|
||||
|
||||
fun enqueue(context: Context) {
|
||||
val mutable =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
111,
|
||||
Intent(context, WatchdogReceiver::class.java),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or mutable,
|
||||
)
|
||||
|
||||
AlarmManagerCompat.setAndAllowWhileIdle(
|
||||
context.getSystemService(Context.ALARM_SERVICE) as AlarmManager,
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
System.currentTimeMillis() + 5000,
|
||||
pendingIntent,
|
||||
)
|
||||
}
|
||||
|
||||
fun getStartAtBoot(context: Context): Boolean {
|
||||
return context.getSharedPreferences(SERVICE_SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
|
||||
.getBoolean(
|
||||
SERVICE_START_AT_BOOT_KEY,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fun setStartAtBoot(context: Context, value: Boolean) {
|
||||
context.getSharedPreferences(SERVICE_SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).edit()
|
||||
.putBoolean(SERVICE_START_AT_BOOT_KEY, value)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun getManuallyStopped(context: Context): Boolean {
|
||||
return context.getSharedPreferences(SERVICE_SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
|
||||
.getBoolean(
|
||||
SERVICE_MANUALLY_STOPPED_KEY,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class BackgroundService : Service(), MoxxyBackgroundServiceApi {
|
||||
|
||||
// Indicates whether the background service is running or not
|
||||
private var isRunning = AtomicBoolean(false)
|
||||
|
||||
// Indicates whether the service was stopped manually
|
||||
private var isManuallyStopped = false
|
||||
|
||||
// If non-null, the Flutter Engine that is running the background service's code
|
||||
private var engine: FlutterEngine? = null
|
||||
|
||||
// The callback for Dart to start execution at
|
||||
private var dartCallback: DartExecutor.DartCallback? = null
|
||||
|
||||
// Method channel for Java -> Dart
|
||||
private var methodChannel: MethodChannel? = null
|
||||
|
||||
// Data for the notification
|
||||
private var notificationTitle: String = SERVICE_DEFAULT_TITLE
|
||||
private var notificationBody: String = SERVICE_DEFAULT_BODY
|
||||
|
||||
private fun setManuallyStopped(context: Context, value: Boolean) {
|
||||
context.getSharedPreferences(SERVICE_SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).edit()
|
||||
.putBoolean(SERVICE_MANUALLY_STOPPED_KEY, value)
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun getHandle(): Long {
|
||||
return getSharedPreferences(SERVICE_SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getLong(
|
||||
SERVICE_ENTRYPOINT_KEY,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateNotificationInfo() {
|
||||
val mutable =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
99778,
|
||||
packageManager.getLaunchIntentForPackage(applicationContext.packageName),
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or mutable,
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(this, "foreground_service").apply {
|
||||
setSmallIcon(R.drawable.ic_service)
|
||||
setAutoCancel(false)
|
||||
setOngoing(true)
|
||||
setContentTitle(notificationTitle)
|
||||
setContentText(notificationBody)
|
||||
setContentIntent(pendingIntent)
|
||||
}.build()
|
||||
startForeground(99778, notification)
|
||||
}
|
||||
|
||||
private fun runService() {
|
||||
try {
|
||||
if (isRunning.get() || (engine?.getDartExecutor()?.isExecutingDart() ?: false)) return
|
||||
|
||||
if (BackgroundServiceStatic.wakeLock == null) {
|
||||
Log.d(TAG, "WakeLock is null. Acquiring and grabbing WakeLock...")
|
||||
BackgroundServiceStatic.acquireWakeLock(applicationContext)
|
||||
.acquire(SERVICE_WAKELOCK_DURATION)
|
||||
Log.d(TAG, "WakeLock grabbed")
|
||||
}
|
||||
|
||||
// Update the notification
|
||||
updateNotificationInfo()
|
||||
|
||||
// Set-up the Flutter Engine, if it's not already set up
|
||||
if (!FlutterInjector.instance().flutterLoader().initialized()) {
|
||||
FlutterInjector.instance().flutterLoader().startInitialization(applicationContext)
|
||||
}
|
||||
FlutterInjector.instance().flutterLoader().ensureInitializationComplete(
|
||||
applicationContext,
|
||||
null,
|
||||
)
|
||||
val callback: FlutterCallbackInformation =
|
||||
FlutterCallbackInformation.lookupCallbackInformation(getHandle())
|
||||
if (callback == null) {
|
||||
Log.e(TAG, "Callback handle not found")
|
||||
return
|
||||
}
|
||||
isRunning.set(true)
|
||||
engine = FlutterEngine(this)
|
||||
engine!!.getServiceControlSurface().attachToService(this, null, true)
|
||||
methodChannel = MethodChannel(
|
||||
engine!!.getDartExecutor()!!.getBinaryMessenger(),
|
||||
SERVICE_BACKGROUND_METHOD_CHANNEL_KEY,
|
||||
)
|
||||
|
||||
MoxxyBackgroundServiceApi.setUp(engine!!.getDartExecutor()!!.getBinaryMessenger(), this)
|
||||
Log.d(TAG, "MoxxyBackgroundServiceApi ready")
|
||||
|
||||
dartCallback = DartExecutor.DartCallback(
|
||||
assets,
|
||||
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
|
||||
callback,
|
||||
)
|
||||
engine!!.getDartExecutor().executeDartCallback(dartCallback!!)
|
||||
} catch (ex: UnsatisfiedLinkError) {
|
||||
Log.e(TAG, "Failed to set up background service: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
notificationBody = SERVICE_DEFAULT_BODY
|
||||
updateNotificationInfo()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (!isManuallyStopped) {
|
||||
BackgroundServiceStatic.enqueue(this)
|
||||
} else {
|
||||
setManuallyStopped(applicationContext, true)
|
||||
}
|
||||
|
||||
// Dispose of the engine
|
||||
engine?.apply {
|
||||
getServiceControlSurface().detachFromService()
|
||||
destroy()
|
||||
}
|
||||
engine = null
|
||||
dartCallback = null
|
||||
|
||||
// Stop the service
|
||||
stopForeground(true)
|
||||
isRunning.set(false)
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
fun receiveData(data: String) {
|
||||
methodChannel?.invokeMethod("dataReceived", data)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
setManuallyStopped(this, false)
|
||||
BackgroundServiceStatic.enqueue(this)
|
||||
runService()
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun getHandler(): Long {
|
||||
return getHandle()
|
||||
}
|
||||
|
||||
override fun getExtraData(): String {
|
||||
return getSharedPreferences(SERVICE_SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getString(
|
||||
SERVICE_EXTRA_DATA_KEY,
|
||||
"",
|
||||
)!!
|
||||
}
|
||||
|
||||
override fun setNotificationBody(body: String) {
|
||||
notificationBody = body
|
||||
updateNotificationInfo()
|
||||
}
|
||||
|
||||
override fun sendData(data: String) {
|
||||
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(
|
||||
Intent(SERVICE_BACKGROUND_METHOD_CHANNEL_KEY).apply {
|
||||
putExtra("data", data)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
isManuallyStopped = true
|
||||
val mutable =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
applicationContext,
|
||||
111,
|
||||
Intent(this, WatchdogReceiver::class.java),
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or mutable,
|
||||
)
|
||||
val stopManager = getSystemService(ALARM_SERVICE) as AlarmManager
|
||||
stopManager.cancel(pendingIntent)
|
||||
stopSelf()
|
||||
BackgroundServiceStatic.setStartAtBoot(applicationContext, false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.moxxy.moxxy_native.service
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.moxxy.moxxy_native.TAG
|
||||
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (BackgroundServiceStatic.getStartAtBoot(context)) {
|
||||
if (BackgroundServiceStatic.wakeLock == null) {
|
||||
Log.d(TAG, "WakeLock is null. Acquiring it...")
|
||||
BackgroundServiceStatic.acquireWakeLock(context)
|
||||
Log.d(TAG, "WakeLock acquired")
|
||||
}
|
||||
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
Intent(context, BackgroundService::class.java),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
package org.moxxy.moxxy_native.service
|
||||
|
||||
import android.util.Log
|
||||
import io.flutter.plugin.common.BasicMessageChannel
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.MessageCodec
|
||||
import io.flutter.plugin.common.StandardMessageCodec
|
||||
|
||||
private fun wrapResult(result: Any?): List<Any?> {
|
||||
return listOf(result)
|
||||
}
|
||||
|
||||
private fun wrapError(exception: Throwable): List<Any?> {
|
||||
if (exception is FlutterError) {
|
||||
return listOf(
|
||||
exception.code,
|
||||
exception.message,
|
||||
exception.details,
|
||||
)
|
||||
} else {
|
||||
return listOf(
|
||||
exception.javaClass.simpleName,
|
||||
exception.toString(),
|
||||
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
||||
* @property code The error code.
|
||||
* @property message The error message.
|
||||
* @property details The error details. Must be a datatype supported by the api codec.
|
||||
*/
|
||||
class FlutterError(
|
||||
val code: String,
|
||||
override val message: String? = null,
|
||||
val details: Any? = null,
|
||||
) : Throwable()
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface MoxxyServiceApi {
|
||||
fun configure(handle: Long, extraData: String)
|
||||
fun isRunning(): Boolean
|
||||
fun start()
|
||||
fun sendData(data: String)
|
||||
|
||||
companion object {
|
||||
/** The codec used by MoxxyServiceApi. */
|
||||
val codec: MessageCodec<Any?> by lazy {
|
||||
StandardMessageCodec()
|
||||
}
|
||||
|
||||
/** Sets up an instance of `MoxxyServiceApi` to handle messages through the `binaryMessenger`. */
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun setUp(binaryMessenger: BinaryMessenger, api: MoxxyServiceApi?) {
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.configure", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val handleArg = args[0].let { if (it is Int) it.toLong() else it as Long }
|
||||
val extraDataArg = args[1] as String
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
api.configure(handleArg, extraDataArg)
|
||||
wrapped = listOf<Any?>(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.isRunning", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
wrapped = listOf<Any?>(api.isRunning())
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.start", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
api.start()
|
||||
wrapped = listOf<Any?>(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.sendData", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val dataArg = args[0] as String
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
api.sendData(dataArg)
|
||||
wrapped = listOf<Any?>(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.moxxy.moxxy_native.service
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.moxxy.moxxy_native.MoxxyNativePlugin
|
||||
import org.moxxy.moxxy_native.SERVICE_ENTRYPOINT_KEY
|
||||
import org.moxxy.moxxy_native.SERVICE_EXTRA_DATA_KEY
|
||||
import org.moxxy.moxxy_native.SERVICE_SHARED_PREFERENCES_KEY
|
||||
import org.moxxy.moxxy_native.TAG
|
||||
import org.moxxy.moxxy_native.service.BackgroundServiceStatic.setStartAtBoot
|
||||
|
||||
object PluginTracker {
|
||||
var instances: MutableList<MoxxyNativePlugin> = mutableListOf()
|
||||
}
|
||||
|
||||
class ServiceImplementation(private val context: Context) : MoxxyServiceApi {
|
||||
override fun configure(handle: Long, extraData: String) {
|
||||
context.getSharedPreferences(SERVICE_SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).edit()
|
||||
.putLong(SERVICE_ENTRYPOINT_KEY, handle)
|
||||
.putString(SERVICE_EXTRA_DATA_KEY, extraData)
|
||||
.apply()
|
||||
}
|
||||
|
||||
override fun isRunning(): Boolean {
|
||||
val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
for (info in manager.getRunningServices(Int.MAX_VALUE)) {
|
||||
if (BackgroundService::class.java.name == info.service.className) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
setStartAtBoot(context, true)
|
||||
BackgroundServiceStatic.enqueue(context)
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
Intent(context, BackgroundService::class.java),
|
||||
)
|
||||
Log.d(TAG, "Background service started")
|
||||
}
|
||||
|
||||
override fun sendData(data: String) {
|
||||
for (plugin in PluginTracker.instances) {
|
||||
val service = plugin.service
|
||||
if (service != null) {
|
||||
service.receiveData(data)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.moxxy.moxxy_native.service
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.moxxy.moxxy_native.service.BackgroundServiceStatic.getManuallyStopped
|
||||
|
||||
class WatchdogReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
if (!getManuallyStopped(context)) {
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
Intent(context, BackgroundService::class.java),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
package org.moxxy.moxxy_native.service.background
|
||||
|
||||
import android.util.Log
|
||||
import io.flutter.plugin.common.BasicMessageChannel
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.MessageCodec
|
||||
import io.flutter.plugin.common.StandardMessageCodec
|
||||
|
||||
private fun wrapResult(result: Any?): List<Any?> {
|
||||
return listOf(result)
|
||||
}
|
||||
|
||||
private fun wrapError(exception: Throwable): List<Any?> {
|
||||
if (exception is FlutterError) {
|
||||
return listOf(
|
||||
exception.code,
|
||||
exception.message,
|
||||
exception.details,
|
||||
)
|
||||
} else {
|
||||
return listOf(
|
||||
exception.javaClass.simpleName,
|
||||
exception.toString(),
|
||||
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
||||
* @property code The error code.
|
||||
* @property message The error message.
|
||||
* @property details The error details. Must be a datatype supported by the api codec.
|
||||
*/
|
||||
class FlutterError(
|
||||
val code: String,
|
||||
override val message: String? = null,
|
||||
val details: Any? = null,
|
||||
) : Throwable()
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface MoxxyBackgroundServiceApi {
|
||||
fun getHandler(): Long
|
||||
fun getExtraData(): String
|
||||
fun setNotificationBody(body: String)
|
||||
fun sendData(data: String)
|
||||
fun stop()
|
||||
|
||||
companion object {
|
||||
/** The codec used by MoxxyBackgroundServiceApi. */
|
||||
val codec: MessageCodec<Any?> by lazy {
|
||||
StandardMessageCodec()
|
||||
}
|
||||
|
||||
/** Sets up an instance of `MoxxyBackgroundServiceApi` to handle messages through the `binaryMessenger`. */
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun setUp(binaryMessenger: BinaryMessenger, api: MoxxyBackgroundServiceApi?) {
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getHandler", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
wrapped = listOf<Any?>(api.getHandler())
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getExtraData", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
wrapped = listOf<Any?>(api.getExtraData())
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.setNotificationBody", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val bodyArg = args[0] as String
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
api.setNotificationBody(bodyArg)
|
||||
wrapped = listOf<Any?>(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.sendData", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val dataArg = args[0] as String
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
api.sendData(dataArg)
|
||||
wrapped = listOf<Any?>(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.stop", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
var wrapped: List<Any?>
|
||||
try {
|
||||
api.stop()
|
||||
wrapped = listOf<Any?>(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapped = wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user