Skip to content

Commit

Permalink
Merge pull request #177 from Flasher-Dev/master
Browse files Browse the repository at this point in the history
Add Parallel DFU Support
  • Loading branch information
juliansteenbakker authored Dec 17, 2024
2 parents 9e6e794 + 7095e53 commit 0ab32cb
Show file tree
Hide file tree
Showing 13 changed files with 603 additions and 202 deletions.
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Thu Jul 13 09:14:07 BRT 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
23 changes: 23 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,28 @@
<service android:name=".DfuService"
android:foregroundServiceType="connectedDevice"
android:exported="false"/>
<service android:name=".DfuService2"
android:foregroundServiceType="connectedDevice"
android:exported="false"/>
<service android:name=".DfuService3"
android:foregroundServiceType="connectedDevice"
android:exported="false"/>
<service android:name=".DfuService4"
android:foregroundServiceType="connectedDevice"
android:exported="false"/>
<service android:name=".DfuService5"
android:foregroundServiceType="connectedDevice"
android:exported="false"/>
<service android:name=".DfuService6"
android:foregroundServiceType="connectedDevice"
android:exported="false"/>
<service android:name=".DfuService7"
android:foregroundServiceType="connectedDevice"
android:exported="false"/>
<service android:name=".DfuService8"
android:foregroundServiceType="connectedDevice"
android:exported="false"/>
<!-- more service classes can be added here to support more parallel DFU processes.
make sure to also update DFU_SERVICE_CLASSES in NordicDfuPlugin.kt -->
</application>
</manifest>
17 changes: 17 additions & 0 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/DfuService2.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.steenbakker.nordicdfu

import android.app.Activity
import no.nordicsemi.android.dfu.DfuBaseService

class DfuService2 : DfuBaseService() {
override fun getNotificationTarget(): Class<out Activity?> {
return NotificationActivity::class.java
}

override fun isDebug(): Boolean {
// Override this method and return true if you need more logs in LogCat
// Note: BuildConfig.DEBUG always returns false in library projects, so please use
// your app package BuildConfig
return true
}
}
17 changes: 17 additions & 0 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/DfuService3.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.steenbakker.nordicdfu

import android.app.Activity
import no.nordicsemi.android.dfu.DfuBaseService

class DfuService3 : DfuBaseService() {
override fun getNotificationTarget(): Class<out Activity?> {
return NotificationActivity::class.java
}

override fun isDebug(): Boolean {
// Override this method and return true if you need more logs in LogCat
// Note: BuildConfig.DEBUG always returns false in library projects, so please use
// your app package BuildConfig
return true
}
}
17 changes: 17 additions & 0 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/DfuService4.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.steenbakker.nordicdfu

import android.app.Activity
import no.nordicsemi.android.dfu.DfuBaseService

class DfuService4 : DfuBaseService() {
override fun getNotificationTarget(): Class<out Activity?> {
return NotificationActivity::class.java
}

override fun isDebug(): Boolean {
// Override this method and return true if you need more logs in LogCat
// Note: BuildConfig.DEBUG always returns false in library projects, so please use
// your app package BuildConfig
return true
}
}
17 changes: 17 additions & 0 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/DfuService5.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.steenbakker.nordicdfu

import android.app.Activity
import no.nordicsemi.android.dfu.DfuBaseService

class DfuService5 : DfuBaseService() {
override fun getNotificationTarget(): Class<out Activity?> {
return NotificationActivity::class.java
}

override fun isDebug(): Boolean {
// Override this method and return true if you need more logs in LogCat
// Note: BuildConfig.DEBUG always returns false in library projects, so please use
// your app package BuildConfig
return true
}
}
17 changes: 17 additions & 0 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/DfuService6.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.steenbakker.nordicdfu

import android.app.Activity
import no.nordicsemi.android.dfu.DfuBaseService

class DfuService6 : DfuBaseService() {
override fun getNotificationTarget(): Class<out Activity?> {
return NotificationActivity::class.java
}

override fun isDebug(): Boolean {
// Override this method and return true if you need more logs in LogCat
// Note: BuildConfig.DEBUG always returns false in library projects, so please use
// your app package BuildConfig
return true
}
}
17 changes: 17 additions & 0 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/DfuService7.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.steenbakker.nordicdfu

import android.app.Activity
import no.nordicsemi.android.dfu.DfuBaseService

class DfuService7 : DfuBaseService() {
override fun getNotificationTarget(): Class<out Activity?> {
return NotificationActivity::class.java
}

override fun isDebug(): Boolean {
// Override this method and return true if you need more logs in LogCat
// Note: BuildConfig.DEBUG always returns false in library projects, so please use
// your app package BuildConfig
return true
}
}
17 changes: 17 additions & 0 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/DfuService8.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.steenbakker.nordicdfu

import android.app.Activity
import no.nordicsemi.android.dfu.DfuBaseService

class DfuService8 : DfuBaseService() {
override fun getNotificationTarget(): Class<out Activity?> {
return NotificationActivity::class.java
}

override fun isDebug(): Boolean {
// Override this method and return true if you need more logs in LogCat
// Note: BuildConfig.DEBUG always returns false in library projects, so please use
// your app package BuildConfig
return true
}
}
110 changes: 89 additions & 21 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/NordicDfuPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,44 @@ import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import no.nordicsemi.android.dfu.DfuBaseService
import no.nordicsemi.android.dfu.DfuBaseService.NOTIFICATION_ID
import no.nordicsemi.android.dfu.DfuProgressListenerAdapter
import no.nordicsemi.android.dfu.DfuServiceController
import no.nordicsemi.android.dfu.DfuServiceInitiator
import no.nordicsemi.android.dfu.DfuServiceListenerHelper
import java.util.*
import android.util.Log

private class DfuProcess(
val deviceAddress: String,
val controller: DfuServiceController,
val pendingResult: MethodChannel.Result,
val serviceClass: Class<out DfuBaseService>
)

private val DFU_SERVICE_CLASSES = arrayListOf<Class<out DfuBaseService>>(
DfuService::class.java,
DfuService2::class.java,
DfuService3::class.java,
DfuService4::class.java,
DfuService5::class.java,
DfuService6::class.java,
DfuService7::class.java,
DfuService8::class.java,
// more service classes can be added here to support more parallel DFU processes
// (make sure to also update AndroidManifest.xml)
)

class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {

private var mContext: Context? = null

private var pendingResult: MethodChannel.Result? = null
private var methodChannel: MethodChannel? = null
private var eventChannel: EventChannel? = null
private var sink: EventChannel.EventSink? = null
private var activeDfuMap: MutableMap<String, DfuProcess> = mutableMapOf()

private var controller: DfuServiceController? = null
private var hasCreateNotification = false

override fun onAttachedToEngine(binding: FlutterPluginBinding) {
Expand All @@ -50,7 +71,7 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHan
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"startDfu" -> initiateDfu(call, result)
"abortDfu" -> abortDfu()
"abortDfu" -> abortDfu(call, result)
else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -107,7 +128,6 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHan
// now, the path is an absolute path, and can pass it to nordic dfu libarary
filePath = tempFileName
}
pendingResult = result
startDfu(
address,
name,
Expand All @@ -127,10 +147,43 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHan
)
}

private fun abortDfu() {
if (controller != null) {
controller!!.abort()
// Aborts ongoing DFU processes.
//
// If `call.argument("address")` is null, the abort command will be sent to all active DFU controllers
// If `call.argument("address")` is provided, the abort command will be sent to the specific DFU controller
//
// Note: the underlying controller implementation does not currently support individual aborts;
// all active DFU processes are affected. See: https://github.com/NordicSemiconductor/Android-DFU-Library/blob/0c559244b34ebd27a4f51f045c067b965f918b73/lib/dfu/src/main/java/no/nordicsemi/android/dfu/DfuServiceController.java#L31-L39
//
// Per-address abort handling is included here for cross-platform consistency and future compatability.
private fun abortDfu(call: MethodCall, result: MethodChannel.Result) {
val address = call.argument<String>("address")

if (address == null) {
// Abort all DFU processes
if (activeDfuMap.isEmpty()) {
result.error("NO_ACTIVE_DFU", "No active DFU processes to abort", null)
return
}
activeDfuMap.values.forEach { it.controller.abort() }
result.success(null)
return
}

// Abort DFU process for the specified address
val process = activeDfuMap[address]
if (process == null) {
result.error("INVALID_ADDRESS", "No DFU process found for address: $address", null)
return
}

// Log a warning if multiple DFU processes are active
if (activeDfuMap.size > 1) {
Log.w("[NordicDfu]", "abortDfu will abort all DFU processes")
}

process.controller.abort()
result.success(null)
}

private fun startDfu(
Expand Down Expand Up @@ -180,7 +233,6 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHan
if (rebootTime != null) {
starter.setRebootTime(rebootTime)
}
pendingResult = result

// fix notification on android 8 and above
if (startAsForegroundService == null || startAsForegroundService) {
Expand All @@ -189,7 +241,25 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHan
hasCreateNotification = true
}
}
controller = starter.start(mContext!!, DfuService::class.java)

val serviceClass = getAvailableDfuServiceClass() ?: run {
result.error("PARALLEL_LIMIT_REACHED", "No available DFU service slots", null)
return
}
val controller = starter.start(mContext!!, serviceClass)

activeDfuMap[address] = DfuProcess(
deviceAddress = address,
controller = controller,
pendingResult = result,
serviceClass = serviceClass
)
}

private fun getAvailableDfuServiceClass(): Class<out DfuBaseService>? {
return DFU_SERVICE_CLASSES.firstOrNull { serviceClass ->
activeDfuMap.values.none { it.serviceClass == serviceClass }
}
}

private fun cancelNotification() {
Expand Down Expand Up @@ -219,14 +289,12 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHan
parameters["errorType"] = errorType
parameters["message"] = message
sink?.success(mapOf("onError" to parameters))
if (pendingResult != null) {
pendingResult!!.error(
"$error",
"DFU FAILED: $message",
"Address: $deviceAddress, Error Type: $errorType"
)
pendingResult = null
}
activeDfuMap[deviceAddress]?.pendingResult?.error(
"$error",
"DFU FAILED: $message",
"Address: $deviceAddress, Error Type: $errorType"
)
activeDfuMap.remove(deviceAddress)
}

override fun onDeviceConnecting(deviceAddress: String) {
Expand All @@ -248,18 +316,18 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHan
super.onDfuAborted(deviceAddress)
cancelNotification()
sink?.success(mapOf("onDfuAborted" to deviceAddress))
pendingResult?.error(
activeDfuMap[deviceAddress]?.pendingResult?.error(
"DFU_ABORTED", "DFU ABORTED by user", "device address: $deviceAddress"
)
pendingResult = null
activeDfuMap.remove(deviceAddress)
}

override fun onDfuCompleted(deviceAddress: String) {
super.onDfuCompleted(deviceAddress)
cancelNotification()
sink?.success(mapOf("onDfuCompleted" to deviceAddress))
pendingResult?.success(deviceAddress)
pendingResult = null
activeDfuMap[deviceAddress]?.pendingResult?.success(deviceAddress)
activeDfuMap.remove(deviceAddress)
}

override fun onDfuProcessStarted(deviceAddress: String) {
Expand Down
Loading

0 comments on commit 0ab32cb

Please sign in to comment.