I got this strange problem when I want to save images to Android from my app. The images get damaged now Android cant use them. I cant see whats wrong in my code.
Here's my download method:
public void createExternalStoragePublicPicture(String DownloadUrl, String fileName) {
// Create a path where we will place our picture in the user's
// public pictures directory. Note that you should be careful about
// what you place here, since the user often manages these files. For
// pictures and other media owned by the application, consider
// Context.getExternalMediaDir().
Log.i("Download", DownloadUrl);
Log.i("File", fileName);
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File file = new File(path, fileName);
try {
// Make sure the Pictures directory exists.
path.mkdirs();
// Very simple code to copy a picture from the application's
// resource into the external file. Note that this code does
// no error checking, and assumes the picture is small (does not
// try to copy it in chunks). Note that if external storage is
// not currently mounted this will silently fail.
URL url = new URL(DownloadUrl);
URLConnection ucon = url.openConnection();
InputStream is = ucon.getInputStream();
OutputStream os = new FileOutputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
is.close();
os.close();
// Tell the media scanner about the new file so that it is
// immediately available to the user.
MediaScannerConnection.scanFile(this,
new String[] { file.toString() }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("ExternalStorage", "Scanned " + path + ":");
Log.i("ExternalStorage", "-> uri=" + uri);
}
});
} catch (IOException e) {
// Unable to create file, likely because external storage is
// not currently mounted.
Log.w("ExternalStorage", "Error writing " + file, e);
}
}
Any ideas what is going wrong here?
Possibly, the is.available() does not work as you expect. Generally you should not rely on available() as it is not strictly specified what value it returns.
See InputStream: "It is particularly important to realize that you must not use this method to size a container and assume that you can read the entirety of the stream [...]")
Use the common "loop"-approach:
int c;
while ( (c = is.read(buf)) >= 0 ) {
os.write(buf,0,c)
}
This makes sure you really read all of the bytes from the source file.
Related
I previously used external storage to store specific data that I would like to share between my applications (without having any contentprovider "host"), by using WRITE_EXTERNAL_STORAGE.
Not a media file, it is more like an encoded string in it.
It does not seem to be possible anymore on Android 11, without requesting MANAGE_EXTERNAL_STORAGE.
But this permission will not be granted by Google to all applications, and will require to fill a form, like everry "restricted permissions" (READ_CALL_LOG, READ_SMS, ACCESS_FINE_LOCATION, etc...) See support.google.com/googleplay/android-developer/answer/9888170
Exemple :
By having XX applications, each one could be the first one to write a file (the first app used by the user basically), and the 3 other applications would read this file when started.
Any idea on how this can be achieved on Android 11?
BlobManager seems to be appropriate but documentation is terrible (I tried it without success: new BlobStoreManager read write on Android 11)
private void writeFile(String data) {
try {
File f = new File(Environment.getExternalStorageDirectory(), FOLDER_NAME);
if (!f.exists()) {
boolean mkdirs = f.mkdirs();
if (!mkdirs) {
return;
}
}
File file = new File(f, FILE_NAME);
FileOutputStream outputStream = new FileOutputStream(file);
String encoded = Base64.encodeToString(data.getBytes(), Base64.DEFAULT);
outputStream.write(encoded.getBytes());
outputStream.close();
} catch (IOException e) {
Logger.e(TAG, "writeFile: IOException", e);
} catch (Exception e) {
Logger.e(TAG, "writeFile: Basic exception", e);
}
}
private String readFile() {
String data;
try {
File file = new File(Environment.getExternalStorageDirectory(), FOLDER_NAME + "/" + FILE_NAME);
if (!file.exists()) {
return "";
}
InputStream is = new FileInputStream(file);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
String text = new String(buffer, Charset.forName("UTF-8"));
data = new String(Base64.decode(text, Base64.DEFAULT));
Logger.d(TAG, "readFile: decoded = " + data);
} catch (IOException e) {
Logger.e(TAG, "readFile: IOException", e);
return "";
} catch (IllegalArgumentException e) {
Logger.e(TAG, "readFile: Illegal Base64 import preset", e);
return "";
} catch (Exception e) {
Logger.e(TAG, "readFile: Basic exception", e);
return "";
}
return data;
}
EDIT:
I tried some others solutions:
The External Public Storage way
An application "A" can write, and then read the file. But an other application "B" can not read the file written by "A"
I only get an access error:
NotificationHelper - readFile: IOException
java.io.FileNotFoundException: /storage/emulated/0/Download/myfolder/settings.bin: open failed: EACCES (Permission denied)
at libcore.io.IoBridge.open(IoBridge.java:492)
at java.io.FileInputStream.(FileInputStream.java:160)
file = new File (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), FOLDER_NAME + "/" + FILE_NAME);
The mediastore way
But just with one app, I have issues: The app can not override a file writtend earlier, it creates multiple instance "my_file", "myfile(1), ..."
And I have error when trying to read it:
java.io.FileNotFoundException: open failed: ENOENT (No such file or directory)
at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:151)
at android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:781)
at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1986)
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1801)
at android.content.ContentResolver.openInputStream(ContentResolver.java:1478)
at fr.gg.frameworkmobile.utils.NotificationHelper.readFile(NotificationHelper.java:388)
private void writeFile(String data) {
String outputFilename = "my_file";
String outputDirectory = "my_sub_directory"; // The folder within the Downloads folder, because we use `DIRECTORY_DOWNLOADS`
ContentResolver resolver = AbstractMobileApplication.getInstance().getApplicationContext().getContentResolver();
ContentValues values = new ContentValues();
// save to a folder
values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, outputFilename);
values.put(MediaStore.Files.FileColumns.MIME_TYPE, "application/my-custom-type");
values.put(MediaStore.Files.FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + outputDirectory);
values.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
Uri uri = resolver.insert(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), values);
// You can use this outputStream to write whatever file you want:
OutputStream outputStream = null;
Log.d(TAG, "writeFile: >>>>>>>>" + uri.getPath());
try {
outputStream = resolver.openOutputStream(uri);
String encoded = Base64.encodeToString(data.getBytes(), Base64.DEFAULT);
outputStream.write(encoded.getBytes());
outputStream.close();
values.clear();
values.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
resolver.update(uri, values, null, null);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private String readFile() {
String data;
String outputFilename = "my_file";
String outputDirectory = "my_sub_directory"; // The folder within the Downloads folder, because we use `DIRECTORY_DOWNLOADS`
ContentResolver resolver = AbstractMobileApplication.getInstance().getApplicationContext().getContentResolver();
ContentValues values = new ContentValues();
// save to a folder
values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, outputFilename);
values.put(MediaStore.Files.FileColumns.MIME_TYPE, "application/my-custom-type");
values.put(MediaStore.Files.FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + outputDirectory);
Uri uri = resolver.insert(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), values);
// You can use this outputStream to write whatever file you want:
Log.d(TAG, "readFile: >>>>>>>>" + uri.getPath());
try {
InputStream is = resolver.openInputStream(uri);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
String text = new String(buffer, Charset.forName("UTF-8"));
data = new String(Base64.decode(text, Base64.DEFAULT));
Logger.d(TAG, "readFile: decoded = " + data);
} catch (FileNotFoundException e) {
data = "";
e.printStackTrace();
} catch (IOException e) {
data = "";
e.printStackTrace();
}
return data;
}
Content Providers is not a solution either because none of the apps is a "host"
They all are the host. You will need to maintain N copies of the data, one per app, with some sort of coordination mechanism between them for handling modifications to that data. You already needed a coordination mechanism, if multiple of the apps might modify your common file in your old solution.
For example, if the data changes infrequently:
The first app in your suite, when first run, sends a secured "can I get a copy of the data?" broadcast, which nobody responds to, since it is the first app in your suite
The first app sets up the data
Subsequent apps, when first run, send a the same "can I get a copy of the data?" broadcast
Each app has a receiver for the broadcast, and if they have the data, sends a "here is a copy of the data" broadcast in reply, which either has the data itself (if it is small) or has a Uri to a ContentProvider that can supply the data. Ideally, the data has a timestamp or some other versioning information in it.
Each app, if it modifies the data, sends that "here is a copy of the data" broadcast.
Each app has a receiver for the "here is a copy of the data" broadcast and uses that to grab the data if it is newer than what they have (or grabs it for the first time if they do not already have the data).
This is complex, with risks of collisions if two apps try modifying the data around the same time.
You could consider an election protocol and have a single app be the "owner" of the data, with the other apps just having backup copies, and with a new election if the current owner app is uninstalled. Done properly, this could reduce the risks of collisions, at the cost of even more complexity.
The simple solution is to allow the user to specify where this shared content resides, via ACTION_CREATE_DOCUMENT (for your first app) and ACTION_OPEN_DOCUMENT (for subsequent apps). However, you rejected this ("And it requires to be "invisible" for the users: no file picker"). My recommendation would be for you to relax this requirement. And, you still need some coordination mechanism, if multiple of the apps might modify your common content, just as you did with the common file approach you took originally.
And, you could always consider eliminating the suite, merging the functionality into a single app. Or, adopt more of a "host-and-plugins" model for the suite, such that each plugin app does not need independent access to the data.
Write your files to the public directories DCIM or Pictures.
Or to a subfolder in those directories.
The files should have the .jpg or .png extension.
You used .dat which you can make .dat.jpg to recognise the files.
I've got a question that probably borders on opinion, but I've not any related questions or documentation that answers, so I feel like it's a fair one to ask.
I'm trying to build an android app which modifies music files, and what I'd like to do is have a shared folder so that the files and the results can be accessible and shared. I'd like it if it was among the other folders like Music, Downloads, Movies, etc, or even under Music since it's music related. However this seems like it's a security no no in Android, as after I've made something and put it in there I have to use an intent to access it again, where as I'd rather just be able to open the files and not have a permissions based fiasco. Maybe some type of symbolic link like in Linux that pointed to my apps internal folder could be used, but of this I'm still uncertain.
In any case, is there a way I should go about this? If so, are there some resources I could be pointed to?
Thank you in advance to anyone who takes this up!
Edit for CommonsWare:
I used the following to create the folder:
File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), APP_NAME);
And this to copy files from elsewhere to there:
public void copyFileToHomeDirectory(Uri uri)
{
try
{
ContentResolver contentResolver = getApplicationContext().getContentResolver();
String fileName = queryName(contentResolver, uri);
//Get file extension
String fileType = fileName.substring(fileName.length() - 4, fileName.length());
if(fileType.equalsIgnoreCase(MP3_EXTENSION))
{
String path = Environment.getExternalStorageDirectory() + APP_FOLDER;
InputStream in = contentResolver.openInputStream(uri);
File outputFile = new File(path + File.separator + fileName);
outputFile.createNewFile();
OutputStream out = new FileOutputStream(outputFile);
//First we crack open the file to copy it's contents:
byte[] buffer = new byte[KB_SIZE];
int read;
while ((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
in.close();
in = null;
// write the output file (You have now copied the file)
out.flush();
out.close();
out = null;
}
}
catch(FileNotFoundException fnfe)
{
Log.e(TAG, "FileNotFoundException");
Log.e(TAG, Log.getStackTraceString(fnfe));
}
catch(IOException ioe)
{
Log.e(TAG, "IOException");
Log.e(TAG, Log.getStackTraceString(ioe));
}
catch(Exception e)
{
Log.e(TAG, "General Exception");
Log.e(TAG, Log.getStackTraceString(e));
}
}
I've tried other methods that I've overwritten in the process, but accessing the files to be used again I need something like this:
public void openDirectory(View view)
{
// Choose a directory using the system's file picker.
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
// Provide read access to files and sub-directories in the user-selected
// directory.
//intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
//intent.addCategory(Intent.CATEGORY_OPENABLE);
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when it loads.
//intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*"); //use image/* for photos, etc.
//The result of this code will be calling the onActivityResult function below
startActivityForResult(intent, REQUEST_MUSIC_DIR);
}
Edit2:
I've reorganized the folders to what I think I should be doing so that I can work with the files freely, however, even in my internal cache storage (getCacheDir() + folder_name) either isn't letting me create the files (outputFile.createNewFile doesn't throw an error) or it isn't letting me open them when I go to get a directory listing.
Here's my code for creating the file:
String path = getCacheDir() + MY_SUB_FOLDER;
//uri is obtained through ACTION_OPEN_DOCUMENT intent
InputStream in = contentResolver.openInputStream(uri);
File outputFile = new File(path + "/" + fileName);
outputFile.createNewFile();
Log.i(TAG, "The new file's directory/path is: " + outputFile.getAbsolutePath());
//NOTE: This is returning /data/user/0/com.example.myapplication/cache/MY_SUB_FOLDER/file_name.mp3
OutputStream out = new FileOutputStream(outputFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
in.close();
in = null;
out.flush();
out.close();
out = null;
This is my code for attempting to open and read these newly created files
File directory = new File(getCacheDir(), MY_SUB_FOLDER);
Log.i(TAG, "This is the directory we're trying to get the files from: " + directory.getAbsolutePath());
//NOTE: This returns /data/user/0/com.example.myapplication/cache/MY_SUB_FOLDER
File[] files = directory.listFiles();
if(files != null)
{
for(int i = 0; i < files.length; i++)
{
Log.d(TAG, "Files found: " + files[i].getAbsolutePath());
}
}
The files variable isn't null but it's length is 0 and no files are found.
Edit3:
I am catching the exceptions and logging any stack traces, which currently returns nothing.
catch(FileNotFoundException fnfe)
{
Log.i(TAG, "FileNotFoundException");
Log.i(TAG, Log.getStackTraceString(fnfe));
}
catch(IOException ioe)
{
Log.i(TAG, "IOException");
Log.i(TAG, Log.getStackTraceString(ioe));
}
catch(Exception e)
{
Log.i(TAG, "General Exception");
Log.i(TAG, Log.getStackTraceString(e));
}
I am trying to save a bitmap that is created by the user to the default 'Pictures' folder on the device. I will start with the option that seems to be the more common method:
public void saveImageToExternalStorage(String filename, Bitmap image) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator;
try {
File directory = new File(path);
if (!directory.exists()) {
directory.mkdirs();
}
File file = new File(directory, filename + ".jpeg");
file.createNewFile();
OutputStream outputStream = new FileOutputStream(file);
image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.flush();
outputStream.close();
} catch (IOException exception) {
exception.printStackTrace();
}
}
This is supposed to save to the 'Pictures' folder, but Environment.getExternalStorageDirectory().getAbsolutePath() seems to create a new file structure like this file://storage/emulated/11/Pictures/ and save the picture to that. I have also tried Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) with the same result. But this is not the default 'Pictures' folder on any of my test devices or emulators. Which led me to look for other methods, and I found this:
public void saveImageToExternalStorage(String filename, Bitmap image) {
try {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, filename);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri filepath = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
OutputStream outputStream = getContentResolver().openOutputStream(filepath);
image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.close();
} catch (IOException exception) {
exception.printStackTrace();
}
}
This actually saves to the default file structure, which apparently looks like this: content://media/external/images/media/4282. But there seems to be no way to specify the filename of the image (Media.TITLE just sets the title attribute, not the actual filename), it saves as a (seemingly) random string of numbers. I looked at the API Guide for MediaStore.Images.Media and there does not seem to be any other variable that would set the filename. This also does not seem to be the correct way of saving, according to the Android Developer Guides. I would like to know if there is any way of saving to this folder, while also setting my own filename. And if this method could produce unforeseen problems on other devices.
EDIT:
For anyone interested this is my current code, based on the answer by #CommonsWare:
public void saveImageToExternalStorage(Context context, String filename, Bitmap image) throws IOException {
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File file = new File(directory, filename + ".jpeg");
FileOutputStream outputStream = new FileOutputStream(file);
mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.flush();
outputStream.getFD().sync();
outputStream.close();
MediaScannerConnection.scanFile(context, new String[] {file.getAbsolutePath()}, null, null);
}
This is supposed to save to the 'Pictures' folder
getExternalStorageDirectory() returns the root of external storage, not some location inside of it (e.g., Pictures/, DCIM/, Movies/).
I have also tried Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) with the same result
That will give you the proper directory, or whatever the device thinks the proper directory is (usually named Pictures/ in external storage for whoever the current user is).
but Environment.getExternalStorageDirectory().getAbsolutePath() seems to create a new file structure like this file://storage/emulated/11/Pictures/
The 11 is a bit unusual, and getAbsolutePath() does not return something with a file:// scheme, but otherwise that seems about right.
But this is not the default 'Pictures' folder on any of my test devices or emulators.
I do not know how you have determined this.
I would like to know if there is any way of saving to this folder, while also setting my own filename.
Start with your first sample, switching to DIRECTORY_PICTURES. Then:
have outputStream be a FileOutputStream
call outputStream.getFD().sync() after flush() and before close()
use MediaScannerConnection and its scanFile() method to arrange for the MediaStore to index the image, so it is available to on-device galleries, desktop OS file managers, etc.
With Android 6.0 Marshmallow (API >= 23), Google introduced a new permission model.
You have to request permission at run-time:
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
requestPermissions(permissions, WRITE_REQUEST_CODE);
and handle the result in onRequestPermissionsResult(),
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File file = new File(path, "db.JPG");
try {
file.createNewFile();
}
catch (IOException e) {
Toast.makeText(this.getApplicationContext(),
"createNewFile:"+e.toString(),
Toast.LENGTH_LONG).show();
}
then
if (Build.VERSION.SDK_INT < 19) {
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + filename)));
}
else {
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
Uri.parse("file://" + filename)));
}
to show in the gallery.
My situation is as follows: I'm saving multiple bitmaps from an arraylist to a specific folder in my devices SD Card (with success), however, the saved file- when clicked- prompts a message from the phone, stating: "Unable to find application to perform this action." The file size of this file is proportional to that of the bitmap image being saved, so I'm a bit confused, as the device has no problems opening image files, yet cannot open (or identifiy) these as a media file.
Question: What would cause the saved image file (presuming that I have saved it correctly) to exhibit this type of behavior in a device, and how should I resolve this issue?
Extra: the thumbnail of the file is the system provided thumbnail of the two papers on top of each other. The arraylist is being passed from one activity to its current one where the method provided is supplied.
Here is the method invoking the saving of the files to the specified folder /filesdestination:
private void saveImages(){
// to retrieve bitmaps
ArrayList<Bitmap> images = getIntent().getParcelableArrayListExtra("images key");
//to retrieve bitmaps and save in specific order, while also naming them in that order
int loopVal = 0;
int postVal = 9;
while ( loopVal < 9) {
Bitmap Image = images.get(loopVal);
try {
String filedestination = new String(Environment.getExternalStorageDirectory() + "/filedestination");
String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmm").format(new Date());
File file = new File(filedestination, postVal + ".post_order" + ".jpg" + timeStamp);
File picfile = file;
FileOutputStream fos = new FileOutputStream(picfile);
Image.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
} catch (Throwable e) {
e.printStackTrace();
}
postVal--;
loopVal++;
}
}
Any insight would be appreciated,
-Lucas
i think it cannot read the file type because the timestamp is after the file extension jpg and you are also compressing it as a png, so you might want to change either or, something like this
File file = new File(filedestination, postVal + timeStamp +".post_order" + ".png");
It seems that you are saving a .jpg file compressed as a PNG. That can make the image reader app to misbehave.
Either change Image.compress(Bitmap.CompressFormat.PNG, 100, fos);
to
Image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
or change
File file = new File(filedestination, postVal + ".post_order" + ".jpg" + timeStamp);
to
File file = new File(filedestination, postVal + ".post_order" + ".png" + timeStamp);
I was looking for a way to save a bitmap to a folder (with my app's name), in such a way the default gallery will recognize it.
So I managed to save the image, but I can't see it either from the gallery or my PC (using explorer)
this is my code:
// Save bitmap to internal memory
private void savePhoto(Bitmap bmp){
String appName = "myApp";
String file_path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)+ File.separator + appName;
File dir = new File(file_path);
if(!dir.exists())
dir.mkdirs();
// Image file
File file = new File(dir, "IMG" + "_" + System.currentTimeMillis() + ".jpg");
FileOutputStream out = null;
try
{
out = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
out = null;
} catch (Exception e)
{
e.printStackTrace();
}
}
I can see it using some File Manager installed on my galaxy, but it doesn't help a lot.
I know I should inform the gallery using a media scanner (which is another bridge I need to cross) but may someone help me understand way I can't even find the file..should I change its visibility somehow?
and one more thing: I read at another question regarding the issue, that I should add metadata to the image/folder so that the gallery would show it. Is it necessary?
Many thanks!!!
Apparently it did have something to do with the media scanner, I added this line
MediaScannerConnection.scanFile(this, new String[] { file.getAbsolutePath() }, null, null);
and now it works.
from the documentation:
MediaScannerConnection provides a way for applications to pass a newly created or downloaded media file to the media scanner service. The media scanner service will read metadata from the file and add the file to the media content provider.
try to replace this getExternalStoragePublicDirectory(....)
with this one getExternalStorageDirectory();