Android Question ExifInterface saveAttributes crashes with "write failed: EBADF (bad file descriptor)"

Alain75

Member
Hi,

I am writing a simple jpg file compressor. Currently, I succeeded in :
  1. getting the file,
  2. getting its metadata (GPS references and DateTimeDigitized) thanks to @Erel post on SimpleMediaManager
  3. writing the new file with parameterized compression level,
  4. setting the metada of the new file with ExifInterface method setAttribute
Unfortunately, my apps crashes on saveAttributes method of ExifInterface :
Extracted code:
Sub Process_Globals
    Dim meta()        As String = Array As String("GPSLatitudeRef","GPSLatitude","GPSLongitudeRef","GPSLongitude","DateTime","DateTimeDigitized")
    ...

Sub Copy
    ...
    'f1 is internal image compressed
    'f2 is name of the new file in the external storage directory where I choose the file
    Dim in As InputStream    = File.OpenInput(File.DirInternal,f1)
    Dim out As OutputStream    = sto.OpenOutputStream(sto.CreateNewFile(sto.Root,f2))
    File.Copy2(in, out)
    out.Close
        
    ' Setting metadata attributes of new file
    Dim in As InputStream    = sto.OpenInputStream(sto.FindFile(sto.Root,f2))
    Dim exif As JavaObject
    exif.InitializeNewInstance("android.media.ExifInterface", Array(in))
    Dim v() As String = Regex.Split(";",metadata), i As Int = 0
    For Each a As String In meta
        exif.RunMethod("setAttribute",Array(a,v(i)))
        i = i + 1
    Next
    ' Save modifications...(supposed to create a new file and then replace the file)
    exif.RunMethod("saveAttributes",Null)

And the log in B4A :
B4X:
copy : content://com.android.externalstorage.documents/document/87C5-17E9%3ADCIM%2FCamera%2F0001.jpg -> temp
copy : new -> 0001-new.jpg
main_copy (java line: 509)
java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:132)
    at b4a.jpgsizer.main._copy(main.java:509)
    at b4a.jpgsizer.main._compressimage(main.java:444)
    at b4a.jpgsizer.main._compressall(main.java:412)
    at b4a.jpgsizer.main._save_click(main.java:800)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:221)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:205)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:201)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
    at android.view.View.performClick(View.java:8160)
    at android.widget.TextView.performClick(TextView.java:16222)
    at android.view.View.performClickInternal(View.java:8137)
    at android.view.View.access$3700(View.java:888)
    at android.view.View$PerformClick.run(View.java:30236)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:246)
    at android.app.ActivityThread.main(ActivityThread.java:8653)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Caused by: java.io.IOException: Failed to save new file
    at android.media.ExifInterface.saveAttributes(ExifInterface.java:2130)
    ... 23 more
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 java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
    at java.io.BufferedOutputStream.write(BufferedOutputStream.java:121)
    at android.media.ExifInterface$ByteOrderedDataOutputStream.write(ExifInterface.java:4787)
    at libcore.io.Streams.copy(Streams.java:203)
    at android.media.ExifInterface.saveJpegAttributes(ExifInterface.java:3530)
    at android.media.ExifInterface.saveAttributes(ExifInterface.java:2118)
    ... 23 more
    Suppressed: 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 java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
        at java.io.FilterOutputStream.close(FilterOutputStream.java:179)
        at android.media.ExifInterface.saveAttributes(ExifInterface.java:2115)
        ... 23 more
    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)
        ... 28 more
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)
    ... 30 more

However the new file before setting metadata is well written :
Image.jpg


Any help ?
Thanks !
 
Solution
Thank's ! Tat's exactly what I did and I needed to open file in read/write mode with ContentResolver :
Open in read/write mode:
Dim cr As JavaObject, fd As JavaObject, uri As Uri
cr = cr.InitializeContext.RunMethod("getContentResolver",Null)
uri.Parse("file://"&File.Combine(File.DirInternal,"new"))
fd = cr.RunMethod("openFileDescriptor",Array(uri,"rw",Null))
Dim exif As JavaObject
exif.InitializeNewInstance("android.media.ExifInterface", Array(fd.RunMethod("getFileDescriptor",Null)))
...
    exif.RunMethod("setAttribute",Array(attr,value(i)))
 ...
exif.RunMethod("saveAttributes",Null)
fd.RunMethod("close",Null)

Alain75

Member
Thank's ! Tat's exactly what I did and I needed to open file in read/write mode with ContentResolver :
Open in read/write mode:
Dim cr As JavaObject, fd As JavaObject, uri As Uri
cr = cr.InitializeContext.RunMethod("getContentResolver",Null)
uri.Parse("file://"&File.Combine(File.DirInternal,"new"))
fd = cr.RunMethod("openFileDescriptor",Array(uri,"rw",Null))
Dim exif As JavaObject
exif.InitializeNewInstance("android.media.ExifInterface", Array(fd.RunMethod("getFileDescriptor",Null)))
...
    exif.RunMethod("setAttribute",Array(attr,value(i)))
 ...
exif.RunMethod("saveAttributes",Null)
fd.RunMethod("close",Null)
 
Upvote 0
Solution

Alain75

Member
The last thing I tried for the moment with no success (using java.io.File or ExternalStorage) is to change datetime of the compressed file to set original one but I think it is a very tricky problem. And Google is unfortunately more and more hardening file access, even in the external storage ! I know it's for security reasons but it becomes complicated for developers now.
 
Upvote 0

Alain75

Member
I tried to change date with ContentResolver... Like previous methods, it crashes but with a different log indicating something different:
B4X:
** Activity (main) Resume **
copy : content://com.android.externalstorage.documents/document/87C5-17E9%3ADCIM%2FCamera%2F0001.jpg -> temp
copy : new -> content://com.android.externalstorage.documents/document/87C5-17E9%3ADCIM%2FCamera%2F0001-new.jpg
main_compressimage (java line: 533)
java.lang.SecurityException: Permission Denial: writing com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/document/87C5-17E9%3ADCIM%2FCamera%2F0001-new.jpg from pid=22870, uid=10245 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()
    at android.os.Parcel.createExceptionOrNull(Parcel.java:2386)
    at android.os.Parcel.createException(Parcel.java:2370)
    at android.os.Parcel.readException(Parcel.java:2353)
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:190)
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
    at android.content.ContentProviderProxy.update(ContentProviderNative.java:649)
    at android.content.ContentResolver.update(ContentResolver.java:2366)
    at android.content.ContentResolver.update(ContentResolver.java:2328)
    at anywheresoftware.b4a.objects.ContentResolverWrapper.Update(ContentResolverWrapper.java:110)
    at b4a.jpgsizer.main._compressimage(main.java:533)
    at b4a.jpgsizer.main._compressall(main.java:428)
    at b4a.jpgsizer.main._save_click(main.java:927)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:221)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:205)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:201)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
    at android.view.View.performClick(View.java:8160)
    at android.widget.TextView.performClick(TextView.java:16222)
    at android.view.View.performClickInternal(View.java:8137)
    at android.view.View.access$3700(View.java:888)
    at android.view.View$PerformClick.run(View.java:30236)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:246)
    at android.app.ActivityThread.main(ActivityThread.java:8653)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
--------- beginning of crash

I looked at grantUriPermission but I am a bit lost... Any help on this topic ?
 
Upvote 0

Alain75

Member
Oh sorry ?. Yes I use ExternalStorage to give access with read/write to folder "DCIM/Camera" on the internal SDCARD where photos are stored and I use an intent to choose one or more files. The algorithm for each file:
  1. I copy the selected picture in DirInternal (name = temp) : Ok,
  2. I get and store the exif informations I want to keep : Ok,
  3. I write a new compressed file (name = new) in DirInternal : Ok
  4. I set the exif informations on the new file : Ok (I can also set "setLastModified" but it's useless)
  5. If name of the selected picture is "xxx.jpg" then the new file in ExternalStorage folder will be named "xxx-new.jpg"
  6. I delete the new file in the ExternalStorage folder if it exists (in case I rerun the app with the same file) : Ok
  7. I copy the new file to the ExternalStorage folder : Ok
  8. Finally, I try to set "setLastModified" on the new file in ExternalStorage folder with ContentResolver : I get the message in the log with MANAGE_DOCUMENTS permission (not allowed to third party app) or grantUriPermission
Hoping it is clearer. I can upload the code if necessary
 
Upvote 0
Top