2

When trying to save any JPEG picture using saveAttributes() from androidx Exifinterface, my program crashes with the error "write failed: EBADF (Bad file descriptor)"

I can replicate the error starting fresh from a new project. I'm using Android Studio: new project->Empty Activity. I'm using the emulator to test.

Below is the full code with the only changes I made to the fresh Empty Activity template.

Using androidx Exifinterface, this code is correctly able to get Exif attributes. However, saveAttributes() crashes every time:

  • saveAttributes() crashes regardless if I first setAttribute() or not.
  • Which picture used does not matter. It crashes for every picture I've tried.
  • I'm using JPEG pictures. I have not tested with other mime types.
  • saveAttributes() throws: "Failed to save new file. Original file is stored in..."

I want to set Exif attributes of the picture and save into the original image file. What is the correct way?

[This post should be tagged androidx-interface. But that tag does not exist, and I don't have the reputation to add a tag. So I used tag android-interface, which does exist].


build.gradle (:app)

implementation "androidx.exifinterface:exifinterface:1.3.2"

AndroidManifest.xml:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />

MainActivity.kt:

    package com.example.test_exif_save
    
    import android.content.ContentUris
    import android.net.Uri
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.provider.MediaStore
    import android.util.Log
    import androidx.core.app.ActivityCompat
    import androidx.exifinterface.media.ExifInterface
    
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            
            // Simple method for PERMISSIONS just for quick testing
            val permissions = arrayOf(
                    android.Manifest.permission.READ_EXTERNAL_STORAGE,
                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    android.Manifest.permission.ACCESS_MEDIA_LOCATION,
            )
            ActivityCompat.requestPermissions(this, permissions, 0)
    
            val contentUri: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    
            // id of arbitrary picture saved in Pictures/
            // The picture chosen does not matter. The same crash occurs for every picture.
            val picture_id: Long = 32
    
            val uri = ContentUris.withAppendedId(contentUri, picture_id)
            
            contentResolver.openInputStream(uri)?.use { stream ->
                val exifData = ExifInterface(stream)
    
                // Check that ExifInterface getAttribute works correctly
                val attr_model = exifData.getAttribute(ExifInterface.TAG_MODEL)
                val attr_datetime_original = exifData.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL)
                val attr_image_width = exifData.getAttribute(ExifInterface.TAG_IMAGE_WIDTH)
                Log.i("ExifData", "Model: $attr_model")
                Log.i("ExifData", "Datetime original: $attr_datetime_original")
                Log.i("ExifData", "Width: $attr_image_width")
                
                // Try to save
                // Causes fatal exception. Error: ErrnoException: write failed: EBADF (Bad file descriptor)
                exifData.saveAttributes()
            }
        }
    }

The stack trace:

com.example.test_exif_save E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.test_exif_save, PID: 12188
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.test_exif_save/com.example.test_exif_save.MainActivity}: java.io.IOException: Failed to save new file. Original file is stored in /data/user/0/com.example.test_exif_save/cache/temp1652156935871844716tmp
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.io.IOException: Failed to save new file. Original file is stored in /data/user/0/com.example.test_exif_save/cache/temp1652156935871844716tmp
        at androidx.exifinterface.media.ExifInterface.saveAttributes(ExifInterface.java:4783)
        at com.example.test_exif_save.MainActivity.onCreate(MainActivity.kt:44)
        at android.app.Activity.performCreate(Activity.java:8000)
        at android.app.Activity.performCreate(Activity.java:7984)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: java.io.IOException: write failed: EBADF (Bad file descriptor)
        at libcore.io.IoBridge.write(IoBridge.java:540)
        at java.io.FileOutputStream.write(FileOutputStream.java:398)
        at androidx.exifinterface.media.ExifInterface.copy(ExifInterface.java:8087)
        at androidx.exifinterface.media.ExifInterface.saveAttributes(ExifInterface.java:4779)
        at com.example.test_exif_save.MainActivity.onCreate(MainActivity.kt:44) 
        at android.app.Activity.performCreate(Activity.java:8000) 
        at android.app.Activity.performCreate(Activity.java:7984) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: android.system.ErrnoException: write failed: EBADF (Bad file descriptor)
        at libcore.io.Linux.writeBytes(Native Method)
        at libcore.io.Linux.write(Linux.java:293)
        at libcore.io.ForwardingOs.write(ForwardingOs.java:240)
        at libcore.io.BlockGuardOs.write(BlockGuardOs.java:418)
        at libcore.io.ForwardingOs.write(ForwardingOs.java:240)
        at libcore.io.IoBridge.write(IoBridge.java:535)
        at java.io.FileOutputStream.write(FileOutputStream.java:398) 
        at androidx.exifinterface.media.ExifInterface.copy(ExifInterface.java:8087) 
        at androidx.exifinterface.media.ExifInterface.saveAttributes(ExifInterface.java:4779) 
        at com.example.test_exif_save.MainActivity.onCreate(MainActivity.kt:44) 
        at android.app.Activity.performCreate(Activity.java:8000) 
        at android.app.Activity.performCreate(Activity.java:7984) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
Taba Buia
  • 23
  • 3
  • As @theCreator already pointed out you try to write attributes to an input stream. Dont let your app crash all the time by catching the exception thrown. – blackapps Feb 08 '21 at 21:21
  • Thanks, blackapps, you're right. @theCreator's answer is correct. My problem is that in API 30, I'm not sure how to efficiently update Exif tags of several images in shared storage without copying to my app-specific area, update exif info with Exifinterface, then copy back into shared storage. Even with batch update function createWriteRequest(), it seems to cause delay from copying and frustrating consent dialog for every batch operation. Any ideas of a better way? – Taba Buia Feb 10 '21 at 23:19
  • Well if you can open an input stream for an uri you can open an output stream for that uri too. And then write your attibutes. But open the stream in overwtite mode. It should not truncate the file. – blackapps Feb 10 '21 at 23:49
  • 1
    @blackapps this is not how ExifInterface works. InputStream is used to create ExifInterface, then when saveAttributes, it internally creates OutputStream. The problem is scoped storage I presume, which doesn't let to properly write to a new file without some permissions. – Dimezis Mar 18 '21 at 14:00
  • 1
    I have the same problem with the scoped storage hell. `java.io.IOException: Failed to copy original file to temp file Caused by: android.system.ErrnoException: lseek failed: EBADF (Bad file descriptor)`. Crashing. I am using `ExifInterface` with `openInputStream` from `contextResolver`. The answer from @Dimezis is working. – t0m May 12 '21 at 06:29

1 Answers1

7

I had the same problem. Fixed it by creating ExifInterface from a FileDescriptor instead of an InputStream. Replace

val exifData = ExifInterface(stream)

With

val exifData = ExifInterface(context.contentResolver.openFileDescriptor(new, "rw", null)!!.fileDescriptor)

(FileDescriptor nullability handling to your taste).

Dimezis
  • 1,541
  • 11
  • 24