Skip to content

Commit

Permalink
Merge pull request #36 from juliansteenbakker/callback
Browse files Browse the repository at this point in the history
imp: updated event channels and callbacks
  • Loading branch information
juliansteenbakker authored May 5, 2022
2 parents 5649dc0 + c71169b commit f410d66
Show file tree
Hide file tree
Showing 16 changed files with 511 additions and 667 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 5.0.0
BREAKING CHANGES:
Callback is now handled through functions in the StartDfu() method. Please see the example app for an example.

Bugs fixed:
Fixed callback not being called on both Android and iOS.

## 4.0.0
BREAKING CHANGES:
NordiDfu now uses a Singelton! The notation changes from NordicDfu.startDfu() to NordicDfu().startDfu()
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ group 'dev.steenbakker.nordicdfu'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.6.20'
ext.kotlin_version = '1.6.21'
repositories {
google()
mavenCentral()
Expand Down
1 change: 1 addition & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<!-- add android:usesPermissionFlags="neverForLocation" when you can strongly assert that
your app never derives physical location from Bluetooth scan results. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application>
<service android:name=".DfuService" />
Expand Down
225 changes: 112 additions & 113 deletions android/src/main/kotlin/dev/steenbakker/nordicdfu/NordicDfuPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,93 +8,103 @@ import android.os.Looper
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
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.*
import no.nordicsemi.android.dfu.DfuBaseService.NOTIFICATION_ID
import java.util.*

class NordicDfuPlugin : FlutterPlugin, MethodCallHandler {
/**
* hold context
*/
class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {

private var mContext: Context? = null

/**
* hold result
*/
private var pendingResult: MethodChannel.Result? = null

/**
* Method Channel
*/
private var channel: MethodChannel? = null
private var methodChannel: MethodChannel? = null
private var eventChannel: EventChannel? = null
private var sink: EventChannel.EventSink? = null

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

override fun onAttachedToEngine(binding: FlutterPluginBinding) {
mContext = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, "dev.steenbakker.nordic_dfu/method")
channel!!.setMethodCallHandler(this)

methodChannel = MethodChannel(binding.binaryMessenger, "dev.steenbakker.nordic_dfu/method")
methodChannel!!.setMethodCallHandler(this)

eventChannel = EventChannel(binding.binaryMessenger, "dev.steenbakker.nordic_dfu/event")
eventChannel!!.setStreamHandler(this)

DfuServiceListenerHelper.registerProgressListener(binding.applicationContext, mDfuProgressListener)
}

override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
mContext = null
channel = null
methodChannel = null
eventChannel = null
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if (call.method == "startDfu") {
val address = call.argument<String>("address")
val name = call.argument<String>("name")
var filePath = call.argument<String>("filePath")
var fileInAsset = call.argument<Boolean>("fileInAsset")
val forceDfu = call.argument<Boolean>("forceDfu")
val enableUnsafeExperimentalButtonlessServiceInSecureDfu = call.argument<Boolean>("enableUnsafeExperimentalButtonlessServiceInSecureDfu")
val disableNotification = call.argument<Boolean>("disableNotification")
val keepBond = call.argument<Boolean>("keepBond")
val packetReceiptNotificationsEnabled = call.argument<Boolean>("packetReceiptNotificationsEnabled")
val restoreBond = call.argument<Boolean>("restoreBond")
val startAsForegroundService = call.argument<Boolean>("startAsForegroundService")
val numberOfPackets = call.argument<Int>("numberOfPackets")
val enablePRNs = call.argument<Boolean>("enablePRNs")
if (fileInAsset == null) {
fileInAsset = false
}
if (address == null || filePath == null) {
result.error("Abnormal parameter", "address and filePath are required", null)
when (call.method) {
"startDfu" -> initiateDfu(call, result)
"abortDfu" -> abortDfu()
else -> result.notImplemented()
}
}

override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
this.sink = events
}

override fun onCancel(arguments: Any?) {
sink = null
}

private fun initiateDfu(call: MethodCall, result: MethodChannel.Result) {
val address = call.argument<String>("address")
val name = call.argument<String>("name")
var filePath = call.argument<String>("filePath")
var fileInAsset = call.argument<Boolean>("fileInAsset")
val forceDfu = call.argument<Boolean>("forceDfu")
val enableUnsafeExperimentalButtonlessServiceInSecureDfu = call.argument<Boolean>("enableUnsafeExperimentalButtonlessServiceInSecureDfu")
val disableNotification = call.argument<Boolean>("disableNotification")
val keepBond = call.argument<Boolean>("keepBond")
val packetReceiptNotificationsEnabled = call.argument<Boolean>("packetReceiptNotificationsEnabled")
val restoreBond = call.argument<Boolean>("restoreBond")
val startAsForegroundService = call.argument<Boolean>("startAsForegroundService")
val numberOfPackets = call.argument<Int>("numberOfPackets")
val enablePRNs = call.argument<Boolean>("enablePRNs")
if (fileInAsset == null) fileInAsset = false
if (address == null || filePath == null) {
result.error("Abnormal parameter", "address and filePath are required", null)
return
}
if (fileInAsset) {
val loader = FlutterInjector.instance().flutterLoader()
filePath = loader.getLookupKeyForAsset(filePath)
val tempFileName = (PathUtils.getExternalAppCachePath(mContext!!)
+ UUID.randomUUID().toString())
// copy asset file to temp path
if (!ResourceUtils.copyFileFromAssets(filePath, tempFileName, mContext!!)) {
result.error("File Error", "File not found!", "$filePath")
return
}
if (fileInAsset) {
val loader = FlutterInjector.instance().flutterLoader()
filePath = loader.getLookupKeyForAsset(filePath)
val tempFileName = (PathUtils.getExternalAppCachePath(mContext!!)
+ UUID.randomUUID().toString())
// copy asset file to temp path
if (!ResourceUtils.copyFileFromAssets(filePath, tempFileName, mContext!!)) {
result.error("File Error", "File not found!", "$filePath")
return
}

// now, the path is an absolute path, and can pass it to nordic dfu libarary
filePath = tempFileName
}
pendingResult = result
startDfu(address, name, filePath, forceDfu, enableUnsafeExperimentalButtonlessServiceInSecureDfu, disableNotification, keepBond, packetReceiptNotificationsEnabled, restoreBond, startAsForegroundService, result, numberOfPackets, enablePRNs)
} else if (call.method == "abortDfu") {
if (controller != null) {
controller!!.abort()
}
} else {
result.notImplemented()

// now, the path is an absolute path, and can pass it to nordic dfu libarary
filePath = tempFileName
}
pendingResult = result
startDfu(address, name, filePath, forceDfu, enableUnsafeExperimentalButtonlessServiceInSecureDfu, disableNotification, keepBond, packetReceiptNotificationsEnabled, restoreBond, startAsForegroundService, result, numberOfPackets, enablePRNs)
}

private fun abortDfu() {
if (controller != null) {
controller!!.abort()
}
}

/**
* Start Dfu
*/
private fun startDfu(address: String, name: String?, filePath: String?, forceDfu: Boolean?, enableUnsafeExperimentalButtonlessServiceInSecureDfu: Boolean?, disableNotification: Boolean?, keepBond: Boolean?, packetReceiptNotificationsEnabled: Boolean?, restoreBond: Boolean?, startAsForegroundService: Boolean?, result: MethodChannel.Result, numberOfPackets: Int?, enablePRNs: Boolean?) {
val starter = DfuServiceInitiator(address)
.setZip(filePath!!)
Expand All @@ -113,21 +123,11 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler {
if (enableUnsafeExperimentalButtonlessServiceInSecureDfu != null) {
starter.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(enableUnsafeExperimentalButtonlessServiceInSecureDfu)
}
if (forceDfu != null) {
starter.setForceDfu(forceDfu)
}
if (disableNotification != null) {
starter.setDisableNotification(disableNotification)
}
if (startAsForegroundService != null) {
starter.setForeground(startAsForegroundService)
}
if (keepBond != null) {
starter.setKeepBond(keepBond)
}
if (restoreBond != null) {
starter.setRestoreBond(restoreBond)
}
if (forceDfu != null) starter.setForceDfu(forceDfu)
if (disableNotification != null) starter.setDisableNotification(disableNotification)
if (startAsForegroundService != null) starter.setForeground(startAsForegroundService)
if (keepBond != null) starter.setKeepBond(keepBond)
if (restoreBond != null) starter.setRestoreBond(restoreBond)
if (packetReceiptNotificationsEnabled != null) {
starter.setPacketsReceiptNotificationsEnabled(packetReceiptNotificationsEnabled)
}
Expand All @@ -142,99 +142,98 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler {
controller = starter.start(mContext!!, DfuService::class.java)
}

private fun cancelNotification() {
// let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again.
Handler(Looper.getMainLooper()).postDelayed({
val manager = mContext!!.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.cancel(NOTIFICATION_ID)
}, 200)
}

private val mDfuProgressListener: DfuProgressListenerAdapter = object : DfuProgressListenerAdapter() {
override fun onDeviceConnected(deviceAddress: String) {
super.onDeviceConnected(deviceAddress)
channel!!.invokeMethod("onDeviceConnected", deviceAddress)
sink?.success(mapOf("onDeviceConnected" to deviceAddress))
}

override fun onError(deviceAddress: String, error: Int, errorType: Int, message: String) {
super.onError(deviceAddress, error, errorType, message)
cancelNotification()
channel!!.invokeMethod("onError", deviceAddress)
val parameters = mutableMapOf<String, Any>()
parameters["deviceAddress"] = deviceAddress
parameters["error"] = error
parameters["errorType"] = errorType
parameters["message"] = message
sink?.success(mapOf("onError" to parameters))
if (pendingResult != null) {
pendingResult!!.error("2", "DFU FAILED", "device address: $deviceAddress")
pendingResult!!.error("$error", "DFU FAILED: $message", "Address: $deviceAddress, Error Type: $errorType")
pendingResult = null
}
}

override fun onDeviceConnecting(deviceAddress: String) {
super.onDeviceConnecting(deviceAddress)
channel!!.invokeMethod("onDeviceConnecting", deviceAddress)
sink?.success(mapOf("onDeviceConnecting" to deviceAddress))
}

override fun onDeviceDisconnected(deviceAddress: String) {
super.onDeviceDisconnected(deviceAddress)
channel!!.invokeMethod("onDeviceDisconnected", deviceAddress)
sink?.success(mapOf("onDeviceDisconnected" to deviceAddress))
}

override fun onDeviceDisconnecting(deviceAddress: String) {
super.onDeviceDisconnecting(deviceAddress)
channel!!.invokeMethod("onDeviceDisconnecting", deviceAddress)
sink?.success(mapOf("onDeviceDisconnecting" to deviceAddress))
}

override fun onDfuAborted(deviceAddress: String) {
super.onDfuAborted(deviceAddress)
cancelNotification()
if (pendingResult != null) {
pendingResult!!.error("2", "DFU ABORTED", "device address: $deviceAddress")
pendingResult = null
}
channel!!.invokeMethod("onDfuAborted", deviceAddress)
sink?.success(mapOf("onDfuAborted" to deviceAddress))
pendingResult?.error("DFU_ABORTED", "DFU ABORTED by user", "device address: $deviceAddress")
pendingResult = null
}

override fun onDfuCompleted(deviceAddress: String) {
super.onDfuCompleted(deviceAddress)
cancelNotification()
if (pendingResult != null) {
pendingResult!!.success(deviceAddress)
pendingResult = null
}
channel!!.invokeMethod("onDfuCompleted", deviceAddress)
sink?.success(mapOf("onDfuCompleted" to deviceAddress))
pendingResult?.success(deviceAddress)
pendingResult = null
}

override fun onDfuProcessStarted(deviceAddress: String) {
super.onDfuProcessStarted(deviceAddress)
channel!!.invokeMethod("onDfuProcessStarted", deviceAddress)
sink?.success(mapOf("onDfuProcessStarted" to deviceAddress))
}

override fun onDfuProcessStarting(deviceAddress: String) {
super.onDfuProcessStarting(deviceAddress)
channel!!.invokeMethod("onDfuProcessStarting", deviceAddress)
sink?.success(mapOf("onDfuProcessStarting" to deviceAddress))
}

override fun onEnablingDfuMode(deviceAddress: String) {
super.onEnablingDfuMode(deviceAddress)
channel!!.invokeMethod("onEnablingDfuMode", deviceAddress)
sink?.success(mapOf("onEnablingDfuMode" to deviceAddress))
}

override fun onFirmwareValidating(deviceAddress: String) {
super.onFirmwareValidating(deviceAddress)
channel!!.invokeMethod("onFirmwareValidating", deviceAddress)
sink?.success(mapOf("onFirmwareValidating" to deviceAddress))
}

override fun onProgressChanged(deviceAddress: String, percent: Int, speed: Float, avgSpeed: Float, currentPart: Int, partsTotal: Int) {
super.onProgressChanged(deviceAddress, percent, speed, avgSpeed, currentPart, partsTotal)
val paras: HashMap<String?, Any?> = object : HashMap<String?, Any?>() {
init {
put("percent", percent)
put("speed", speed)
put("avgSpeed", avgSpeed)
put("currentPart", currentPart)
put("partsTotal", partsTotal)
put("deviceAddress", deviceAddress)
}
}
channel!!.invokeMethod("onProgressChanged", paras)
}
}
val parameters = mutableMapOf<String, Any>()
parameters["deviceAddress"] = deviceAddress
parameters["percent"] = percent
parameters["speed"] = speed
parameters["avgSpeed"] = avgSpeed
parameters["currentPart"] = currentPart
parameters["partsTotal"] = partsTotal

private fun cancelNotification() {
// let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again.
Handler(Looper.getMainLooper()).postDelayed({
val manager = mContext!!.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.cancel(NOTIFICATION_ID)
}, 200)
sink?.success(mapOf("onProgressChanged" to parameters))
}
}

}
4 changes: 2 additions & 2 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.6.20'
ext.kotlin_version = '1.6.21'
repositories {
google()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath 'com.android.tools.build:gradle:7.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>9.0</string>
</dict>
</plist>
Loading

0 comments on commit f410d66

Please sign in to comment.