BitmapFactory can't decode a Bitmap from Uri after photos taken on Android Nougat
Asked Answered
C

6

15

I tried to take a photo and then to use the photo. Here is what I did.

My device was Nexus 6P (Android 7.1.1).

First, I created a Uri:

Uri mPicPath = UriUtil.fromFile(this, UriUtil.createTmpFileForPic());
//Uri mPicPath = UriUtil.fromFile(this, UriUtil.createFileForPic());

And then, I started Intent:

Intent intent = ActivityUtils.getTakePicIntent(mPicPath);
if (intent.resolveActivity(getPackageManager()) != null) {
    startActivityForResult(intent, RequestCode.TAKE_PIC);
}

At last, I handled this Uri on onActivityResult:

if (requestCode == RequestCode.TAKE_PIC) {
    if (resultCode == RESULT_OK && mPicPath != null) {
        Bitmap requireBitmap = BitmapFactory.decodeFile(mPicPath.getPath());
        //path is like this: /Download/Android/data/{@applicationId}/files/Pictures/JPEG_20170216_173121268719051242.jpg
        requireBitmap.recycle();//Here NPE was thrown.
    }
}

At the meantime, Here are UriUtil:

public class UriUtil {

    public static File createFileForPic() throws IOException {
        String fileName = "JPEG_" + new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault()).format(new Date()) + ".jpg";
        File storageDic = SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        return new File(storageDic, fileName);
    }

    public static File createTmpFileForPic() throws IOException {
        String fileName = "JPEG_" + new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault()).format(new Date());
        File storageDic = SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        return File.createTempFile(fileName, ".jpg", storageDic);
    }

    public static Uri fromFile(@NonNull Context context, @NonNull File file) {
        if (context == null || file == null) {
            throw new RuntimeException("context or file can't be null");
        }
        if (ActivityUtils.requireSDKInt(Build.VERSION_CODES.N)) {
            return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".file_provider", file);
        } else {
            return Uri.fromFile(file);
        }
    }
}

and getTakePicIntent(Uri):

public static Intent getTakePicIntent(Uri mPicPath) {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, mPicPath);
    if (!ActivityUtils.requireSDKInt(Build.VERSION_CODES.KITKAT_WATCH)) {//in pre-KitKat devices, manually grant uri permission.
        List<ResolveInfo> resInfoList = SPApplication.getInstance().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            SPApplication.getInstance().grantUriPermission(packageName, mPicPath, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    } else {
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }
    return intent;
}

and requireSDKInt:

public static boolean requireSDKInt(int sdkInt) {
    return Build.VERSION.SDK_INT >= sdkInt;
}

Everything worked on different Android API except on Android Nougat(7.x.x). Even 'FileProvider' was provided, 'requireBitmap' always be returned as 'null'.

After logs read, FileNotFoundException was thrown from BitmapFactory. It was like:

BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /Download/Android/data/{@applicationId}/files/Pictures/JPEG_20170216_1744551601425984925.jpg (No such file or directory)

It seems all is clear, but I still can't understand that.

How could it be? Clearly I created a File! How could I solve that? Any ideas?

Conceivable answered 16/2, 2017 at 3:19 Comment(8)
After logs read, FileNotFoundException was thrown from BitmapFactory.. Yes but it will mention the used path too! Why did you omit it? decodeFile(mUri.getPath()). Please tell the value of mUri.getPath(). All works on 6.0 ?Bor
File.createTempFile. Do NOT create a temporary file. Only create a file name. The file will be created by the camera app.Bor
Uri mPicPath AND mUri.getPath()) ?????? Where does mUri come from?Bor
intent.addFlags(). Are not needed. Not for any Android version. They do always nothing. Confusing code.Bor
@Bor I have updated my post. At the meantime, value of mPicUri.getPath() is like: "/Download/Android/data/{@applicationId}/files/Pictures/JPEG_20170216_173121268719051242.jpg". Even UriUtil.createFileForPic() doesn't work. Intent.FLAG_GRANT_READ_URI_PERMISSION don't work indeed. And all works on pre-7.0.Conceivable
"/Download/Android/data/{@applicationId}/files/Pictures/........" That would be an non existend nonsense path. Check if the directory storageDic exists before use. A full path can never start with /Download/.... SPApplication.getInstance().getExternalFilesDir() is implemented wrong.Bor
@Conceivable try this github.com/raghunandankavi2010/SamplesAndroid/tree/master/….Coldblooded
Maybe this can help. It was my solution. es.#207132Kinematograph
C
16

I tried your code. Here's the sample of my try.https://github.com/raghunandankavi2010/SamplesAndroid/tree/master/StackOverFlowTest.

Have a look at this blog https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html

In the blog commonsware mentions that you should not do new File (mPicPath.getPath()).

Instead you should use the below in onActivityResult

try {
       InputStream ims = getContentResolver().openInputStream(mPicPath);
       // just display image in imageview
       imageView.setImageBitmap(BitmapFactory.decodeStream(ims));
    } catch (FileNotFoundException e) {
            e.printStackTrace();
    }

And the xml

 <external-files-path name="external_files" path="path" />

Note: its a content uri that you have. On my phone i get the uri as below. Tested only on Nexus6p.

content://com.example.raghu.stackoverflowtest.fileProvider/external_files/Pictures/JPEG_20170424_161429691143693160.jpg

More on file provider https://developer.android.com/reference/android/support/v4/content/FileProvider.html

Coldblooded answered 24/4, 2017 at 10:45 Comment(2)
Yeah, finally, only BitmapFactory.decodeStream(inputStream) worked.Conceivable
When using a "content://..." path like that, I can load a chosen png file using Glide, whereas, using BitmapFactory.loadFile(uri), this class returns with a file not found expection, stating, ODDLY ENOUGH, that the path not found is "content:/…" with ONE forward slash only. Why is BitmapFactory removing one of the slashes, only to fail? Mystic mysterium!Underbred
M
3

Try this function may be you get path from it. This is works for me

@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getRealPathFromURI_API19(Context context, Uri uri) {

    if (HelperFunctions.isExternalStorageDocument(uri)) {

        // ExternalStorageProvider
        final String docId = DocumentsContract.getDocumentId(uri);
        final String[] split = docId.split(":");
        final String type = split[0];

        if ("primary".equalsIgnoreCase(type)) {
            return Environment.getExternalStorageDirectory() + "/"
                    + split[1];
        }
    } else if (HelperFunctions.isDownloadsDocument(uri)) {

        // DownloadsProvider

        final String id = DocumentsContract.getDocumentId(uri);
        final Uri contentUri = ContentUris.withAppendedId(
                Uri.parse("content://downloads/public_downloads"),
                Long.valueOf(id));

        return HelperFunctions.getDataColumn(context, contentUri, null, null);

    } else if (HelperFunctions.isMediaDocument(uri)) {


        final String docId = DocumentsContract.getDocumentId(uri);
        final String[] split = docId.split(":");
        final String type = split[0];

        Uri contentUri = null;
        if ("image".equals(type)) {
            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        } else if ("video".equals(type)) {
            contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        } else if ("audio".equals(type)) {
            contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
        }

        final String selection = "_id=?";
        final String[] selectionArgs = new String[]{split[1]};

        return HelperFunctions.getDataColumn(context, contentUri, selection,
                selectionArgs);


    } else if ("content".equalsIgnoreCase(uri.getScheme())) {

        // Return the remote address
        if (HelperFunctions.isGooglePhotosUri(uri))
            return uri.getLastPathSegment();

        return HelperFunctions.getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;

}

here is my static function HelperFunction class which use above.

 public class HelperFunction{
       /**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}



/**
 * @param uri
 *            The Uri to check.
 * @return Whether the Uri authority is Google Photos.
 */
public static boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri
            .getAuthority());
}

}

Mezuzah answered 24/4, 2017 at 4:37 Comment(1)
Just a heads up, you left out the code for the HelperFunctions.getDataColumn() function. This would not work without it.Battleship
S
2

It looks very much like {@applicationID} is actually supposed to contain the package id of your application. As it is, the folder doesn't exist so the file can't be written or read. it looks like SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES); is not returning a valid path.

Shatzer answered 18/4, 2017 at 12:18 Comment(0)
L
2

Nougat and Previous Update Is Working With This Code

To get the real path from database in nougat use this function and pass the uri which you are getting in data field as in onActivityResult and then get the file from the path.

    public String getPath(Uri uri) {
    Cursor cursor = getContentResolver().query(uri, null, null, null, null);
    cursor.moveToFirst();
    String document_id = cursor.getString(0);
    document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
    cursor.close();

    cursor = getContentResolver().query(
            android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
    cursor.moveToFirst();
    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
    cursor.close();

    return path;
}
Letitialetizia answered 22/4, 2017 at 5:50 Comment(0)
L
2

You should use FileProvider. You may refer to this commit for required change.

Larva answered 24/4, 2017 at 9:37 Comment(0)
S
2

Try Glide.

1. Add Glide dependency to app/build.gradle

repositories {
   mavenCentral() // jcenter() works as well because it pulls from Maven Central
}

dependencies {
   compile 'com.github.bumptech.glide:glide:3.7.0'
   compile 'com.android.support:support-v4:19.1.0'
 }

2. Load image using Glide

Glide.with(context).load(new File(uri.getPath())).placeholder(R.drawable.placeholder).into(imageView);

OR

Glide.load(new File(uri.getPath())) // Uri of the picture
.transform(new CircleTransform(..))
.into(imageView);
Simp answered 24/4, 2017 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.