Hello!
I am in the process of adding OTA DFU support to our existing application. However, I'm having problems starting the DFU process.
For security reasons we want to embed the firmware file in the Android application, but when attempting to start the DFU process I get an error about the file not being found:
E/DfuBaseService: An exception occurred while opening file java.io.FileNotFoundException: file:/android_assets/firmware.zip: open failed: ENOENT (No such file or directory) at libcore.io.IoBridge.open(IoBridge.java:575) at java.io.FileInputStream.<init>(FileInputStream.java:160) at java.io.FileInputStream.<init>(FileInputStream.java:115) at no.nordicsemi.android.dfu.DfuBaseService.openInputStream(DfuBaseService.java:1462) at no.nordicsemi.android.dfu.DfuBaseService.onHandleIntent(DfuBaseService.java:1201) at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:78) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.os.HandlerThread.run(HandlerThread.java:67) Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory) at libcore.io.Linux.open(Native Method) at libcore.io.ForwardingOs.open(ForwardingOs.java:567) at libcore.io.BlockGuardOs.open(BlockGuardOs.java:273) at libcore.io.ForwardingOs.open(ForwardingOs.java:567) at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:8611) at libcore.io.IoBridge.open(IoBridge.java:561) at java.io.FileInputStream.<init>(FileInputStream.java:160) at java.io.FileInputStream.<init>(FileInputStream.java:115) at no.nordicsemi.android.dfu.DfuBaseService.openInputStream(DfuBaseService.java:1462) at no.nordicsemi.android.dfu.DfuBaseService.onHandleIntent(DfuBaseService.java:1201) at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:78) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.os.HandlerThread.run(HandlerThread.java:67) E/AndroidRuntime: FATAL EXCEPTION: main Process: no.comq.leveler, PID: 4274 android.app.RemoteServiceException: Bad notification for startForeground at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2155) at android.app.ActivityThread.access$2900(ActivityThread.java:315) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2381) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8751) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135) I/Process: Sending signal. PID: 4274 SIG: 9 Disconnected from the target VM, address: 'localhost:54653', transport: 'socket'
I am using DFU version 2.1.0, "targetSDK 31" and "compileSDK 32".
The test device is a Samsung Galaxy S10+ with Android 12.
Here is the code that starts the DFU process:
fun install(context: Context, advertisement: Advertisement): DfuServiceController { val starter = DfuServiceInitiator(advertisement.address).apply { setDeviceName(advertisement.name) setKeepBond(false) setForceDfu(false) setForceScanningForNewAddressInLegacyDfu(false) setPrepareDataObjectDelay(400L) setRebootTime(0L) setScanTimeout(2_000L) setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true) setPacketsReceiptNotificationsEnabled(false) setPacketsReceiptNotificationsValue(12) } starter.setZip("file:///android_assets/firmware.zip") return starter.start(context, DFUService::class.java) }
I have also added DFUService from the GitHub page:
package ***.data import android.app.Activity import no.nordicsemi.android.dfu.DfuBaseService object DFUServiceRunningObserver { var isRunning: Boolean = false } internal class DFUService : DfuBaseService() { private var runningObserver: DFUServiceRunningObserver = DFUServiceRunningObserver @Deprecated("Deprecated in Java") override fun onCreate() { super.onCreate() runningObserver.isRunning = true } @Deprecated("Deprecated in Java") override fun onDestroy() { super.onDestroy() runningObserver.isRunning = false } override fun getNotificationTarget(): Class<out Activity?> { /* * As a target activity the NotificationActivity is returned, not the MainActivity. This is because the notification must create a new task: * * intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); * * when user press it. Using NotificationActivity we can check whether the new activity is a root activity (that means no other activity was open before) * or that there is other activity already open. In the later case the notificationActivity will just be closed. System will restore the previous activity. * However if the application has been closed during upload and user click the notification a NotificationActivity will be launched as a root activity. * It will create and start the main activity and terminate itself. * * This method may be used to restore the target activity in case the application was closed or is open. It may also be used to recreate an activity * history (see NotificationActivity). */ return Class.forName("***.data.NotificationActivity") as Class<out Activity> } override fun isDebug(): Boolean { // return BuildConfig.DEBUG; return true } }
As well as a NotificationActivity:
package ***.data import android.app.Activity import android.content.Intent import android.os.Bundle import ***.MainActivity class NotificationActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // If this activity is the root activity of the task, the app is not running if (isTaskRoot) { // Start the app before finishing val intent = Intent(this, MainActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.putExtras(getIntent().extras!!) // copy all extras startActivity(intent) } // Now finish, which will drop you to the activity at which you were at the top of the task stack finish() } }
I have also added those to the manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ... /> <application ...> ... <activity android:name=".data.NotificationActivity" android:exported="false"> </activity> <service android:name=".data.DFUService" /> </application> </manifest>
Can anyone tell me what I'm doing wrong?
Best regards
Magnus Johansen