feat: Move over the service/background service API

This commit is contained in:
PapaTutuWawa 2023-09-09 00:28:01 +02:00
parent 42ff70a966
commit dfbb64c8ae
19 changed files with 1137 additions and 3 deletions

View File

@ -48,5 +48,6 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.activity:activity-ktx:1.7.2"
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
implementation "androidx.datastore:datastore-preferences:1.0.0"
}

View File

@ -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>

View File

@ -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"

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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),
)
}
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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),
)
}
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -1,7 +1,18 @@
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:moxxy_native/moxxy_native.dart';
import 'package:permission_handler/permission_handler.dart';
@pragma('vm:entry-point')
Future<void> entrypoint() async {
WidgetsFlutterBinding.ensureInitialized();
print('CALLED FROM NEW FLUTTERENGINE');
final extra = await MoxxyBackgroundServiceApi().getExtraData();
print('EXTRA DATA: $extra');
}
void main() {
runApp(const MyApp());
@ -96,6 +107,32 @@ class MyAppState extends State<MyApp> {
child: const Text('Test cryptography'),
),
if (imagePath != null) Image.file(File(imagePath!)),
TextButton(
onPressed: () async {
// Create channel
await MoxxyNotificationsApi().createNotificationChannels(
[
NotificationChannel(
id: 'foreground_service',
title: 'Foreground service',
description: 'lol',
importance: NotificationChannelImportance.MIN,
showBadge: false,
vibration: false,
enableLights: false,
),
],
);
await Permission.notification.request();
final handle = PluginUtilities.getCallbackHandle(entrypoint)!
.toRawHandle();
final api = MoxxyServiceApi();
await api.configure(handle, 'lol');
await api.start();
},
child: const Text('Start foreground service')),
],
),
),

View File

@ -130,6 +130,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.2"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
url: "https://pub.dev"
source: hosted
version: "10.4.5"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
url: "https://pub.dev"
source: hosted
version: "10.3.6"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
url: "https://pub.dev"
source: hosted
version: "9.1.4"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: f2343e9fa9c22ae4fd92d4732755bfe452214e7189afcc097380950cf567b4b2
url: "https://pub.dev"
source: hosted
version: "3.11.5"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
url: "https://pub.dev"
source: hosted
version: "0.1.3"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
url: "https://pub.dev"
source: hosted
version: "2.1.6"
sky_engine:
dependency: transitive
description: flutter
@ -193,4 +241,4 @@ packages:
version: "2.1.4"
sdks:
dart: ">=2.19.6 <3.0.0"
flutter: ">=2.5.0"
flutter: ">=2.8.0"

View File

@ -28,6 +28,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
permission_handler: ^10.4.5
dev_dependencies:
flutter_test:

View File

@ -66,5 +66,19 @@
# an used parameter.
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${sdk}/share/android-sdk/build-tools/34.0.0/aapt2";
};
apps = {
androidLint = let
script = pkgs.writeShellScript "lint-android.sh" ''
${pkgs.ktlint}/bin/ktlint \
--format \
--disabled_rules=standard:package-name \
android/src/main/kotlin/org/moxxy/moxxy_native/
'';
in {
program = "${script}";
type = "app";
};
};
});
}

View File

@ -1,6 +1,8 @@
export 'pigeon/background_service.g.dart';
export 'pigeon/contacts.g.dart';
export 'pigeon/cryptography.g.dart';
export 'pigeon/media.g.dart';
export 'pigeon/notifications.g.dart';
export 'pigeon/picker.g.dart';
export 'pigeon/platform.g.dart';
export 'pigeon/service.g.dart';

View File

@ -0,0 +1,140 @@
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
class MoxxyBackgroundServiceApi {
/// Constructor for [MoxxyBackgroundServiceApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
MoxxyBackgroundServiceApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;
static const MessageCodec<Object?> codec = StandardMessageCodec();
Future<int> getHandler() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getHandler', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as int?)!;
}
}
Future<String> getExtraData() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getExtraData', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as String?)!;
}
}
Future<void> setNotificationBody(String arg_body) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.setNotificationBody', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_body]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<void> sendData(String arg_data) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.sendData', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_data]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<void> stop() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.stop', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}

113
lib/pigeon/service.g.dart Normal file
View File

@ -0,0 +1,113 @@
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
class MoxxyServiceApi {
/// Constructor for [MoxxyServiceApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
MoxxyServiceApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;
static const MessageCodec<Object?> codec = StandardMessageCodec();
Future<void> configure(int arg_handle, String arg_extraData) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.configure', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_handle, arg_extraData]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<bool> isRunning() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.isRunning', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as bool?)!;
}
}
Future<void> start() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.start', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<void> sendData(String arg_data) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.sendData', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_data]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}

View File

@ -0,0 +1,25 @@
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/pigeon/background_service.g.dart',
kotlinOut:
'android/src/main/kotlin/org/moxxy/moxxy_native/service/background/BackgroundServiceApi.kt',
kotlinOptions: KotlinOptions(
package: 'org.moxxy.moxxy_native.service.background',
),
),
)
@HostApi()
abstract class MoxxyBackgroundServiceApi {
int getHandler();
String getExtraData();
void setNotificationBody(String body);
void sendData(String data);
void stop();
}

23
pigeon/service.dart Normal file
View File

@ -0,0 +1,23 @@
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/pigeon/service.g.dart',
kotlinOut:
'android/src/main/kotlin/org/moxxy/moxxy_native/service/ServiceApi.kt',
kotlinOptions: KotlinOptions(
package: 'org.moxxy.moxxy_native.service',
),
),
)
@HostApi()
abstract class MoxxyServiceApi {
void configure(int handle, String extraData);
bool isRunning();
void start();
void sendData(String data);
}