21

I successfully have used this code snippet before, but with the file pointing to somewhere on the SD card.

final File temp = new File(getCacheDir(), "temp.jpg");
temp.delete();
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(temp));
startActivityForResult(intent, CONFIG.Intents.Actions.SELECT_CAMERA_PHOTO);

However when I use getCacheDir instead of a loc on the SD card it seems the photo is never saved. Is this a limitation of cache dir and image capture?

hasanghaforian
  • 13,858
  • 11
  • 76
  • 167
sgarman
  • 6,152
  • 5
  • 40
  • 44

2 Answers2

42

Technically, this is because writing to internal storage is not supported when using the Camera application to capture an image. In fact, you may notice an exception printed in logcat stating Writing to internal storage is not supported. However, the real reason this doesn't work is because by default you are creating a file that is private to your application package and another application (i.e. the Camera app) can't access that file location because it doesn't have permission to do so. External storage is the only globally accessibly portion of the filesystem.

The workaround is for you to create the file with global (WORLD_WRITEABLE) permissions. Typically, this allows the Camera app to access the file via the passed Uri. There aren't really methods to do this directly on File, so you have to create the file using the methods available in Context and then grab a handle to it afterward:

//Remove if exists, the file MUST be created using the lines below
File f = new File(getFilesDir(), "Captured.jpg");
f.delete();
//Create new file
FileOutputStream fos = openFileOutput("Captured.jpg", Context.MODE_WORLD_WRITEABLE);
fos.close();
//Get reference to the file
File f = new File(getFilesDir(), "Captured.jpg");

This also sort of limits where you can place the file since the Context methods inherently create files in the root "files" directory, and you can't redirect that to the cache directory.

HTH

devunwired
  • 62,780
  • 12
  • 127
  • 139
  • Great, I had a hunch that was the issue. I think I'm just going to move saving photos temporarily to the SD card again, but I;ll add this to my toolkit :) – sgarman Oct 11 '11 at 01:37
  • 1
    Any ideas about how to solve the Context.MODE_WORLD_WRITEABLE deprecation? – bre_dev Mar 20 '16 at 12:21
  • 6
    From [here](http://developer.android.com/reference/android/content/Context.html): **MODE_WORLD_WRITEABLE This constant was deprecated in API level 17. Creating world-writable files is very dangerous, and likely to cause security holes in applications. It is strongly discouraged;** – Atul May 14 '16 at 08:25
  • 1
    Indeed how things have changed over the past 5 years :) A ContentProvider (such as FileProvider) is the best solution to address this. – devunwired May 27 '16 at 02:35
30

Best solution I found is: FileProvider (needs support-library-v4)
It uses the internal storage! https://developer.android.com/reference/android/support/v4/content/FileProvider.html

  1. Define your FileProvider in Manifest in Application element:

    <provider
          android:name="android.support.v4.content.FileProvider"
          android:authorities="your.package.name.fileprovider"
          android:exported="false"
          android:grantUriPermissions="true" >
          <meta-data
                     android:name="android.support.FILE_PROVIDER_PATHS"
                     android:resource="@xml/image_path" />
    </provider>
    
  2. Add camera feature to AndroidManifest.xml's root element if mandatory:

    <uses-feature android:name="android.hardware.camera"
    android:required="true" />
    
  3. Define your image paths in for example res/xml/image_path.xml:

    <paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="captured_image" path="your/path/"/>
    </paths>
    
  4. Java:

    private static final int IMAGE_REQUEST_CODE = 1;
    // your authority, must be the same as in your manifest file 
    private static final String CAPTURE_IMAGE_FILE_PROVIDER = "your.package.name.fileprovider";
    

4.1 capture intent:

    File path = new File(activity.getFilesDir(), "your/path");
    if (!path.exists()) path.mkdirs();
    File image = new File(path, "image.jpg");
    Uri imageUri = FileProvider.getUriForFile(activity, CAPTURE_IMAGE_FILE_PROVIDER, image);
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    startActivityForResult(intent, IMAGE_REQUEST_CODE);

4.2 onActivityResult():

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == IMAGE_REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                File path = new File(getFilesDir(), "your/path");
                if (!path.exists()) path.mkdirs();
                File imageFile = new File(path, "image.jpg");
                // use imageFile to open your image
            }
        }
        super.onActivityResult(requestCode, resultCode, intent);
    }
Harry
  • 907
  • 10
  • 11
  • Agree FileProvide is the best solution to save an image internally using the Camera Intent. – tagy22 Feb 19 '15 at 12:03
  • Are you sure you need the `READ_EXTERNAL_STORAGE` permission for FileProvide? I don't believe it's correct. – Martin Konecny Oct 06 '15 at 16:21
  • Martin, I think you are right. I will change my answer. – Harry Oct 07 '15 at 20:36
  • @Harry What if I want to capture more than one image ? We have to give hard coded image name in captured_image in xml ? – Smeet May 10 '16 at 16:49
  • I tried above technique on android 4.4 kitkat, but camera app can't able to create file. Did any one got a better luck ? – Gem Dec 27 '16 at 09:01
  • Works, even without this line: `` Tested on Android 5.1 and 7.0 whereby I wrote to the internal cache directory. For that I had to change in xml `files-path` to `cache-path` and instead of `getFilesDir()` I used `getCacheDir()`. – Bevor Apr 08 '18 at 15:17
  • What does the `` in `image_path.xml` do? I don't see where you refer to `captured_image` path in the code. – Chuck Krutsinger May 26 '21 at 23:21
  • It is the file name of the photo, if I remember correct. The XML file needs to be defined in the AndroidManifest.xml. (see step 1). – Harry May 28 '21 at 07:40