Writing EXIF data to image saved with DocumentFile class - android

I need to get a File from DocumentFile or Uri with correct scheme not the one with content://com.android.externalstorage.documents/tree/primary: if the device's main memory is selected.
To get File or absolute path of the image i need the one with file:///storage/emulated/0 or storage/emulated/0 but i could not find a way to get correct Uri for building a File to write EXIF data to image.
My scenario is:
User chooses a path to save images which returns Uri with content://com.android.externalstorage.documents onActivityResult(). I save this path with treeUri.toString() to SharedPreferences for using later.
User takes a picture and image is saved with DocumentFile.fromTreeUri(MainActivity.this, Uri.parse(uriString));
This where i fail, getting a File that correctly points to image, Uri with content:// does not return the existing image.Correct Uri should file:///storage/emulated/ and i can convert this Uri to file using File filePath = new File(URI.create(saveDir.getUri().toString()));
How can i get the Uri needed for consturcting File or File using Uri i got from SAF UI?
EDIT: ExifInterface Support Library is introduced for Android 7.1+ that can use InputStream or FileDescriptor.
Uri uri; // the URI you've received from the other app
InputStream in;
try {
in = getContentResolver().openInputStream(uri);
ExifInterface exifInterface = new ExifInterface(in);
// Now you can extract any Exif tag you want
// Assuming the image is a JPEG or supported raw format
} catch (IOException e) {
// Handle any errors
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignored) {}
}
}
Note: ExifInterface will not work with remote InputStreams, such as those returned from a HttpURLConnection. It is strongly recommended to only use them with content:// or file:// URIs.
Snippet above is reading data obviously since it opens an InputStream to read data. I need to be able to write EXIF data to JPEG files.

Answer for writing Exif data to an image previously saved and with known content Uri using FileDescriptor if Api is 24 or above
private void writeEXIFWithFileDescriptor(Uri uri) {
if (Build.VERSION.SDK_INT < 24) {
showToast("writeEXIFWithInputStream() API LOWER 24", Toast.LENGTH_SHORT);
return;
}
ParcelFileDescriptor parcelFileDescriptor = null;
try {
parcelFileDescriptor = mContext.getContentResolver().openFileDescriptor(uri, "rw");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
showToast("writeEXIFWithFileDescriptor(): " + fileDescriptor.toString(), Toast.LENGTH_LONG);
ExifInterface exifInterface = new ExifInterface(fileDescriptor);
// TODO Create Exif Tags class to save Exif data
exifInterface.saveAttributes();
} catch (FileNotFoundException e) {
showToast("File Not Found " + e.getMessage(), Toast.LENGTH_LONG);
} catch (IOException e) {
// Handle any errors
e.printStackTrace();
showToast("IOEXception " + e.getMessage(), Toast.LENGTH_LONG);
} finally {
if (parcelFileDescriptor != null) {
try {
parcelFileDescriptor.close();
} catch (IOException ignored) {
ignored.printStackTrace();
}
}
}
}
If Api is lower than 24 it's necessary to use a buffer file and save that buffer file to actual location with DocumentFile after writing Exif data is finished.
private boolean exportImageWithEXIF(Bitmap bitmap, DocumentFile documentFile) {
OutputStream outputStream = null;
File bufFile = new File(Environment.getExternalStorageDirectory(), "buffer.jpg");
long freeSpace = Environment.getExternalStorageDirectory().getFreeSpace() / 1048576;
double bitmapSize = bitmap.getAllocationByteCount() / 1048576d;
showToast("exportImageWithEXIF() freeSpace " + freeSpace, Toast.LENGTH_LONG);
showToast("exportImageWithEXIF() bitmap size " + bitmapSize, Toast.LENGTH_LONG);
try {
outputStream = new FileOutputStream(bufFile);
// Compress image from bitmap with JPEG extension
if (mCameraSettings.getImageFormat().equals(Constants.IMAGE_FORMAT_JPEG)) {
isImageSaved = bitmap.compress(CompressFormat.JPEG, mCameraSettings.getImageQuality(), outputStream);
showToast("isImageSaved: " + isImageSaved, Toast.LENGTH_SHORT);
}
if (isImageSaved) {
writeEXIFWithFile(bufFile);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStream os = null;
InputStream is = null;
try {
int len;
byte[] buf = new byte[4096];
os = mContext.getContentResolver().openOutputStream(documentFile.getUri());
is = new FileInputStream(bufFile);
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
os.close();
is.close();
if (bufFile != null) {
bufFile.delete();
bufFile = null;
}
} catch (IOException e) {
e.printStackTrace();
}
return isImageSaved;
}
Both methods can be used to write Exif data to image saved to device's memory or SD card. You can also save image to SD card using valid Uri from Storage Access Framework.
I also found a way to get absolute path for memory and SD card from content Uri but it's irrelevant for this question and using Uri instead of absolute path is encouraged and does not lead to unnoticed errors, i also wasn't able to save image to SD card with absolute path, only able to read from it.

Related

How to copy a JPG file via Uri

Why is it not working?
Edit: added new versions of code & logs:
private void savePhotoFromCacheToFolder(Uri uri) {
File goodPhoto = album.setUpPhotoFile(); //new empty JPG
File currentPhoto = new File(uri.getPath()); //JPG from camera in cache
Log.v(TAG, "\ngoodPhoto Path " + goodPhoto);
Log.v(TAG, "\ncurrentPhoto Path " + currentPhoto);
FileInputStream source = null;
FileOutputStream destination = null;
try {
source = new FileInputStream(currentPhoto);
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.v(TAG, "\ncurrentPhoto not found ");
}
try {
destination = new FileOutputStream(goodPhoto);
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.v(TAG, "\ngoodPhoto not found ");
}
FileChannel sourceFileChannel = source.getChannel();
FileChannel destinationFileChannel = destination.getChannel();
long size = 0;
try {
size = sourceFileChannel.size();
sourceFileChannel.transferTo(0, size, destinationFileChannel);
} catch (IOException e) {
e.printStackTrace();
Log.v(TAG, "\nshit happens ");
}
}
Logs:
V/MainActivity: goodPhoto Path /storage/emulated/0/Pictures/Good photos/IMG_20170222_113700_-913025224.jpg
V/MainActivity: currentPhoto Path /cache/photo.jpg
V/MainActivity: currentPhoto not found
java.lang.NullPointerException: Attempt to invoke virtual method 'java.nio.channels.FileChannel java.io.FileInputStream.getChannel()' on a null object reference
It looks like Uri is not correct, but this Uri was returned by Camera app. Or maybe I have no access to cache folder, but earlier I did preview photo using this Uri.
we have created an input stream and an output stream object. The input stream points to the current java file and the output stream is pointing to Output.java. It is to this Output.java we want the contents of the file to be transferred. As mentioned earlier, a file object is associated with a File Channel object. So, we obtain the File Channel object for both the input and the output stream using the following code,
public copyFile( String filePath ){
FileInputStream source = new FileInputStream(filePath );
FileOutputStream destination = new FileOutputStream("Output.java");
FileChannel sourceFileChannel = source.getChannel();
FileChannel destinationFileChannel = destination.getChannel();
long size = sourceFileChannel.size();
sourceFileChannel.transferTo(0, size, destinationFileChannel);
}
Compare your code with above code , there is difference of method for transferring data and while is not used here.

How can I get a Picasa image folder on my Marshmallow device?

My application allows users to select an image to upload. When users select an image from a picasa album my data intent comes back with dat=content://com.sec.android.gallery3d.provider/picasa/item/....
Apparently when selecting an image from a picasa folder, I must handle getting the image differently as noted in this answer.
But before I implement a fix, I want to be able to reproduce the crash so I can verify my fix actually works. So how can I get a Picasa folder on my new (marshmallow) Android test device since Picasa has been killed by Google?
The most guaranteed way of getting a file send inside an intent, is to open a stream to it and copy it over to a private folder on your app.
This way works for local file, content uri, picasa, all of it.
Something like that:
private File getSharedFile() {
Uri uri = intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
// or using the new compat lib
Uri uri = ShareCompat.IntentReader(this).getStream();
InputStream is = null;
OutputStream os = null;
try {
File f = ... define here a temp file // maybe getCacheDir();
is = getContentResolver().openInputStream(uri);
os = new BufferedOutputStream(new FileOutputStream(f));
int read;
byte[] bytes = new byte[2048];
while ((read = is.read(bytes)) != -1) {
os.write(bytes, 0, read);
}
return f;
} catch (Exception e) {
... handle exceptions, buffer underflow, NPE, etc
} finally {
try { is.close(); } catch (Exception e) { /* u never know */ }
try {
os.flush();
os.close();
} catch (Exception e) { /* seriously can happen */ }
}
return null;
}

Get Exif data using Uri

I want to get Exif data for an image that I pick from Android gallery or similar apps like Google Photos. Here are the methods that I am using for getting image file from Uri:
public static String getActualPathFromUri(#NonNull Context context, #NonNull Uri selectedImageUri) {
Bitmap bitmap = null;
try {
bitmap = getBitmapFromUri(context, selectedImageUri);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null) {
return null;
}
File imageFileFolder = new File(context.getCacheDir(), "example");
if (!imageFileFolder.exists()) {
imageFileFolder.mkdir();
}
FileOutputStream out = null;
File imageFileName = new File(imageFileFolder, "example-" + System.currentTimeMillis() + ".jpg");
try {
out = new FileOutputStream(imageFileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (IOException e) {
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return imageFileName.getAbsolutePath();
}
private static Bitmap getBitmapFromUri(#NonNull Context context, #NonNull Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
context.getContentResolver().openFileDescriptor(uri, "r");
assert parcelFileDescriptor != null;
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
}
The reason I am opting for Bitmap from the Uri instead of getting system filename is because the method is fragmented for different versions of Android. Plus, in apps like Google Photos, some photos are shown directly from server and are not stored on the device, so getting filename is not possible. Even if I get the filename, I get Security Exception when trying to use that in my app.
The problem with this approach is, when I get the Bitmap, I don't get the Exif data. But I need the Exif data and attach it to the file that I save in my cache folder so that when I upload the image to server, it gets the image along with Exif so that it can do some further processing. The reason, I need to save it as a file or at least get the file, if it already exists in the system is because Retrofit needs a File object which then gets converted to TypedFile and sent to server using Multipart request.
So, how can I accurately get the Exif information from any kind of image Uri(local or on server) and then save the image along with it's Exif data so that I can send it to the server.

crop image save in internal storage and get bitmap from uri

I write a code crop image.The image maybe a little big,so I use intent.putExtra("return-data", false); and intent.putExtra("output", outputUri); So I should decode the uri to get bitmap.Just like this(omActivityResult)
InputStream is = null;
is = getContentResolver().openInputStream(outputUri);
Bitmap avatar = BitmapFactory.decodeStream(is);
I first make External Storage to store the crop image.It work well.
outputUri = Uri.fromFile(new File("/storage/emulated/0/upload.jpg"));
But I need to store it in Internal Storage.I follow the Android developer
to save in Internal Storage.The code follow.
FileOutputStream os = null;
try
{
os = openFileOutput("upload.jpg", 0);
}
catch (FileNotFoundException e)
{
}
finally
{
if (os != null)
{
try
{
os.close();
}
catch (IOException e)
{
}
}
}
outputUri = Uri.fromFile(new File(getFilesDir().getAbsolutePath(), "upload.jpg"));
the avatar will be null.I have read many questions in stackoverflow but it doesn't work. Can you help me?

What is the phone storage in Android devices?

I can pass sdcard location to my adb command using
file:///sdcard/Android/screen.bmp
What is the equivalent string, if my file is saved in phone memory instead of sdcard, will it be
file:///phone/Android/screen.bmp
That isn't necessarily how you access things saved internally on your phone.
Keep in mind how you save an image to internal storage:
Bitmap bitmap = ______; //get your bitmap however
try {
FileOutputStream fos = context.openFileOutput("desiredFilename.png", Context.MODE_PRIVATE);
image.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
} catch (Exception e) {
Log.e("saveToInternalStorage()", e.getMessage());
Log.e("Saving the bitmap",e.getMessage());
}
Now, to go read it, we just get the context, and call getFileStreamPath(filename) on it.
Bitmap retrievedImage;
String filename = "desiredFilename.png";
try {
File filePath = context.getFileStreamPath(filename);
FileInputStream fi = new FileInputStream(filePath);
retrievedImage = BitmapFactory.decodeStream(fi);
} catch (Exception e) {
Log.e("Retrieving the image", e.getMessage());
}
You can read more about this here.

Categories

Resources