4

In Fragment, I try to capture an image from the camera and use it. I want to do it with ActivityResultContracts.TakePicture() but when I try to use this image after capturing I get:

W/ImageView: Unable to open content: content://com.myniprojects.pixagram/my_images/default_image.jpg
    java.io.FileNotFoundException: open failed: ENOENT (No such file or directory)

I have seen this question ActivityResultContracts.TakePicture() but I don't know what I am doing wrong. Here is everything that I have done:

Following this docks to manifest I added:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.myniprojects.pixagram"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

and in res/xml I created file file_paths:

<paths>
    <files-path
        name="my_images"
        path="images/" />
</paths>

And in Fragment:

class AddFragment : Fragment(R.layout.fragment_add)
{
    private lateinit var imagePath: File
    private lateinit var newFile: File
    private lateinit var uri: Uri

    override fun onViewCreated(view: View, savedInstanceState: Bundle?)
    {
        super.onViewCreated(view, savedInstanceState)

        //...

        imagePath = File(requireContext().filesDir, "images")
        newFile = File(imagePath, "default_image.jpg")
        uri = getUriForFile(
            requireContext(),
            requireContext().applicationContext.packageName,
            newFile
        )

        binding.butMakeNewImage.setOnClickListener {
            takePicture.launch(uri)
        }
    }

    private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { isSaved ->
        if (isSaved)
        {
            binding.imgSelected.setImageURI(uri)
        }
    }
}

If it helps this is the full error logact

2021-01-16 16:15:38.871 9433-9433/com.myniprojects.pixagram W/ImageView: Unable to open content: content://com.myniprojects.pixagram/my_images/default_image.jpg
    java.io.FileNotFoundException: open failed: ENOENT (No such file or directory)
        at android.os.ParcelFileDescriptor.openInternal(ParcelFileDescriptor.java:315)
        at android.os.ParcelFileDescriptor.open(ParcelFileDescriptor.java:220)
        at androidx.core.content.FileProvider.openFile(FileProvider.java:566)
        at android.content.ContentProvider.openAssetFile(ContentProvider.java:1740)
        at android.content.ContentProvider.openTypedAssetFile(ContentProvider.java:1922)
        at android.content.ContentProvider.openTypedAssetFile(ContentProvider.java:1989)
        at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:539)
        at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1698)
        at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1514)
        at android.content.ContentResolver.openInputStream(ContentResolver.java:1198)
        at android.graphics.ImageDecoder$ContentResolverSource.createImageDecoder(ImageDecoder.java:282)
        at android.graphics.ImageDecoder.decodeDrawableImpl(ImageDecoder.java:1743)
        at android.graphics.ImageDecoder.decodeDrawable(ImageDecoder.java:1736)
        at android.widget.ImageView.getDrawableFromUri(ImageView.java:1023)
        at android.widget.ImageView.resolveUri(ImageView.java:992)
        at android.widget.ImageView.setImageURI(ImageView.java:561)
        at androidx.appcompat.widget.AppCompatImageView.setImageURI(AppCompatImageView.java:120)
        at com.myniprojects.pixagram.ui.fragments.AddFragment$takePicture$1.onActivityResult(AddFragment.kt:105)
        at com.myniprojects.pixagram.ui.fragments.AddFragment$takePicture$1.onActivityResult(AddFragment.kt:30)
        at androidx.activity.result.ActivityResultRegistry.doDispatch(ActivityResultRegistry.java:361)
        at androidx.activity.result.ActivityResultRegistry.dispatchResult(ActivityResultRegistry.java:321)
        at androidx.activity.ComponentActivity.onActivityResult(ComponentActivity.java:631)
        at androidx.fragment.app.FragmentActivity.onActivityResult(FragmentActivity.java:164)
        at android.app.Activity.dispatchActivityResult(Activity.java:8412)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:5580)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:5628)
        at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:149)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:103)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2473)
        at android.os.Handler.dispatchMessage(Handler.java:110)
        at android.os.Looper.loop(Looper.java:219)
        at android.app.ActivityThread.main(ActivityThread.java:8347)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
2021-01-16 16:15:38.871 9433-9433/com.myniprojects.pixagram W/ImageView: resolveUri failed on bad bitmap uri: content://com.myniprojects.pixagram/my_images/default_image.jpg

In docs we can read

An ActivityResultContract to take a picture saving it into the provided content-Uri. Returns true if the image was saved into the given Uri.

So why I get true when after trying to use this image from Uri I get an error?

iknow
  • 8,358
  • 12
  • 41
  • 68
  • 1
    From a quick scan of the code, my guess is that `true` really means "we got `RESULT_OK` from the camera app". Camera apps are buggy, particularly for `ACTION_IMAGE_CAPTURE`. So, despite the docs, I would not assume that the image wound up being written where you expect. Since it's your own file, you can check to see if the file exists before using it. – CommonsWare Jan 16 '21 at 16:03
  • I check if the file exists and log it like this: `"Exists: ${newFile.exists()}"`. It prints `Exists: false`. So it means that I made something wrong? I was testing this on my phone but now on the emulator the same code returns `false` as `isSaved` – iknow Jan 16 '21 at 21:30
  • 1
    "So it means that I made something wrong?" -- probably not, though I haven't played with `ActivityResults` much yet, as it is still pre-release AFAIK. My guess is that you have a buggy camera app, one that can't cope with `ACTION_IMAGE_CAPTURE` well, or at least can't cope with `content://` `Uri` values well. If your test device runs Android 10 or older, you might consider installing another camera app (e.g., Open Camera) and see how your app behaves with it. – CommonsWare Jan 16 '21 at 21:34
  • I do it like You said and in `Open Camera` I got `Failed to save image`. So I started to change all this stuff with `FileProvider` and it seems to work now. Thanks – iknow Jan 17 '21 at 16:31
  • I may be a bit late to the party, but does your imagePath `imagePath = File(requireContext().filesDir, "images")` exist? I don't think android creates the parent directory of a file automatically if it doesn't exist. – derpirscher Jun 17 '21 at 11:43

1 Answers1

18

I finally figured out how to do it. I am not sure but I think that in the question was a problem with File/Uri. Below is a step-by-step solution how to capture an image.

  1. Dependencies:
implementation "androidx.activity:activity-ktx:1.2.0-rc01"
implementation "androidx.fragment:fragment-ktx:1.3.0-rc01"
implementation "com.github.bumptech.glide:glide:4.11.0"
  1. Manifest:
<uses-permission android:name="android.permission.CAMERA" />

<application>
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>
</application>
  1. res/xml/file_paths.xml (com.example.name should be changed to proper package name)
<paths>
    <external-path name="my_images"
        path="Android/data/com.example.name/files/Pictures" />
</paths
  1. Check if Manifest.permission.CAMERA is granted. If not request permission.

  2. In Fragment create ActivityResultLauncher like this:

private lateinit var uri: Uri

private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { isSaved ->
    if (isSaved)
    {
        glide
            .load(uri)
            .into(binding.imgSelected)
    }
}
  1. To launch takePicture (e.g. on button click)
binding.butMakeNewImage.setOnClickListener {

    val photoFile = File.createTempFile(
        "IMG_",
        ".jpg",
        requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    )

    uri = FileProvider.getUriForFile(
        requireContext(),
        "${requireContext().packageName}.provider",
        photoFile
    )

    takePicture.launch(uri)
}

Tip. If you got the message permission denial on API level below 21, try to do this:

val resultLauncher = registerForActivityResult(
    object : ActivityResultContracts.TakePicture()
    {
        override fun createIntent(
            context: Context,
            input: Uri
        ): Intent
        {
            val intent = super.createIntent(context, input)
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP)
            {
                intent.clipData = ClipData.newRawUri("", input)
                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
            }
            return intent
        }
    }) { success ->
    if (success)
    {
        // TODO
    }
}

resultLauncher.launch(photoUri)
iknow
  • 8,358
  • 12
  • 41
  • 68
  • 1
    Everything OK, but one more thing: You don't even need to declare the CAMERA permission, its NOT needed, if the image is only needed for own application. But if you declare Camera permission in manifest, then you need to handle permission to get it. – Truly Feb 10 '22 at 11:03
  • When I add the CAMERA permission to my manifest, I get the error: SecurityException: Permission Denial: starting Intent { act=android.media.action.IMAGE_CAPTURE ... – Paul Smith Mar 25 '22 at 14:52