I would like, as title suggests to find an image via Android File Explorer. I'm taking a photo and saving in on external storage but that part of storage is only available to app itself.
Basically, before I start Camera activity I have following code:
public File getPhotoFile(Contact contact, Context context) {
File externalFilesDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (externalFilesDir == null) {
return null;
}
File f = new File(externalFilesDir, contact.getPhotoFilename());
return f;
}
Method in Contact class getPhotoFilename():
public String getPhotoFilename() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
return "IMG_" + this.getId() + "_" + timeStamp + ".jpg";
}
And in activity:
cameraButton.setOnClickListener((View v) -> {
File f = getPhotoFile(newContact, this);
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.email.fileprovider",f);
captureImage.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(captureImage, REQUEST_TAKE_PHOTO);
});
I display full path, like this:
Log.i("PATH", filePath); //file path is member variable which just holds f.getAbsoluthPath();
What I get in output is:
/storage/emulated/0/Android/data/com.example.email/files/Pictures/IMG_0_20200428_001544.jpg
I've tried find that image in File Device Explorer, but no luck.
Related
We have an Android app where field workers take photographs which are stored on their phone and also uploaded via a web api.
If the uploads fail they do have retry mechanisms but sometimes they need to resort to pulling the images off their phone.
In order to bring the app up to Android 10 version without deprecation I was forced to write the images to an app internal directory.
The problem is that when they upgrade their app they lose their photos from the app.
(I do also copy the images to a backup directory but this is all looking a bit klutzy)
I would like to write the images to :
/storage/emulated/0/DCIM/GoTrialImages
Instead they are going to :
/storage/emulated/0/Android/data/au.come.aceware.ktr.ktr/files/DCIM/GoTrialImages/photoIntent
(where photoIntent is the activity that this is occurring in)
Here is the code I have copied and tweaked from an online article:
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = "JPEG_" + timeStamp + ".jpg";
File mediaStorageDir = new File(getExternalFilesDir(Environment.DIRECTORY_DCIM + File.separator +"GoTrialPhotos"), TAG);
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists() && !mediaStorageDir.mkdirs()){
Log.d(TAG, "failed to create directory");
}
// Return the file target for the photo based on filename
File file = new File(mediaStorageDir.getPath() + File.separator + fileName);
Uri bmpUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file);
Here is my file provider entry in the manifest:
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>```
and here is #xml/provider_paths:
```<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>```
1) Is it possible to do what I am seeking to do ?
2) How do I do it without using deprecated code
Many thanks in advance
Tony
Following the suggestion to use media store I kept most of the code for creating the app internal file name
(mainly because I wanted the randomised display name):
private File createImageFileV2() throws IOException
{
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
imageFileNameToUseAtWebServerEnd = strTrial + "_" + timeStamp + "_" + strUserId + ".jpg";
File[] storageDir = getExternalMediaDirs();
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir[0] /* directory */
);
return image;
}
I then passed the file object in to the following code:
public Uri testgetPhotoFileUri2(File f)
{
Uri uri = null;
String strDisplayName = f.getName();
final ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, strDisplayName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM );
final ContentResolver resolver = thisContext.getContentResolver();
try
{
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
uri = resolver.insert(contentUri, values);
if (uri == null)
throw new IOException("Failed to create new MediaStore record.");
return uri;
}
catch (IOException e)
{
if (uri != null) {
// Don't leave an orphan entry in the MediaStore
resolver.delete(uri, null, null);
}
}
return uri;
}
I then used the resulting uri as my camera uri:
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri);
However, when the OnActivityResult calls HandleBigCameraPhoto and attempts to extract the bitmap using the CameraUri:
private void handleBigCameraPhoto() {
Bitmap bitmap = null;
if (cameraUri != null)
{
if (Build.VERSION.SDK_INT >= 29) {
ImageDecoder.Source source = ImageDecoder.createSource(getApplicationContext().getContentResolver(), cameraUri);
try {
bitmap = ImageDecoder.decodeBitmap(source);
} catch (IOException e) {
e.printStackTrace();
}
It error traps to "no such file or directory"
Does this mean that I need to most of my work (image resizing, rotation, etc) using my app private file only and then as a last step insert the bitmap in to media store (and then delete the app private file so the user does not see the file name twice under gallery, recents)?
You will not make use of a deprecated function:
File file = new File(getExternalFilesDir(null)
.getParentFile()
.getParentFile()
.getParentFile()
.getParentFile(), "DCIM");
;-).
After creating video and save it, I'm trying to make it visible in the gallery app by scanning the video path using the code below :
Path of saved video :
public String getPath() {
String videoFileName = System.currentTimeMillis() + ".mp4";
File storageDir = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
+ "/MyFolderApp");
boolean success = true;
if (!storageDir.exists()) {
success = storageDir.mkdirs();
}
if (success) {
File videoFile = new File(storageDir, videoFileName);
savedVideoPath = videoFile.getAbsolutePath();
}
return savedVideoPath;
}
Scan path :
private void galleryScanner(String path) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(path);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
sendBroadcast(mediaScanIntent);
}
After saving the video, I call the scan method like this: galleryAddPic(getPath());
Weird behavior : If I open the Google Photos app and go to Album > MyFolderApp, I could see the saved video there, also when I exit the Google Photos app and return to Gallery System, the saved video shown normally
My app selects an image taken by the system's camera and obtain its Uri from onActivityResult method, from here i would like to convert the Uri to android standard file path so that i will be able to check its orientation by passing the file path to Exifinterface's constructor and execute getAttributeInt to receive a value and then decide on how many degrees to rotate the image.
I found a sample code here on stackoverflow that has the capability to convert the image uri to file path. but the problem is, it uses DocumentContract class which is added in api level 19 onwards but my app needs to support lower version than API level 19. How can I do this? Or atleast have an alternative solution for getting the orientation of the image.
Use the following method to get the file path from the Uri. Here you need to pass the context and the uri and it maintains the compatibility for pre-Kitkat.
public String getRealPathFromURI(Context context, Uri contentUri) {
String res = "";
String[] proj = { MediaStore.Images.Media.DATA };
Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
res = cursor.getString(column_index);
}
cursor.close();
} else {
Log.d(TAG, "Cursor is null");
return contentUri.getPath();
}
return res;
}
Updated for Camera : The above solution is working for Uri returned for the Gallery Intent. For the camera intent use the below code.
public File getOutputMediaFile() {
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.
File mediaStorageDir;
// If the externam directory is writable then then return the External
// pictures directory.
if (isExternalStorageWritable()) {
mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
.getAbsolutePath() + File.separator + IConstants.CUSTOM_PROFILE_PIC_PATH);
} else {
mediaStorageDir = Environment.getDownloadCacheDirectory();
}
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Log.d("MyCameraApp", "failed to create directory");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
return mediaFile;
}
Create a global variable selectedImage for storing the image path.
private Uri getOutputMediaFileUri() {
File mediaFile = Utilities.getInstance().getOutputMediaFile();
selectedImage = mediaFile.getAbsolutePath();
return Uri.fromFile(mediaFile);
}
Now call the Camera intent using the following method.
public void dispatchCameraIntent(View view) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// If there any applications that can handle this intent then call the intent.
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
Uri fileUri = getOutputMediaFileUri();
Log.d(TAG, "camera Uri : " + fileUri);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(takePictureIntent, CAMERA_PICKER);
}
}
In OnActivityResult use the selectedImage as the file path.
I want to create a XML file inside my Android app.
This file I want to write into the documents folder of my Android device.
Later I want to connect my Android device to my PC using USB and read that XML file out of the documents folder.
My Device is an Android Galaxy Tab Pro 10.1, Android 4.4.2.
I tried already:
String fileName = "example.xml";
String myDirectory = "myDirectory";
String externalStorage = Environment.getExternalStorageDirectory().getAbsolutePath();
File outputFile = new File(externalStorage + File.separator + myDirectory + File.separator + fileName);
But no file is created. I also want later to read that file out of the documents folder into may app again.
Any help is appreciated, thanks!
I know this is late, but you can get the documents directory like this:
File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File file = new File(dir, "example.txt");
//Write to file
try (FileWriter fileWriter = new FileWriter(file)) {
fileWriter.append("Writing to file!");
} catch (IOException e) {
//Handle exception
}
Set permission in Android Manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Use this code to write to external directory
String fileName = "example.xml";
String dirName = "MyDirectory";
String contentToWrite = "Your Content Goes Here";
File myDir = new File("sdcard", dirName);
/*if directory doesn't exist, create it*/
if(!myDir.exists())
myDir.mkdirs();
File myFile = new File(myDir, fileName);
/*Write to file*/
try {
FileWriter fileWriter = new FileWriter(myFile);
fileWriter.append(contentToWrite);
fileWriter.flush();
fileWriter.close();
}
catch(IOException e){
e.printStackTrace();
}
Before creating file you have to create directory in which you are saving the file.
Try like this one:-
String fileName = "example.xml";
String myDirectory = "myDirectory";
String externalStorage = Environment.getExternalStorageDirectory().getAbsolutePath();
File outputDirectory = new File(externalStorage + File.separator + myDirectory );
if(!outputDirectory.exist()){
outputDirectory.mkDir();
}
File outputFile = new File(externalStorage + File.separator + myDirectory + File.separator + fileName);
outputFile.createFile();
Try restarting you device and then check if the file exists. If so, you are creating it (which it looks like you should be based on your code) but it is not showing up until the media is scanned on your device. Try implementing MediaScannerConnectionClient so it will show become visible after creation.
public class MainActivity extends Activity implements MediaScannerConnectionClient {
private MediaScannerConnection msConn;
private File example;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
msConn = new MediaScannerConnection(this.getApplicationContext(), this);
String dir = Environment.getExternalStorageDirectory() + "/Documents/";
example = new File(dir, "example.xml");
msConn.connect();
}
#Override
public void onMediaScannerConnected() {
msConn.scanFile(example.getAbsolutePath(), null);
}
#Override
public void onScanCompleted(String path, Uri uri) {
msConn.disconnect();
}
From Android 10 onwards, Android started using Scoped Storage model to protect user privacy.
If you want to share this file with the User, then you should write this file in Shared Storage. To write a file in Shared Storage, this has to be done in 3 steps:-
Step 1: Launch System Picker to choose the destination by the user. This will return Uri of the destination directory.
private ActivityResultLauncher<Intent> launcher; // Initialise this object in Activity.onCreate()
private Uri baseDocumentTreeUri;
public void launchBaseDirectoryPicker() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
launcher.launch(intent);
}
Step 2: Launch System Picker to choose the destination by the user. This will return the Uri of the destination directory. Also, you can optionally persist the permissions and Uri for future use.
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
baseDocumentTreeUri = Objects.requireNonNull(result.getData()).getData();
final int takeFlags = (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// take persistable Uri Permission for future use
context.getContentResolver().takePersistableUriPermission(result.getData().getData(), takeFlags);
SharedPreferences preferences = context.getSharedPreferences("com.example.fileutility", Context.MODE_PRIVATE);
preferences.edit().putString("filestorageuri", result.getData().getData().toString()).apply();
} else {
Log.e("FileUtility", "Some Error Occurred : " + result);
}
}
Step 3: Write CSV content into a file.
public void writeFile(String fileName, String content) {
try {
DocumentFile directory = DocumentFile.fromTreeUri(context, baseDocumentTreeUri);
DocumentFile file = directory.createFile("text/*", fileName);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(file.getUri(), "w");
FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
fos.write(content.getBytes());
fos.close();
} catch (IOException e) {
}
}
For more explanation, you can read "How to Save a file in Shared Storage in Android 10 or Higher" or Android official documentation.
I can press a button, open up the native camera app, and successfully take a picture. But then when I check the Gallery or Photos native apps on my phone, the picture isn't saved there. I'm very new to Android so it's likely I'm missing something important in my code.
Questions:
1) Where are these pictures being saved?
2) Can I modify the below code somehow to save instead to internal storage, so all pictures taken with my app are private and only accessible through my app?
3) If I wanted to save these pictures to an object, along with some text/other input, what would be the best way? Should I just save a Uri or some identifier to reference the image later, or save the actual BitMap image?
Any help is greatly appreciated, thanks!
Here is my code to take the picture:
mImageButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
imageUri = CameraUtils.getOutputMediaFileUri(CameraUtils.MEDIA_TYPE_IMAGE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, REQUEST_IMAGE);
}
}
CameraUtils class taken straight from Google developer guides:
public static Uri getOutputMediaFileUri(int type)
{
return Uri.fromFile(getOutputMediaFile(type));
}
public static File getOutputMediaFile(int type)
{
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "camera");
if (!mediaStorageDir.exists())
{
if (!mediaStorageDir.mkdirs())
{
return null;
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
if (type == MEDIA_TYPE_IMAGE)
{
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_" + timeStamp + ".jpg");
}
else if(type == MEDIA_TYPE_VIDEO)
{
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"VID_" + timeStamp + ".mp4");
}
else
{
return null;
}
return mediaFile;
}
1) By looking at the code, I'd expect the pictures to be saved in a directory called 'camera' which would be found in the Pictures folder on your device (external storage). These might not instantly appear in your gallery, however in later versions of Android (Kitkat and maybe jelly-bean though I can't verify that right now) you should be able to open the Photos app and find them somewhere in there. If that is not the case, then launch a file explorer app (example apps are ASTRO File Manager or X-Plore) and browse to the Pictures/camera directory where you should see your images. The next time your media gets re-indexed (phone reboot, or a re-index triggered from elsewhere), you should see these pictures in your gallery/photo apps. If you want to refresh your media programatically, here might help. Finally, make sure you have the READ_EXTERNAL_STORAGE permission in your Android manifest as specified this (Android doc).
2) If you want to save images to be only available to your application, you need to save them to your application's internal data directory. Take a look at this straight from the Android doc. Make sure to use the MODE_PRIVATE flag.
3) For this, you would want to store the file path somewhere accessible to your app. Either you could save your file paths to a text file with some other text data, or you could use a sqlite database. Finally, you could use an ORM like ORMLite for Android to save a java object which might hold data for your picture and have some fields you want to persist (title, description, path, etc). Here and here is an intro on how to get started with SQLite database in Android (straight from the official doc). If you want to use ORMLite, there is plenty of information on their site here. The developer has spent a lot of time answering StackOverflow questions..
All of your questions can be answered with a few simple Google searches. They are very standard and basic things to do in Android, so you should be able to find a plethora of information and tutorials online.
EDIT:
In response to your comment about the second question. This is what I would probably do (or something similar):
Note that I didn't test this. It's from the top of my head. If you have more issues comment here!
Activity code...
mImageButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
imageUri = CameraUtils.getOutputMediaFileUri(currentActivity, CameraUtils.MEDIA_TYPE_IMAGE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, REQUEST_IMAGE);
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == REQUEST_IMAGE)
{
if (resultCode == RESULT_OK)
{
String pathToInternallyStoredImage = CameraUtils.saveToInternalStorage(this, imageUri);
// Load the bitmap from the path and display it somewhere, or whatever
}
else if (resultCode == RESULT_CANCELED)
{
//Cancel code
}
}
}
CameraUtils class code...
public static Uri getOutputMediaFileUri(int type)
{
return Uri.fromFile(getOutputMediaFile(type));
}
public static File getOutputMediaFile(int type)
{
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "camera");
createMediaStorageDir(mediaStorageDir);
return createFile(type, mediaStorageDir);
}
private static File getOutputInternalMediaFile(Context context, int type)
{
File mediaStorageDir = new File(context.getFilesDir(), "myInternalPicturesDir");
createMediaStorageDir(mediaStorageDir);
return createFile(type, mediaStorageDir);
}
private static void createMediaStorageDir(File mediaStorageDir) // Used to be 'private void ...'
{
if (!mediaStorageDir.exists())
{
mediaStorageDir.mkdirs(); // Used to be 'mediaStorage.mkdirs();'
}
} // Was flipped the other way
private static File createFile(int type, File mediaStorageDir ) // Used to be 'private File ...'
{
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile = null;
if (type == MEDIA_TYPE_IMAGE)
{
mediaFile = new File(mediaStorageDir .getPath() + File.separator +
"IMG_" + timeStamp + ".jpg");
}
else if(type == MEDIA_TYPE_VIDEO)
{
mediaFile = new File(mediaStorageDir .getPath() + File.separator +
"VID_" + timeStamp + ".mp4");
}
return mediaFile;
}
public static String saveToInternalStorage(Context context, Uri tempUri)
{
InputStream in = null;
OutputStream out = null;
File sourceExternalImageFile = new File(tempUri.getPath());
File destinationInternalImageFile = new File(getOutputInternalMediaFile(context).getPath());
try
{
destinationInternalImageFile.createNewFile();
in = new FileInputStream(sourceExternalImageFile);
out = new FileOutputStream(destinationInternalImageFile);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0)
{
out.write(buf, 0, len);
}
}
catch (IOException e)
{
e.printStackTrace();
//Handle error
}
finally
{
try {
if (in != null) {
in.close();
}
if (out != null) {
in.close();
}
} catch (IOException e) {
// Eh
}
}
return destinationInternalImageFile.getPath();
}
So now you have the path pointing to your internally stored image, which you can then manipulate/load from your onActivityResult.