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

Related