I have an Activity that handles image files:
if (Intent.ACTION_VIEW.equals(action) && type != null && type.startsWith("image/")) {
Uri imageUri = i.getData();
try {
Bitmap image = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
} catch (Exception e) {
e.printStackTrace();
}
}
This gets me the image in the bitmap, however I also need to get exif data from it. Saving the image using image.compress removes this data.
If I click View in gmail for example, there is no real path that I can use, so I'm stuck at trying to get the exif data. Is there a way to get a byte[] of the data passed into my activity, or get the exif data from a Bitmap object?
Note:
This:
ExifInterface exifData = new ExifInterface(imageUri.getPath());
Does not cause an exception, however I get null for all the tags, and if I try to print the path, I get this:
/my.email#gmail.com/messages/248/attachments/0.1/BEST/false
If I first hit save and then view in gmail, I can successfully retrieve the exif data with the above method, passing imageUri.getPath() to an exif constructor.
If there is no way to make view work, then how can I make it so the user is prompted to use my application only after saving the file to disk? This is how my intent filters look like right now:
<activity android:name=".GameActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
it should be possible to extract the exif data with:
How to use ExifInterface with a stream or URI
Taken from there:
http://android-er.blogspot.co.at/2011/04/read-exif-of-jpg-file.html
//Get Real URL
String[] proj = { MediaStore.Images.Media.DATA };
Cursor cursor = parentActivity.managedQuery(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
String realURL = cursor.getString(column_index);
//Access Exif
ExifInterface exifInterface = new ExifInterface(realURL);
String focal_length = exifInterface.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
Related
For some time I've been trying to implement the functionality of sending an audio file from my app through WhatsApp. When debugging everything seems to work correctly in the application, the audio file is generated and saved correctly in the external storage of the device, the WhatsApp window opens and allows me to select the chat to which I want to send the audio. The problem is that when I press the send button, WhatsApp returns the message "Failed to share. Please try again" (I leave a screenshot of the error so that it can be better viewed, in addition to the code used to add said functionality).
Capture of the error shown on the screen by WhatsApp when trying to share the audio:
https://i.stack.imgur.com/gZuLt.jpg
Code used:
//////Boton//////
btn1.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view) {
try {
String mediaPath = copyFiletoExternalStorage(R.raw.audio1, "audio1.mp3");
File myFile = new File(mediaPath);
Uri newUri = getUriForFile(wspActivity.this, "com.restart.shareaudiofiles.fileprovider", myFile);
Intent compartirAudio = new Intent(android.content.Intent.ACTION_SEND);
compartirAudio.setType("com.whatsapp");
compartirAudio.setType("audio/mp3");
compartirAudio.putExtra(Intent.EXTRA_STREAM,newUri);
startActivity(Intent.createChooser(compartirAudio, "Compartir vía"));
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "Whatsapp no se encuentra instalado", Toast.LENGTH_LONG).show();
}
}
});
/////funcion auxiliar/////
private String copyFiletoExternalStorage(int resourceId, String resourceName){
String pathi= Environment.getExternalStorageDirectory() + "/Android/data/myProject/";
boolean exists = (new File(pathi)).exists();
if (!exists) {
new File(pathi).mkdirs();
}
String pathSDCard = Environment.getExternalStorageDirectory() + "/Android/data/TeLoResumoBotonera/" + resourceName;
try{
InputStream in = getResources().openRawResource(resourceId);
FileOutputStream out = null;
out = new FileOutputStream(pathSDCard);
byte[] buff = new byte[1024];
int read = 0;
try {
while ((read = in.read(buff)) > 0) {
out.write(buff, 0, read);
}
} finally {
in.close();
out.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return pathSDCard;
}
/////Elementos agregados al manifest/////
<queries>
<package android:name="com.whatsapp" />
<package android:name="com.whatsapp.w4b" />
</queries>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.restart.shareaudiofiles.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
I researched and implemented various resources to the manifest such as the fileprovider and queries with specific packages for WhatsApp (as can be seen in the code). However, the app still doesn't work. This makes me think that the problem could be in the mobile device that I am using to test the application (I would like to use another one to rule out this option, but I don't have an extra one). If the device has nothing to do with it, then clearly there is a bug in my code. Due to this, in case someone manages to identify the improvement that could make the application work correctly, I would be very grateful if you can share it with me, or at least give me an idea of where to address the problem.
I've a Bitmap which converted into URI by doing something like this
fun bitmapToUri(ctx: Context, bitmap: Bitmap): Uri {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, ByteArrayOutputStream())
val fileName = "${ctx.getString(R.string.app_name)}-${System.currentTimeMillis()}"
val path = MediaStore.Images.Media.insertImage(ctx.contentResolver, bitmap, fileName, "blah blah")
return Uri.parse(path)
}
So, somewhere I've heard that If your application created some media in my case IMAGE.
You can delete without requiring any permission after API LEVEL - 30 ( Android 11 ). However I've added permission in Manifest
So I'm deleting a file or better Image from URI like this ( Let me know If I can also delete in other ways. Target SDK - 30)
fun deleteUri(context: Context, uri: Uri) {
val contentResolver = context.contentResolver
contentResolver.delete(uri, null, null)
}
Calling this function in some secret place just kidding in MainActivity like this -
uri = bitmapToUri(this#MainActivity, bitmap)
...
// I assure you uri is not null
coroutineScope.launch {
deleteUri(this#MainActivity, uri)
}
...
I'm getting a SecurityException :(
Writing exception to parcel
java.lang.SecurityException: com.example.myapp has no access to content://media/external/images/media/1000000077
at com.android.providers.media.MediaProvider.enforceCallingPermissionInternal(MediaProvider.java:9959)
at com.android.providers.media.MediaProvider.enforceCallingPermission(MediaProvider.java:9856)
at com.android.providers.media.MediaProvider.deleteInternal(MediaProvider.java:5878)
at com.android.providers.media.MediaProvider.delete(MediaProvider.java:5767)
.... and some more
I've also added file provider in Manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"
tools:replace="android:resource" />
</provider>
P.S - I'm testing in Emulator - Pixel 6 API 33, also new on on StackOverflow so please be easy
If you have any question don't hesitate to shoot comment :)
I'm trying to take a picture and save it following Android Documentation.
The main difference is that my code is extending Fragment, not Activity. And the fact that I'm storing pictures on the private app folder instead of the public external storage.
My Code:
private void dispatchTakePictureIntent() {
PackageManager packageManager = getActivity().getPackageManager();
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(packageManager) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
Log.d(TAG, "Error occurred while creating the File:"+ex.toString());
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(getActivity(),
"com.eric.nativetoolkit.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
private File createImageFile() throws IOException
{
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpeg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = image.getAbsolutePath();
return image;
}
This is basically cause every time I try to save pictures on the external Storage, AndroidStudio logcat shows the error "Error occurred while creating the File" due to permisions, I've the following permisions in my AndroidManifest.xml, and I also have a method to request it, but nothing works.
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eric.nativetoolkit">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<application
android:allowBackup="true"
android:supportsRtl="true">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.eric.nativetoolkit.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
</application>
</manifest>
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="." />
</paths>
But the main problem is that even correctly storing pictures inside my package/files/Pictures directory, when I try to see the image plugging my device onto the computer and navigating to the folder, it shows the message (in spanish) "Can't open file":
What I can not understand is why in my device I can correctly visualize the image. I've tried changing format (JPG, JPEG, PNG...) but no diference.
Checking if that FLAG_GRANT works. But why I need that flag only for external? And where is the relation with the fact that I can not visualize my pictures on Desktop?
You are launching a third-party camera app via ACTION_IMAGE_CAPTURE. It has no rights to write to the location specified by your FileProvider-supplied Uri. Adding FLAG_GRANT_WRITE_URI_PERMISSION tells Android that your app wishes to give write access to that location to the camera app.
Without that permission, the camera app will fail with some sort of error. You will still have a file, since you are using File.createTempFile() to create an unnecessary empty file before you try ACTION_IMAGE_CAPTURE. If you look at the file size via your desktop file manager, you should see that it is 0 bytes. A 0-byte file is not a valid image, which is why the desktop cannot display it.
I'm trying to pass an image that resides in the res/raw directory of my app along with a share intent.
I followed the process described in the FileProvider docs, and here's my code:
AndroidManifest.xml
<application ...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths" />
</provider>
</application>
res/xml/paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="shared" path="./"/>
</paths>
The code in my activity:
String shareToPackage = ...
File imageFile = new File(context.getFilesDir().getPath() + "/image");
if (!imageFile.exists()) { // image isn't in the files dir, copy from the res/raw
final InputStream inputStream = context.getResources().openRawResource(R.raw.my_image);
final FileOutputStream outputStream = context.openFileOutput("image", Context.MODE_PRIVATE);
byte buf[] = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
outputStream.close();
inputStream.close();
imageFile = new File(context.getFilesDir().getPath() + "/image");
}
if (!imageFile.exists()) {
throw new IOException("couldn't find file");
}
final Uri uri = Uri.fromFile(imageFile);
context.grantUriPermission(shareToPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/png");
intent.putExtra(Intent.EXTRA_TEXT, "here's the image");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setPackage(shareToPackage);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
The above does not work as the file that I get in the other app isn't accessible:
java.io.FileNotFoundException: FILE_PATH: open failed: EACCES
(Permission denied)
Any idea what I'm doing wrong here?
Thanks.
Get rid of the path attribute in <files-path>, as it is not needed here, since you are serving everything from getFilesDir().
Do not use string concatenation when creating File objects. Replace:
new File(context.getFilesDir().getPath() + "/image.png");
with:
new File(context.getFilesDir().getPath(), "image.png");
Most importantly, do not use Uri.fromFile(). Use FileProvider.getUriForFile(). As it stands, you are going through all this work to set up FileProvider, then you do not use the FileProvider for making the content available to the other app.
Or, get rid of all of this, and use my StreamProvider, which can serve a raw resource directly.
Or, write your own ContentProvider that serves the raw resource directly.
#nitzan-tomer, see https://stackoverflow.com/a/33031091/966789
What Are Runtime Permissions?
With Android 6.0 Marshmallow, Google introduced a new permission model that allows users to better understand why an application may be requesting specific permissions. Rather than the user blindly accepting all permissions at install time, the user is now prompted to accept permissions as they become necessary during application use.
Sorry for the title, I was not sure how to word it, my problem with my app is taking a photo and getting its Uri content to write to a new file using input/output stream. So far I have this
Intent take_photo_intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (take_photo_intent.resolveActivity(getActivity().getPackageManager()) != null)
{
try
{
// create file from a template
image_file = createFileForImage();
}
catch (IOException e)
{
e.printStackTrace();
}
// check if file is null
if(image_file != null)
{
// create uri using file and applying a provider
image_uri = FileProvider.getUriForFile(getContext(), PROVIDER, image_file);
take_photo_intent.putExtra(MediaStore.EXTRA_OUTPUT, image_uri);
image_path = image_uri.getPath();
// start activity
startActivityForResult(take_photo_intent, TAKE_PHOTO);
}
}
in this case, the return bitmap is what I need to display the thumbnail quality image in my app
my issue comes with trying to get the full scaled image and save it to a custom folder in the android devices storage.
I been following this tutorial
https://developer.android.com/training/camera/photobasics.html#Save%20the%20Full-size%20Photo
I am not 100% sure I set up the provider correctly, the app compiles and runes, my issue is with the the Uri.
if you go back to this chunk of my app
image_uri = FileProvider.getUriForFile(getContext(), PROVIDER, image_file);
take_photo_intent.putExtra(MediaStore.EXTRA_OUTPUT, image_uri);
image_path = image_uri.getPath();
the image_file is a File object I created in a folder in the external storage, that part is fine since i used that path for other things and can verify it in a file manager. this is not used until later.
The provider "seems to work" since the app runs
my issue is the image_uri. My thoughts is, i have a empty file saved in that dir i created, now I need to read the contents of image_uri into that file, so I did this in the onActivityResult
// this will hold the Uri data
InputStream in_file = null;
// this will be used to write the input stream to the file
OutputStream out_file = null;
// check if Uri is null
if(image_uri != null) {
try {
// get the Uri data into an input stream
in_file = getContext().getContentResolver().openInputStream(image_uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// check for nulls
if (in_file != null && image_file != null) {
// create output stream linked to new file location
try {
out_file = new FileOutputStream(image_file);
Log.i("IMAGE", "open outsream");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// create temp byte array
byte[] image_bytes = null;
try {
// use apache tools to write bytes to that file using the outputstream
image_bytes = IOUtils.toByteArray(in_file);
} catch (IOException e) {
e.printStackTrace();
}
if (image_bytes != null && out_file != null) {
try {
out_file.write(image_bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Now im thinking, ok, I have the picture just taken which the provider set up, I create an external file onto sd card which I know works, and I have an input stream to the uri and an outputstream to the file, and used IOUtils.toByteArray to write tho.
However, my problem is the uri data is all blank. by that i mean the bytes are all 0's or size of array after that IOUtils.toByteArray call is 0. so I am guessing it must be a problem with the provider. I am still not sure how it works, going off the tutorial, I have this
AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.main.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"/>
</provider>
res/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="my_images" path="Android/data/com.myapp/files/Pictures" />
As I said, I am not sure how this works, is /files/Pictures a folder in my apps internal storage by default? do I have to make it?
when logging out some stuff, this is what I got
image_uri = FileProvider.getUriForFile(getContext(), PROVIDER, image_file);
Log.i("IMAGE", "DEBUG--->: " + image_uri.getPath());
I/IMAGE: DEBUG--->: /my_images/camera_shots/20160808_190857-1642881770.jpg
and I never created that folder, so I am confused on how this all works.