lamelemon lamelemon - 1 month ago 17
Android Question

Image capture always returns result code of 0 and makes the Gallery stop

I am following this tutorial line for line on the android site. I am attempting to take a picture, save it, and then retrieve it later. But every time I take the picture and hit okay I get the message "Unfortunately, Gallery has stopped." and the result code is returning as 0, unsurprisingly. I test to make sure the storage is read and writable so I'm not sure where the issue is.

For reference I am testing on Android version: 4.4.2, minSdkVersion: 15, targetSdkVersion: 24.

file_paths.xml

<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/com.lamelemon.arbitrary/files/Pictures" />
</paths>


AndroidManifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera2" android:required="true"/>


Acitivity

private void dispatchCapturePictureIntent() {
Intent capturePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (capturePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File

}
// Continue only if the File was successfully created
if (photoFile != null) {

Uri photoURI = FileProvider.getUriForFile(this,
"com.lamelemon.arbitrary.fileprovider",
photoFile);

capturePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(capturePictureIntent,REQUEST_IMAGE_CAPTURE);
}
}
}

private File createImageFile() throws IOException
{
String test;
String test2;

if(isExternalStorageReadable()){test = "Read true";}
else{test = "Read false";}

if(isExternalStorageWritable()){test2 = "Write true";}
else{test2 = "Write false";}

Log.e("Readable", test);
Log.e("Writable", test2);
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);

// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = "file:" + image.getAbsolutePath();
return image;
}


private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}


/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}

Answer

Android 4.4 does not automatically grant the other app any access to your ContentProvider. Their sample only works on Android 5.0+, and even that relies on undocumented behavior on the part of the Intent class.

You need to call addFlags() on the Intent to grant write access, as is demonstrated in this sample app. The core chunk of code is explained in greater detail in this blog post, as granting permissions for a Uri in an Intent extra is not that easy.

So, given an Intent named i and a Uri named outputUri, we have:

  if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP) {
    i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  }
  else if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN) {
    ClipData clip=
      ClipData.newUri(getContentResolver(), "A photo", outputUri);

    i.setClipData(clip);
    i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  }
  else {
    List<ResolveInfo> resInfoList=
      getPackageManager()
        .queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);

    for (ResolveInfo resolveInfo : resInfoList) {
      String packageName = resolveInfo.activityInfo.packageName;
      grantUriPermission(packageName, outputUri,
        Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }
  }

Technically, the first case (the simple call to addFlags()) is superfluous, but since this behavior is undocumented, I prefer to manually add the flag to be certain.