Android DFU doesn't find firmware in assets folder

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

Parents Reply Children
  • Hello Hung Bui!

    Thank you for a great response, however, it did not solve our problem.

    I changed the code in the DfuServiceInitiator:

    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(Uri.parse("file:///android_assets/firmware.zip"))
        return starter.start(context, DFUService::class.java)
    }

    However, I still got (almost) the same error:

    E/AndroidRuntime: FATAL EXCEPTION: main
        Process: ***, PID: 12973
        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)
    E/DfuBaseService: An exception occurred while opening file
        java.io.FileNotFoundException: /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 android.content.ContentResolver.openInputStream(ContentResolver.java:1523)
            at no.nordicsemi.android.dfu.DfuBaseService.openInputStream(DfuBaseService.java:1483)
            at no.nordicsemi.android.dfu.DfuBaseService.onHandleIntent(DfuBaseService.java:1199)
            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 android.content.ContentResolver.openInputStream(ContentResolver.java:1523) 
            at no.nordicsemi.android.dfu.DfuBaseService.openInputStream(DfuBaseService.java:1483) 
            at no.nordicsemi.android.dfu.DfuBaseService.onHandleIntent(DfuBaseService.java:1199) 
            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) 
    I/Process: Sending signal. PID: 12973 SIG: 9
    Disconnected from the target VM, address: 'localhost:50997', transport: 'socket'

    Here you can see that the file is in the assets folder in our project:

    Do you, or the developer, have any other suggestions?

    - Magnus

  • Update on the current situation:

    I have attempted to do the update by copying the zip file to my device and then opening the file in the application.

    That resulted in the following error:

    E/AndroidRuntime: FATAL EXCEPTION: main
        Process: ***, PID: 22018
        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: 22018 SIG: 9
    Disconnected from the target VM, address: 'localhost:55854', transport: 'socket'
    

    I found this issue on the GitHub page for the DFU library, and added the solution proposed by the user "nijat2018":

    fun install(context: Context, advertisement: Advertisement, uri: Uri): 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(uri)
        starter.setForeground(false)
        starter.setDisableNotification(true)
        return starter.start(context, DFUService::class.java)
    }

    Then the application successfully updated the device over DFU.

    Now I have verified that the DFU functionality works, so all that remains is to be able to update from the zip file contained in the assets folder in the project.

    - Magnus

  • Hi, did you try res/raw folder instead? You may then pass the reference to zip file as R.raw.firmware.

  • You may also set the notification channel by calling DfuServiceInitiator.createDfuNotificationChannel(context), instead of disabling them if you want.

  • Thank you Aleksander!

    That worked!

    I also enabled the notification channel as you suggested in your other answer, which also worked.

Related