MediaStore.ACTION_VIDEO_CAPTURE URI to external storage - java

I am using an Intent to capture videos in my app. My code is similar to this (adapted from https://developer.android.com/training/camera-deprecated/photobasics):
File mVideo;
private File createVideoFile() throws IOException {
#SuppressLint("SimpleDateFormat")
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmssSSS").format(new Date());
String videoFileName = "VID_" + timeStamp + ".mp4";
File storageDir = Environment.getExternalStorageDirectory();
File movies = new File(storageDir, "Movies");
if (!movies.exists() && !movies.mkdir()) { // suprisingly even this works?!
Log.e(TAG, "could not create Movies directory");
throw new IOException();
}
mCurrentVideo = new File(movies, videoFileName);
}
private final ActivityResultLauncher<Uri> requestRecordVideoLauncher =
registerForActivityResult(new ActivityResultContracts.CaptureVideo(), success -> {
if (success) {
Log.i(TAG, "successfully recorded video")
assert(mVideo.canRead());
} else {
if (!mCurrentVideo.delete()) {
Log.w(TAG, "could not delete aborted video recording");
}
}
});
public void dispatchTakeVideoIntent() {
File videoFile;
try {
createVideoFile();
} catch (IOException ex) {
Log.i(TAG, "could not create file")
return;
}
Uri videoUri = FileProvider.getUriForFile(mContext, getPackageName() + ".provider", videoFile);
requestRecordVideoLauncher.launch(videoUri);
}
I have a file provider registered with this path:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>
The major difference to the code in the developer manual is that I am using a path to public external storage. Thus, videos would usually be saved to the path /storage/emulated/0/Movies/VID_{DATE}{TIME}.
Testing on different devices and emulators with different API levels (21, 30, 33) concludes that this is a legal thing to do (it does not even need any permissions to read the generated files).
From what I have read in the developer references, with scoped storage an app can still access all media files it has previously created without requesting any permission (https://developer.android.com/training/data-storage/shared/media#storage-permission-not-always-needed). Additionally, the File API can still be used to access files in external storage (https://developer.android.com/about/versions/11/privacy/storage#media-direct-file-native).
Do you think, it is a good idea to trust that this will still work in later Android versions? Is there any other way to easily record a video to external storage?

Related

How to create folder & save app related files (jpg) as per Scoped Storage

Just like Whatsapp, Telegram does, I want to create a folder "DemoApp" and save .jpg images inside it in Internal storage Environment.getExternalStorageDirectory().getPath() of Android 11 as per scoped storage.
I have already given following permission in AndroidManifest.xml file.
uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
And also written code to grant Read/Write Permission of folders from user.
if (ContextCompat.checkSelfPermission(SplashActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "LOG, Permission to record denied");
ActivityCompat.requestPermissions(SplashActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_WRITE_STORAGE);
But still after user grants folder permissions, the folder is been created, but the Images are not being saved in the 'DemoApp' folder.
Following is the code which is working till Android 10 but not in Android 11:
File localFile;
final String folderLocation= Environment.getExternalStorageDirectory() + "/" + "DemoApp" + "/";
// userImage.setImageDrawable(postOwnerImage.getContext().getDrawable(R.drawable.profilepic));
localFile = new File(folderLocation);
localFile.mkdirs();
final File file = new File(folderLocation+ "/" + "Picture1" + ".jpg");
if (file.exists()) {
Picasso.get().load(file).resize(1024, 800).noPlaceholder().onlyScaleDown().into(postOwnerImage);
} else {
StorageReference storageReference;
storageReference = FirebaseStorage.getInstance().getReference();
String STORAGE_PATH_DOWNLOADS = "ImageFolder" + "/"; // backslash to refer folder in firebase Storage
final File downloadFile = new File(folderLocation + "/" + "Picture1" + ".jpg");
storageReference.child(STORAGE_PATH_DOWNLOADS +
"Picture1.jpg").getFile(downloadFile).addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {
#Override
public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {
if (downloadFile.exists()) {
Picasso.get().load(downloadFile).resize(1024, 800).noPlaceholder().onlyScaleDown().centerCrop().into(postImage);
} else {
}
}
});
}

Android app: How do I create a directory in the internal storage

I can't seem to be able to figure out how to create a directory/file through an android app to the internal storage. I have the following code:
public class Environment extends SurfaceView implements SurfaceHolder.Callback {
public static String FILE_PATH;
//other unimportant variables
public Environment(Conext context) {
super(context);
FILE_PATH = context.getFilesDir() + "/My Dir/";
File customDir = new File(FILE_PATH);
if(!customDir.exists())
System.out.println("created my dir: " + customDir.mkdir());
File test = new File(FILE_PATH + "testFile.txt");
try {
if(!test.exists())
System.out.println("created test: " + test.createNewFile());
} catch(Exception e) {
e.printStackTrace();
}
//other unimportant stuff
}
}
I then use ES File Explorer to see if it created the file and I don't see the directory/file anywhere despite it printing out "true" for the System.out.println() calls.
What am I doing wrong?
The path where you are creating file is in apps private location. Generally you can't access it from outside. It's actually created in apps data folder. However it seems you want to write in external folder.
To write in the external storage, you must request the WRITE_EXTERNAL_STORAGE permission in your manifest file:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
code:
String folder_main = "My Dir";
File f = new File(Environment.getExternalStorageDirectory(), folder_main);
if (!f.exists()) {
f.mkdirs();
}
File test = new File(f , "testFile.txt");
Here you will find how to you will create folder/file in external storage.
Save a File on External Storage
You can try with below:
ContextWrapper contextWrapper = new ContextWrapper(getApplicationContext());
File directory = contextWrapper.getDir(getFilesDir().getName(), Context.MODE_PRIVATE);
File file = new File(directory,”fileName”);
String data = “TEST DATA”;
FileOutputStream fos = new FileOutputStream(“fileName”, true); // save
fos.write(data.getBytes());
fos.close();
This will write file in Device's internal storage (/data/user/0/com.yourapp/)
Hope this helps!

can't delete file from external storage in android programmatically

I am trying to delete a file located at the path
/storage/714D-160A/Xender/image/Screenshot_commando.png
What I've done so far:
try{
String d_path = "/storage/714D-160A/Xender/image/Screenshot_commando.png";
File file = new File(d_path);
file.delete();
}catch(Exception e){
e.printStackTrace();
}
and the file is still at its place(Not deleted :( )
Also I've given permission in Manifest file.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.STORAGE" />
public static boolean delete(final Context context, final File file) {
final String where = MediaStore.MediaColumns.DATA + "=?";
final String[] selectionArgs = new String[] {
file.getAbsolutePath()
};
final ContentResolver contentResolver = context.getContentResolver();
final Uri filesUri = MediaStore.Files.getContentUri("external");
contentResolver.delete(filesUri, where, selectionArgs);
if (file.exists()) {
contentResolver.delete(filesUri, where, selectionArgs);
}
return !file.exists();
}
Using ContentResolver to delete media files is wrong and provides many problems for the user.
You can not delete a file on the sd-card simply by deleting its information from the ContentResolver on Android versions greater than Jelly Bean(4.3).
It works only on Android versions prior to KitKat(4.4).
That's why the Android team provided DocumentProvider.
Why contentResolver.delete(...) is wrong?
1. Fills up the sd-card
When you try to delete a media file on the sd-card by the ContentResolver on Android versions greater than 4.3, the actual media file will remain untouched because the contentResolver.delete(...) approach only removes the information (name, date, path ...) of the media and you will end up having unregistered media files on your sd-card which ContentResolver has no idea about their existence anymore and that's why you couldn't see them in your gallery and you think they've been deleted with this approach while they're still there and fill up the sd-card each time the user tries to delete a media file on the sd-card.
2. Media files (Images, videos, gifs ...) will come back to the gallery
There are many apps out there especially gallery and file manager ones that will find these unregistered media files and will add them to the ContentResolver again as of their normal behavior while the user assumes his/her unwanted media files are gone.
Sure no user wants his/her assuming deleted images or videos show up in the middle of a demonstration.
So, what's the correct approach to remove media files on the sd-card?
Well, this has already been answered here with the use of DocumentProvider.
From Android 4.4 onwards, you can't write to SD card files (except in the App directory) using the normal way. You'll have to use the Storage Access Framework using DocumentFile for that.
The following code works for me:
private void deletefile(Uri uri, String filename) {
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, uri);
DocumentFile file = pickedDir.findFile(filename);
if(file.delete())
Log.d("Log ID", "Delete successful");
else
Log.d("Log ID", "Delete unsuccessful");
}
where filename is the name of the file to be deleted and uri is the URI returned by ACTION_OPEN_DOCUMENT_TREE:
private static final int LOCATION_REQUEST = 1;
private void choosePath() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivityForResult(intent, LOCATION_REQUEST);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == LOCATION_REQUEST && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
Uri uri = resultData.getData();
if (uri != null) {
/* Got the path uri */
}
}
}
}
Use Environment.getExternalStorageDirectory().getAbsolutePath() instead of hard coding storage path
String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath();
File f = new File(baseDir + "/714D-160A/Xender/image/Screenshot_commando.png");
boolean d = f.delete();

FileProvider.getUriForFile() not correctly linking to image taken from MediaStore.ACTION_IMAGE_CAPTURE

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.

Share a file without saving it on the external storage

I am using the following code to allow the user to share a bitmap,
try {
File save_dir = Environment.getExternalStorageDirectory();
FileOutputStream out = new FileOutputStream(save_dir
+ "/test.jpg");
final_bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/jpeg");
share.putExtra(Intent.EXTRA_STREAM,
Uri.parse("file:///" + save_dir + "/test.jpg"));
startActivity(Intent.createChooser(share,
getString(R.string.share_dialog_title)));
} catch (Exception e) {
Toast.makeText(mainActivity, "Error in sharing.",
Toast.LENGTH_LONG).show();
}
the problem is, the if the device has no SD Card, the file will not be saved.
is there is any good way to save it to the internal storage, in somewhere other apps can access, since saving it in the applications data directory will not allow other applications to access it.
Regards

Categories