Android mediaScannerConnection.scanFile failing to refresh images in gallery - android

Should I be using something other than the MediaScannerConnection.scanFile method to refresh the gallery?
After saving a new jpg I run media scanner to refresh the gallery app like so
MediaScannerConnection.scanFile(this,
new String[] { fullname }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.d("ExternalStorage", "#### Scanned " + path + ":");
Log.d("ExternalStorage", "#### -> uri=" + uri);
}
});
The output of the log shows the following correct output
#### Scanned /data/data/com.mypackage/files/skit_106_01.jpg:
#### -> uri=content://media/external/images/media/95
The gallery app shows no media available
This code has been working perfectly for some time now. It was only when I created an Android avd against version 4.4.2 for further testing that the problem has surfaced.
The code I have seems to be the recommended way of refreshing the gallery app according to Androids documentation so maybe this issue is related to the way I am saving the file, the code for which is as follows.
UPDATE
The code checks for external storage availability and will write to external storage and if external storage is not available it will write the file to internal storage.
private void doSave(String fname, boolean doShare) {
fname = "skit_"+mCurrentSkitId +
"_"+mSkitManager.getCurrentFrameCount(
mCurrentSkitId)+1;
Log.d(TAG, "#### doSave fName = " + fname + " Current skit id = " + mCurrentSkitId);
CharSequence text = getResources().getString(R.string.saved_as)
+ " " + fname;
try {
Bitmap b = mMainView.getSaveBitmap();
if (b == null) {
text = getResources().getString(R.string.save_fail_1);
;
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
return;
}
fname = FileUtils.replaceInvalidFileNameChars(fname);
String value = fname;
File folder = FileUtils.getWritableFolder(this);
/*
* String folder =
* Environment.getExternalStorageDirectory().toString() +
* "/Pictures"; try { folder =
* Environment.getExternalStoragePublicDirectory
* (Environment.DIRECTORY_PICTURES).toString(); } catch
* (NoSuchFieldError e) {
*
* }
*/
String ext = ".jpg";
if (mPrefs.getString("format", "JPG").equals("PNG"))
ext = ".png";
String fullname = folder.getAbsolutePath() + File.separator + value
+ ext;
Map<String, String> hm = new HashMap<String, String>();
hm.put("filename", fullname);
FileOutputStream fos;
if (folder == getFilesDir())
fos = openFileOutput(value + ext, Context.MODE_WORLD_WRITEABLE);
else {
File f2 = new File(fullname);
fos = new FileOutputStream(f2);
}
b.compress(CompressFormat.JPEG, 95, fos);
fos.close();
String[] str = new String[1];
str[0] = fullname;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.FROYO) {
MediaScannerConnection.scanFile(this,
new String[] { fullname }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.d("ExternalStorage", "#### Scanned " + path + ":");
Log.d("ExternalStorage", "#### -> uri=" + uri);
}
});
}
text = text + value + ext + " "
+ getResources().getString(R.string.saved_end);
;
mLastSaveName = value;
setDetailTitle();
mSkitManager.createFrame(mCurrentSkitId, fullname);
} catch (Exception e) {
Map<String, String> hm = new HashMap<String, String>();
hm.put("text", e.toString());
e.printStackTrace();
text = getResources().getString(R.string.save_fail_2)
+ e.toString();
} catch (Error e) {
Map<String, String> hm = new HashMap<String, String>();
hm.put("text", e.toString());
e.printStackTrace();
text = getResources().getString(R.string.save_fail_2)
+ e.toString();
}
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
The code that does the check for external storage availability looks like this
public static File getWritableFolder(Context context) {
File folder = context.getFilesDir();
if (externalStorageAvailable()) {
try {
folder = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (!folder.exists() || !folder.canWrite()) {
folder = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
}
if (!folder.exists() || !folder.canWrite()) {
folder = Environment.getExternalStorageDirectory();
}
} catch (Exception e) {
folder = Environment.getExternalStorageDirectory();
} catch (Error e) {
folder = Environment.getExternalStorageDirectory();
}
if (!folder.exists() || !folder.canWrite()) {
folder = context.getFilesDir();
}
}
return folder;
}
private static boolean externalStorageAvailable() {
boolean mExternalStorageAvailable;
boolean mExternalStorageWriteable;
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
// We can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
// We can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Something else is wrong. It may be one of many other states, but
// all we need
// to know is we can neither read nor write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
return mExternalStorageAvailable && mExternalStorageWriteable;
}
If anyone is able to pick holes in any of the above that might help to solve this issue then that would be great.

i was having mixed results with MediaScannerConnection so i used the sendBroadcast method instead. I do not know if the sendBroadcast method is not standard/should not be used but it works for me.
public void galleryAddPic(File currentPhotoPath) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(currentPhotoPath);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
Also regarding the MediaScannerConnection : https://stackoverflow.com/a/4825615/1497188

Related

Firebase Storage download file to Pictures or Movie folder in Android Q

I was able to download a file from Firebase Storage to storage/emulated/0/Pictures which is a default folder for picture that is being used by most popular app as well such as Facebook or Instagram. Now that Android Q has a lot of behavioral changes in storing and accessing a file, my app no longer be able to download a file from the bucket when running in Android Q.
This is the code that write and download the file from the Firebase Storage bucket to a mass storage default folders like Pictures, Movies, Documents, etc. It works on Android M but on Q it will not work.
String state = Environment.getExternalStorageState();
String type = "";
if (downloadUri.contains("jpg") || downloadUri.contains("jpeg")
|| downloadUri.contains("png") || downloadUri.contains("webp")
|| downloadUri.contains("tiff") || downloadUri.contains("tif")) {
type = ".jpg";
folderName = "Images";
}
if (downloadUri.contains(".gif")){
type = ".gif";
folderName = "Images";
}
if (downloadUri.contains(".mp4") || downloadUri.contains(".avi")){
type = ".mp4";
folderName = "Videos";
}
//Create a path from root folder of primary storage
File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + Environment.DIRECTORY_PICTURES + "/MY_APP_NAME");
if (Environment.MEDIA_MOUNTED.equals(state)){
try {
if (dir.mkdirs())
Log.d(TAG, "New folder is created.");
}
catch (Exception e) {
e.printStackTrace();
Crashlytics.logException(e);
}
}
//Create a new file
File filePath = new File(dir, UUID.randomUUID().toString() + type);
//Creating a reference to the link
StorageReference httpsReference = FirebaseStorage.getInstance().getReferenceFromUrl(download_link_of_file_from_Firebase_Storage_bucket);
//Getting the file from the server
httpsReference.getFile(filePath).addOnProgressListener(taskSnapshot ->
showProgressNotification(taskSnapshot.getBytesTransferred(), taskSnapshot.getTotalByteCount(), requestCode)
);
With this it will download the files from server to your device storage with path storage/emulated/0/Pictures/MY_APP_NAME but with Android Q this will no longer work as many APIs became deprecated like Environment.getExternalStorageDirectory().
Using android:requestLegacyExternalStorage=true is a temporary solution but will no longer work soon on Android 11 and above.
So my question is how can I download files using Firebase Storage APIs on default Picture or Movie folder that is in the root instead of Android/data/com.myapp.package/files.
Does MediaStore and ContentResolver has solution for this? What changes do I need to apply?
Here is my solution:
Download file with Glide
public void downloadFile(Context context, String url){
String mimeType = getMimeType(url);
Glide.with(context).asFile().load(url).listener(new RequestListener<File>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(File resource, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
saveFile(context,resource, mimeType);
return false;
}
}).submit();
}
Get file mimeType
public static String getMimeType(String url) {
String mimeType = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return mimeType;
}
And save file to external storage
public Uri saveFile(Context context, File file, String mimeType) {
String folderName = "Pictures";
String extension = ".jpg";
if(mimeType.endsWith("gif")){
extension = ".gif";
}else if(mimeType.startsWith("image/")){
extension = ".jpg";
}else if(mimeType.startsWith("video/")){
extension = ".mp4";
folderName = "Movies";
}else if(mimeType.startsWith("audio/")){
extension = ".mp3";
folderName = "Music";
}else if(mimeType.endsWith("pdf")){
extension = ".pdf";
folderName = "Documents";
}
if (android.os.Build.VERSION.SDK_INT >= 29) {
ContentValues values = new ContentValues();
values.put(MediaStore.Files.FileColumns.MIME_TYPE, mimeType);
values.put(MediaStore.Files.FileColumns.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Files.FileColumns.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Files.FileColumns.RELATIVE_PATH, folderName);
values.put(MediaStore.Files.FileColumns.IS_PENDING, true);
values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, "file_" + System.currentTimeMillis() + extension);
Uri uri = null;
if(mimeType.startsWith("image/")){
uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}else if(mimeType.startsWith("video/")){
uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
}else if(mimeType.startsWith("audio/")){
uri = context.getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
}else if(mimeType.endsWith("pdf")){
uri = context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
}
if (uri != null) {
try {
saveFileToStream(context.getContentResolver().openInputStream(Uri.fromFile(file)), context.getContentResolver().openOutputStream(uri));
values.put(MediaStore.Video.Media.IS_PENDING, false);
context.getContentResolver().update(uri, values, null, null);
return uri;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
return null;
}
save file to stream
private void saveFileToStream(InputStream input, OutputStream outputStream) throws IOException {
try {
try (OutputStream output = outputStream ){
byte[] buffer = new byte[4 * 1024]; // or other buffer size
int read;
while ((read = input.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
output.flush();
} catch (IOException e) {
e.printStackTrace();
}
} finally {
input.close();
}
}
I tried with emulator Android 29. It works fine.
Note : getExternalStorageDirectory() was deprecated in API level 29. To improve user privacy, direct access to shared/external storage devices is deprecated. When an app targets Build.VERSION_CODES.Q, the path returned from this method is no longer directly accessible to apps. Apps can continue to access content stored on shared/external storage by migrating to alternatives such as Context#getExternalFilesDir(String), MediaStore, or Intent#ACTION_OPEN_DOCUMENT.
==NEW ANSWER==
If you wanted to monitor the download progress you can use getStream() of FirebaseStorage SDK like this:
httpsReference.getStream((state, inputStream) -> {
long totalBytes = state.getTotalByteCount();
long bytesDownloaded = 0;
byte[] buffer = new byte[1024];
int size;
while ((size = inputStream.read(buffer)) != -1) {
stream.write(buffer, 0, size);
bytesDownloaded += size;
showProgressNotification(bytesDownloaded, totalBytes, requestCode);
}
// Close the stream at the end of the Task
inputStream.close();
stream.flush();
stream.close();
}).addOnSuccessListener(taskSnapshot -> {
showDownloadFinishedNotification(downloadedFileUri, downloadURL, true, requestCode);
//Mark task as complete so the progress download notification whether success of fail will become removable
taskCompleted();
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, false);
resolver.update(uriResolve, contentValues, null, null);
}).addOnFailureListener(e -> {
Log.w(TAG, "download:FAILURE", e);
try {
stream.flush();
stream.close();
} catch (IOException ioException) {
ioException.printStackTrace();
FirebaseCrashlytics.getInstance().recordException(ioException);
}
FirebaseCrashlytics.getInstance().recordException(e);
//Send failure
showDownloadFinishedNotification(null, downloadURL, false, requestCode);
//Mark task as complete
taskCompleted();
});
==OLD ANSWER==
Finally after tons of hours I manage to do it but using .getBytes(maximum_file_size) instead of .getFile(file_object) as last resort.
Big big thanks to #Kasim for bringing up the idea of getBytes(maximum_file_size) with also sample code working with InputStream and OutputStream.By searching across S.O topic related to I/O also is a big help here and here
The idea here is .getByte(maximum_file_size) will download the file from the bucket and return a byte[] on its addOnSuccessListener callback. The downside is you must specify the file size you allowed to download and no download progress computation can be done AFAIK unless you do some work with outputStream.write(0,0,0); I tried to write it chunk by chunk like here but the solution is throwing ArrayIndexOutOfBoundsException since you must be accurate on working with index into an array.
So here is the code that let you saved file from your Firebase Storage Bucket to your device default directories: storage/emulated/0/Pictures, storage/emulated/0/Movies, storage/emulated/0/Documents, you name it
//Member variable but depending on your scope
private ByteArrayInputStream inputStream;
private Uri downloadedFileUri;
private OutputStream stream;
//Creating a reference to the link
StorageReference httpsReference = FirebaseStorage.getInstance().getReferenceFromUrl(downloadURL);
Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String type = "";
String mime = "";
String folderName = "";
if (downloadURL.contains("jpg") || downloadURL.contains("jpeg")
|| downloadURL.contains("png") || downloadURL.contains("webp")
|| downloadURL.contains("tiff") || downloadURL.contains("tif")) {
type = ".jpg";
mime = "image/*";
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
folderName = Environment.DIRECTORY_PICTURES;
}
if (downloadURL.contains(".gif")){
type = ".gif";
mime = "image/*";
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
folderName = Environment.DIRECTORY_PICTURES;
}
if (downloadURL.contains(".mp4") || downloadURL.contains(".avi")){
type = ".mp4";
mime = "video/*";
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
folderName = Environment.DIRECTORY_MOVIES;
}
if (downloadURL.contains(".mp3")){
type = ".mp3";
mime = "audio/*";
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
folderName = Environment.DIRECTORY_MUSIC;
}
final String relativeLocation = folderName + "/" + getString(R.string.app_name);
final ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, UUID.randomUUID().toString() + type);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mime); //Cannot be */*
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);
ContentResolver resolver = getContentResolver();
Uri uriResolve = resolver.insert(contentUri, contentValues);
try {
if (uriResolve == null || uriResolve.getPath() == null) {
throw new IOException("Failed to create new MediaStore record.");
}
stream = resolver.openOutputStream(uriResolve);
//This is 1GB change this depending on you requirements
httpsReference.getBytes(1024 * 1024 * 1024)
.addOnSuccessListener(bytes -> {
try {
int bytesRead;
inputStream = new ByteArrayInputStream(bytes);
while ((bytesRead = inputStream.read(bytes)) > 0) {
stream.write(bytes, 0, bytesRead);
}
inputStream.close();
stream.flush();
stream.close();
//FINISH
} catch (IOException e) {
closeSession(resolver, uriResolve, e);
e.printStackTrace();
Crashlytics.logException(e);
}
});
} catch (IOException e) {
closeSession(resolver, uriResolve, e);
e.printStackTrace();
Crashlytics.logException(e);
}

Downloading and restore of sqlite database stored in google drive app folder

I have a database backup stored in app folder in the drive. Below is the code I have written.
public void startRestore(View view)
{
int EXTERNAL_WRITE_PERMISSION = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.M)
{
if(EXTERNAL_WRITE_PERMISSION != PackageManager.PERMISSION_GRANTED)
{
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE))
{
Snackbar.make(mLayout, "Write permission is required",
Snackbar.LENGTH_INDEFINITE).setAction("OK", new View.OnClickListener() {
#Override
public void onClick(View view) {
// Request the permission
ActivityCompat.requestPermissions(BackupActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_REQUEST_STORAGE);
}
}).show();
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_REQUEST_STORAGE);
}
}
}
if (backupExists())
{
Log.d("RESTORE: ", "Started restore");
final String driveFileID = sharedPreferences.getString("dbBackupDriveFileID", "");
final DriveFile driveFile = DriveId.decodeFromString(driveFileID).asDriveFile();
Log.d("RESTORE_FileID: ", driveFileID);
final Task<DriveContents> openFileTask = mDriveResourceClient.openFile(driveFile, DriveFile.MODE_READ_ONLY);
openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>()
{
#Override
public Task<Void> then(#NonNull Task<DriveContents> task) throws Exception
{
Log.d("RESTORE: ", "open File task");
DriveContents driveContents = task.getResult();
//TODO download file an add to database
InputStream inputStream = driveContents.getInputStream();
byte[] buf = new byte[8192];
int c = 0;
String baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
String fileName = DatabaseHelper.DATABASE_NAME;
Log.d("RESTORE: ", baseDir + "/" +fileName);
File f = new File(baseDir+File.pathSeparator+fileName);
if(f.canWrite())
{
Log.d("RESTORE: ", "File writable");
OutputStream outputStream = new FileOutputStream(f);
while ((c = inputStream.read(buf, 0, buf.length)) > 0)
{
outputStream.write(buf, 0, c);
outputStream.flush();
}
outputStream.close();
}
else
{
Log.d("RESTORE: ", "File not writable");
}
return mDriveResourceClient.discardContents(driveContents);
}
})
.addOnFailureListener(new OnFailureListener()
{
#Override
public void onFailure(#NonNull Exception e)
{
}
});
}
else
{
Toast.makeText(this, "Backup does not exists", Toast.LENGTH_SHORT).show();
}
}
In the above code the the control always reaches to Log.d("RESTORE: ", "File not writable");. I have the write permissions defined in the manifest and also runtime permission is granted. Also there is no error in the log.
Below is the backup function for reference.
public void startBackup(View view)
{
final ProgressDialog progressDialog = new ProgressDialog(this);
final File currentDB = this.getDatabasePath(DatabaseHelper.DATABASE_NAME);
Log.d("DATABASE: ", currentDB.getAbsolutePath());
Log.d("DATABASE: ", currentDB.getName());
progressDialog.setMessage("Backing Up!!!!");
progressDialog.show();
final Task<DriveFolder> appFolderTask = mDriveResourceClient.getAppFolder();
final Task<DriveContents> createContentsTask = mDriveResourceClient.createContents();
Tasks.whenAll(appFolderTask, createContentsTask)
.continueWithTask(new Continuation<Void, Task<DriveFile>>()
{
#Override
public Task<DriveFile> then(#NonNull Task<Void> task) throws Exception
{
DriveFolder parent = appFolderTask.getResult();
DriveContents contents = createContentsTask.getResult();
InputStream inputStream = null;
try
{
inputStream = new FileInputStream(currentDB);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
OutputStream outputStream = contents.getOutputStream();
int c = 0;
byte[] buf = new byte[8192];
if (inputStream != null)
{
while ((c = inputStream.read(buf, 0, buf.length)) > 0)
{
outputStream.write(buf, 0, c);
outputStream.flush();
}
outputStream.close();
}
else
{
Toast.makeText(BackupActivity.this, "Some error occurred", Toast.LENGTH_SHORT).show();
}
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setMimeType("application/x-sqlite3")
.setTitle(currentDB.getName())
.build();
return mDriveResourceClient.createFile(parent, changeSet, contents);
}
})
.addOnSuccessListener(this, new OnSuccessListener<DriveFile>() {
#Override
public void onSuccess(DriveFile driveFile)
{
progressDialog.dismiss();
String driveFileID = driveFile.getDriveId().encodeToString();
String dateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("dbBackupDriveFileID", driveFileID);
editor.putString("lastDbBackupTime", dateTime);
editor.apply();
Log.d("DRIVE_FILE", driveFileID);
String d = getString(R.string.last_backup) + dateTime;
textView.setText(d);
Toast.makeText(BackupActivity.this, "Backup Successful. File "+driveFile.getDriveId()
.encodeToString(), Toast.LENGTH_LONG).show();
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e)
{
progressDialog.dismiss();
Log.e("DRIVE ", "Unable to create file", e);
Toast.makeText(BackupActivity.this, "Unable to backup", Toast.LENGTH_SHORT).show();
}
});
}
Instead of using the File f = new File(baseDir+File.pathSeparator+fileName); I replaced the File usage with an FileOutputStream.
Modified part of the restore function:
DriveContents driveContents = task.getResult();
//TODO download file an add to database
InputStream inputStream = driveContents.getInputStream();
byte[] buf = new byte[8192];
int c = 0;
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
Log.d("RESTORE: ", "External DIR mounted");
String baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
String fileName = DatabaseHelper.DATABASE_NAME;
String fileFullName = baseDir + File.separator + fileName;
Log.d("RESTORE: ", fileFullName);
FileOutputStream outputStream;
outputStream = new FileOutputStream(fileFullName, false);
while ((c = inputStream.read(buf, 0, buf.length)) > 0) {
outputStream.write(buf, 0, c);
outputStream.flush();
}
outputStream.close();
}
else
{
Log.d("RESTORE: ", "External DIR not mounted");
}
This solved my issue.
The answer could be as simple as adding f.mkdirs() immediately after File f = new File(baseDir+File.pathSeparator+fileName); and before if(f.canWrite()).
However there are numerous reasons why canWrite can return false, So you should check the STATE (probably before you try canWrite)
Personally I utilise the following rather long winded code :-
class StoreData {
private String directory; //Note built internally and includes subdirectory
private String subdirectory;
private String filename;
private boolean mounted;
private boolean inerror;
private boolean fileexists;
private boolean direxists;
private long errorcode;
private ArrayList<String> errorlist = new ArrayList<>();
private ArrayList<File> otherfilesindirectory = new ArrayList<>();
// Need to be aware of the API
#SuppressWarnings("unused")
public static final int API_VERSION = Build.VERSION.SDK_INT;
private static final long UNMOUNTED = 1;
private static final long FILEIOERR = 2;
private static final long READERR = 4;
private static final String NEWLINE = "\r\n";
/**
* Sole Constructor for a StoreData object
* Note instantiating creates but the deletes a file, assuming that
* no prior errors left the instance in an unusable state (as initially set)
* Note instantiating, if existcheck (3rd param) is true, does not create
* and delete the file, rather it checks that the file exists
* typically for reading an existing file.
*
* #param subdirectory - Sub directory in which to create file
* #param filename - the file name where actual data will be stored
* #param existcheck - whether or not to check for the existence of the file
*
* Note!! existcheck, if true, will not try to create the file
*/
public StoreData(String subdirectory, #SuppressWarnings("SameParameterValue") String filename, boolean existcheck) {
fileexists = false;
direxists = false;
mounted = false;
inerror = false;
errorcode = 0;
this.directory = "";
this.subdirectory = subdirectory;
this.filename = filename;
// External Storage must be mounted.
String chkmnt = Environment.getExternalStorageState();
if(!(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))) {
switch (Environment.getExternalStorageState()) {
case Environment.MEDIA_SHARED : {
errorlist.add(
"Although External Storage is present." +
" It cannot be used as it's in use via USB." +
"\nDisconnect the USB cable and then try again."
);
break;
}
case Environment.MEDIA_REMOVED : {
errorlist.add(
"External Storage is not present." +
"\nInsert an SD Card."
);
break;
}
case Environment.MEDIA_EJECTING : {
errorlist.add(
"External Storage is being ejected." +
"\nRe-insert the SD Card."
);
break;
}
case Environment.MEDIA_NOFS : {
errorlist.add(
"External Storage is blank or does not have the correct" +
" filesystem present." +
"\nUse a valid SDCard."
);
break;
}
case Environment.MEDIA_BAD_REMOVAL : {
errorlist.add(
"External Storage was removed incorrectly." +
"\nRe-insert the SD Card, if this fails then" +
" try restarting the device."
);
break;
}
case Environment.MEDIA_CHECKING : {
errorlist.add(
"External Storage is unavailable as it is being checked." +
"\nTry again."
);
}
case Environment.MEDIA_MOUNTED_READ_ONLY : {
errorlist.add(
"External Storage is READ ONLY." +
"\nInsert an SD card that is not protected."
);
}
case Environment.MEDIA_UNKNOWN : {
errorlist.add(
"External Storage state is UNKNOWN." +
"\ntry a different SD Card."
);
}
case Environment.MEDIA_UNMOUNTABLE : {
errorlist.add(
"External Storage cannot be mounted." +
"\nTry re-inserting the SD Card or using a different SD Card."
);
}
case Environment.MEDIA_UNMOUNTED : {
errorlist.add(
"External Storage is not mounted." +
"\nTry re-inserting the SD Card or using a different SD Card."
);
}
default: {
errorlist.add(
"Undefined Error"
);
}
}
this.errorcode = UNMOUNTED;
return;
} else {
this.mounted = true;
}
// Get the required directory and specified sub directory
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),subdirectory);
this.directory = dir.getPath();
// If existcheck is true check that the directories exist
if (existcheck && dir.exists()) {
direxists = true;
}
// If the directories do not exist try to create them and redo check
// Note! existcheck is more for file level so always try to create
// directories
else {
boolean x = dir.mkdirs();
if(dir.exists()) {
direxists = true;
}
}
if(direxists) {
refreshOtherFilesInDirectory();
}
// File level
File f = new File(directory,filename);
// Check if the file exists if requested and return if it does
if (existcheck) {
if (f.exists()) {
fileexists = true;
}
return;
}
try {
boolean x = f.createNewFile();
}
catch (IOException e) {
e.printStackTrace();
this.errorcode = FILEIOERR ;
errorlist.add(
"File Error " + e.getMessage()
);
return;
}
boolean x = f.delete();
}
#SuppressWarnings({"ConstantConditions", "UnusedReturnValue"})
public boolean refreshOtherFilesInDirectory() {
boolean rv = true;
File dir = new File(directory);
File[] dirlist = dir.listFiles();
if((dirlist.length) > 0) {
// Sort the list
Arrays.sort(dirlist, new Comparator<File>() {
#Override
public int compare(File object1, File object2) {
return object1.getName().compareTo(object2.getName());
}
});
otherfilesindirectory.clear();
for (File aDirlist : dirlist) {
if (!(aDirlist.getName().equals(this.filename))) {
otherfilesindirectory.add(aDirlist);
}
}
}
return rv;
}
/**
* writeData - Write data to the file from String Arraylist passed
* Note!! a linefeed is added to each string
* #param datatowrite - strng ArrayList holding data to write
* #return result flag
*/
#SuppressWarnings("unused")
public boolean writeData(ArrayList<String> datatowrite) {
// Check that this instance is OK
if (!this.isOK()) {
this.errorlist.add(
"\nError prior to call to writeData method."
);
return false;
}
// Prepare to write
this.errorlist.clear();
File f = new File(this.directory,File.separator + this.filename);
try {
boolean x = f.createNewFile();
FileOutputStream fos = new FileOutputStream(f);
OutputStreamWriter osw = new OutputStreamWriter(fos);
for (int i = 0; i < datatowrite.size(); i++) {
osw.write(datatowrite.get(i) + NEWLINE);
}
osw.flush();
osw.close();
fos.flush();
fos.close();
this.fileexists = true;
}
catch (IOException e) {
e.printStackTrace();
this.errorcode = FILEIOERR;
errorlist.add(
"File Error " + e.getMessage()
);
return false;
}
return true;
}
/**
* readData - Populate a String ArrayList from the data in the file
* Note! Assumes linefeeds in the file separate strings of data
* #return - result flag
*/
#SuppressWarnings("unused")
public ArrayList<String> readData() {
ArrayList<String> rv = new ArrayList<>();
if(!this.isOKandExists()) {
this.errorlist.add(
"\nError prior to call to readData method or the file doesn't exist."
);
this.errorcode = READERR;
return rv;
}
this.errorlist.clear();
File f = new File(this.directory,File.separator + this.filename);
try {
FileInputStream fis = new FileInputStream(f);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line;
while((line = br.readLine()) != null) {
rv.add(line);
}
}
catch (IOException e) {
e.printStackTrace();
this.errorcode = READERR;
errorlist.add(
"File Read Error" + e.getMessage()
);
return rv;
}
return rv;
}
/**
* isOK - Check if object is usable
* #return true if OK else false
*/
public boolean isOK() {
return !(errorcode != 0 || !mounted || inerror);
}
/**
* exists = Check if the file exists
* #return - Result of check
*/
#SuppressWarnings("unused")
public boolean exists() {
return this.fileexists;
}
public boolean isOKandExists() {
return this.isOK() && this.fileexists;
}
/**
* Return a string displaying the instances details
* #return string displaying object's members
*/
public String Display() {
String rv;
rv = "Directory path=" + directory + "\n" +
"SubDirectory=" + subdirectory + "\n" +
"Filename=" + filename + "\n" +
"Mounted =" + Boolean.toString(mounted) + "\n" +
"Directory Exists=" + Boolean.toString(this.direxists) + "\n" +
"File Exists=" + Boolean.toString(this.fileexists) + "\n" +
"In Error=" + Boolean.toString(inerror) + "\n" +
"Last Error Code=" + Long.toString(errorcode);
return rv;
}
#SuppressWarnings("unused")
public String DisplayWithOtherFiles() {
String rv;
rv = this.Display() + "\nOther Files in Directory (" + this.directory + ") ";
for(int i = 0; i < otherfilesindirectory.size(); i++) {
rv = rv + "\n\t" + otherfilesindirectory.get(i).getName();
}
return rv;
}
/**
* Retrieve generated error messages. if any
* #return sting comprised of all error messages generated
*/
#SuppressWarnings("unused")
public String getErrorMessages() {
String rv = "";
for(int i = 0; i < errorlist.size(); i++) {
rv = rv + errorlist.get(i);
}
return rv;
}
/**
* Method: getDirectory - get the backup directory as a String
* #return Directory as a String
*/
public String getDirectory() {
return this.directory;
}
/**
* Method: getFilename - get the filename of the object as a String
* #return Filename as a String
*/
#SuppressWarnings("unused")
public String getFilename() {
return this.filename;
}
/**
* Method: getSubDirectory - get the sub-directory as a string
* #return Sub-Directory as a String
*/
#SuppressWarnings("unused")
public String getSubDirectory() {
return this.subdirectory;
}
/**
* Method: getFilesInDirectory - return an ArrayList of type File
* #return List of files in the directory as an ArrayList<File>
*/
public ArrayList<File> getFilesInDirectory() {
return this.otherfilesindirectory;
}
}

Save Images, Display in Gallery App

I'm currently working on an App that receives multiple images via socket. To save them, I wrote the following methods:
public static boolean saveTempImageToGallery(Context c) {
try {
FileInputStream fis = c.openFileInput(Settings.TEMP_PHOTO_STORAGE);
// create name of file: [date]-[time]-baby
final String tFilename = new SimpleDateFormat("dd-MM-yyyy_hh-mm-ss")
.format(new Date()) + ".png";
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
Log.d(TAG, "External storage available.");
// sd card available
File dir = getExternalStorageDir("Photos");
if (dir.mkdirs() || dir.isDirectory()) {
Log.i(TAG, "Directory: "+dir.getAbsolutePath());
File newImage = new File(dir, tFilename);
if (newImage.createNewFile() && newImage.isFile()) {
Log.i(TAG, "Saving image to "+newImage.getAbsolutePath());
final Bitmap bmp = BitmapFactory.decodeStream(fis);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// compress image to png
bmp.compress(Bitmap.CompressFormat.PNG, 40, baos);
FileOutputStream fo = new FileOutputStream(newImage);
fo.write(baos.toByteArray());
fo.close();
Log.i(TAG, "Image saved!");
return true;
}
} else {
Log.d(TAG, "Could not create directory.");
}
} else {
Log.d(TAG, "External storage not available.");
}
} catch (Exception e) {
}
return false;
}
public static File getExternalStorageDir() {
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File(sdCard.getAbsolutePath() + "/"
+ Settings.EXT_STORAGE_DIRECTORY);
return dir;
}
public static File getExternalStorageDir(String subdir) {
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File(sdCard.getAbsolutePath() + "/"
+ Settings.EXT_STORAGE_DIRECTORY + "/" + subdir);
return dir;
}
After saving them, I'd like to offer the user the possibility to view them in the default gallery app. After reading some post, I adapted the following code:
MediaScannerConnectionClient mScanClient = new MediaScannerConnectionClient() {
#Override
public void onScanCompleted(String path, Uri uri) {
try {
Log.d("onScanCompleted", uri + "success");
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
startActivity(intent);
}
} finally {
if (mScanCon != null)
mScanCon.disconnect();
mScanCon = null;
}
}
#Override
public void onMediaScannerConnected() {
Log.i(TAG, "Media Scan Connected.");
String[] files = Support.getExternalStorageDir("Photos")
.list();
Log.i(TAG,
Support.getExternalStorageDir("Photos").list().length
+ " elements in dir.");
if (files.length > 0) {
for (String cur : files) {
if (cur.equals(".") || cur.equals(".."))
continue;
Log.i(TAG, "Using "
+ cur
+ " to scan stuff. "
+ Support.getExternalStorageDir("Photos")
.getAbsolutePath() + "/" + cur);
Log.i(TAG, "Not using "
+ cur
+ " to scan stuff. "
+ Support.getExternalStorageDir("Photos")
.toString() + "/" + cur);
mScanCon.scanFile(
Support.getExternalStorageDir("Photos")
.getAbsolutePath() + "/" + cur,
"image/*");
break;
}
} else {
Toast.makeText(getApplicationContext(),
"No images available.", Toast.LENGTH_LONG)
.show();
}
}
};
if (mScanCon != null)
mScanCon.disconnect();
mScanCon = new MediaScannerConnection(getApplicationContext(),
mScanClient);
mScanCon.connect();
Weird thing: Seems like onMediaScannerConnected is never fired - anyone has an idea? I've been searching the web and stackoverflow for the last hour..
Thank you.
You really don't have to connect to the media scanner to start a scan, you can use this static method instead.
MediaScannerConnection.scanFile(context, new String[] {dir.getAbsolutePath()}, null, null);
edit:
Uri uri = Uri.parse(filePath);
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(uri, "image/*");
startActivity(i);

Android saving file to external storage

I have a little issue with creating a directory and saving a file to it on my android application. I'm using this piece of code to do this :
String filename = "MyApp/MediaTag/MediaTag-"+objectId+".png";
File file = new File(Environment.getExternalStorageDirectory(), filename);
FileOutputStream fos;
fos = new FileOutputStream(file);
fos.write(mediaTagBuffer);
fos.flush();
fos.close();
But it's throwing an exception :
java.io.FileNotFoundException: /mnt/sdcard/MyApp/MediaCard/MediaCard-0.png (No such file or directory)
on that line : fos = new FileOutputStream(file);
If I set the filename to : "MyApp/MediaTag-"+objectId+" it's working, but If I try to create and save the file to an another directory it's throwing the exception. So any ideas what I'm doing wrong?
And another question: Is there any way to make my files private in external storage so user can't see them in gallery, only if he connect his device as Disk Drive?
Use this function to save your bitmap in SD card
private void SaveImage(Bitmap finalBitmap) {
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root + "/saved_images");
if (!myDir.exists()) {
myDir.mkdirs();
}
Random generator = new Random();
int n = 10000;
n = generator.nextInt(n);
String fname = "Image-"+ n +".jpg";
File file = new File (myDir, fname);
if (file.exists ())
file.delete ();
try {
FileOutputStream out = new FileOutputStream(file);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
and add this in manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
EDIT: By using this line you will be able to see saved images in the gallery view.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + Environment.getExternalStorageDirectory())));
look at this link also http://rajareddypolam.wordpress.com/?p=3&preview=true
The code presented by RajaReddy no longer works for KitKat
This one does (2 changes):
private void saveImageToExternalStorage(Bitmap finalBitmap) {
String root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
File myDir = new File(root + "/saved_images");
myDir.mkdirs();
Random generator = new Random();
int n = 10000;
n = generator.nextInt(n);
String fname = "Image-" + n + ".jpg";
File file = new File(myDir, fname);
if (file.exists())
file.delete();
try {
FileOutputStream out = new FileOutputStream(file);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
}
catch (Exception e) {
e.printStackTrace();
}
// 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);
}
});
}
Update 2018, SDK >= 23.
Now you should also check if the user has granted permission to external storage by using:
public boolean isStoragePermissionGranted() {
String TAG = "Storage Permission";
if (Build.VERSION.SDK_INT >= 23) {
if (this.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG, "Permission is granted");
return true;
} else {
Log.v(TAG, "Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG,"Permission is granted");
return true;
}
}
public void saveImageBitmap(Bitmap image_bitmap, String image_name) {
String root = Environment.getExternalStorageDirectory().toString();
if (isStoragePermissionGranted()) { // check or ask permission
File myDir = new File(root, "/saved_images");
if (!myDir.exists()) {
myDir.mkdirs();
}
String fname = "Image-" + image_name + ".jpg";
File file = new File(myDir, fname);
if (file.exists()) {
file.delete();
}
try {
file.createNewFile(); // if file already exists will do nothing
FileOutputStream out = new FileOutputStream(file);
image_bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
MediaScannerConnection.scanFile(this, new String[]{file.toString()}, new String[]{file.getName()}, null);
}
}
and of course, add in the AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
You need a permission for this
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
and method:
public boolean saveImageOnExternalData(String filePath, byte[] fileData) {
boolean isFileSaved = false;
try {
File f = new File(filePath);
if (f.exists())
f.delete();
f.createNewFile();
FileOutputStream fos = new FileOutputStream(f);
fos.write(fileData);
fos.flush();
fos.close();
isFileSaved = true;
// File Saved
} catch (FileNotFoundException e) {
System.out.println("FileNotFoundException");
e.printStackTrace();
} catch (IOException e) {
System.out.println("IOException");
e.printStackTrace();
}
return isFileSaved;
// File Not Saved
}
Make sure your app has the proper permissions to be allowed to write to external storage: http://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE
It should look something like this in your manifest file:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Try This :
Check External storage device
Write File
Read File
public class WriteSDCard extends Activity {
private static final String TAG = "MEDIA";
private TextView tv;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView) findViewById(R.id.TextView01);
checkExternalMedia();
writeToSDFile();
readRaw();
}
/**
* Method to check whether external media available and writable. This is
* adapted from
* http://developer.android.com/guide/topics/data/data-storage.html
* #filesExternal
*/
private void checkExternalMedia() {
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// Can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// Can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Can't read or write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
tv.append("\n\nExternal Media: readable=" + mExternalStorageAvailable
+ " writable=" + mExternalStorageWriteable);
}
/**
* Method to write ascii text characters to file on SD card. Note that you
* must add a WRITE_EXTERNAL_STORAGE permission to the manifest file or this
* method will throw a FileNotFound Exception because you won't have write
* permission.
*/
private void writeToSDFile() {
// Find the root of the external storage.
// See http://developer.android.com/guide/topics/data/data-
// storage.html#filesExternal
File root = android.os.Environment.getExternalStorageDirectory();
tv.append("\nExternal file system root: " + root);
// See
// http://stackoverflow.com/questions/3551821/android-write-to-sd-card-folder
File dir = new File(root.getAbsolutePath() + "/download");
dir.mkdirs();
File file = new File(dir, "myData.txt");
try {
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
pw.println("Hi , How are you");
pw.println("Hello");
pw.flush();
pw.close();
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.i(TAG, "******* File not found. Did you"
+ " add a WRITE_EXTERNAL_STORAGE permission to the manifest?");
} catch (IOException e) {
e.printStackTrace();
}
tv.append("\n\nFile written to " + file);
}
/**
* Method to read in a text file placed in the res/raw directory of the
* application. The method reads in all lines of the file sequentially.
*/
private void readRaw() {
tv.append("\nData read from res/raw/textfile.txt:");
InputStream is = this.getResources().openRawResource(R.raw.textfile);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr, 8192); // 2nd arg is buffer
// size
// More efficient (less readable) implementation of above is the
// composite expression
/*
* BufferedReader br = new BufferedReader(new InputStreamReader(
* this.getResources().openRawResource(R.raw.textfile)), 8192);
*/
try {
String test;
while (true) {
test = br.readLine();
// readLine() returns null if no more lines in the file
if (test == null) break;
tv.append("\n" + " " + test);
}
isr.close();
is.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
tv.append("\n\nThat is all");
}
}
I have created an AsyncTask for saving bitmaps.
public class BitmapSaver extends AsyncTask<Void, Void, Void>
{
public static final String TAG ="BitmapSaver";
private Bitmap bmp;
private Context ctx;
private File pictureFile;
public BitmapSaver(Context paramContext , Bitmap paramBitmap)
{
ctx = paramContext;
bmp = paramBitmap;
}
/** Create a File for saving an image or video */
private File getOutputMediaFile()
{
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.
File mediaStorageDir = new File(Environment.getExternalStorageDirectory()
+ "/Android/data/"
+ ctx.getPackageName()
+ "/Files");
// This location works best if you want the created images to be shared
// between applications and persist after your app has been uninstalled.
// Create the storage directory if it does not exist
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmm").format(new Date());
File mediaFile;
String mImageName="MI_"+ timeStamp +".jpg";
mediaFile = new File(mediaStorageDir.getPath() + File.separator + mImageName);
return mediaFile;
}
protected Void doInBackground(Void... paramVarArgs)
{
this.pictureFile = getOutputMediaFile();
if (this.pictureFile == null) { return null; }
try
{
FileOutputStream localFileOutputStream = new FileOutputStream(this.pictureFile);
this.bmp.compress(Bitmap.CompressFormat.PNG, 90, localFileOutputStream);
localFileOutputStream.close();
}
catch (FileNotFoundException localFileNotFoundException)
{
return null;
}
catch (IOException localIOException)
{
}
return null;
}
protected void onPostExecute(Void paramVoid)
{
super.onPostExecute(paramVoid);
try
{
//it will help you broadcast and view the saved bitmap in Gallery
this.ctx.sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri
.parse("file://" + Environment.getExternalStorageDirectory())));
Toast.makeText(this.ctx, "File saved", 0).show();
return;
}
catch (Exception localException1)
{
try
{
Context localContext = this.ctx;
String[] arrayOfString = new String[1];
arrayOfString[0] = this.pictureFile.toString();
MediaScannerConnection.scanFile(localContext, arrayOfString, null,
new MediaScannerConnection.OnScanCompletedListener()
{
public void onScanCompleted(String paramAnonymousString ,
Uri paramAnonymousUri)
{
}
});
return;
}
catch (Exception localException2)
{
}
}
}
}
Probably exception is thrown because there is no MediaCard subdir. You should check if all dirs in the path exist.
About visibility of your files: if you put file named .nomedia in your dir you are telling Android that you don't want it to scan it for media files and they will not appear in the gallery.
For API level 23 (Marshmallow) and later, additional to uses-permission in manifest, pop up permission should also be implemented, and user needs to grant it while using the app in run-time.
Below, there is an example to save hello world! as content of myFile.txt file in Test directory inside picture directory.
In the manifest:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Where you want to create the file:
int permission = ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
String[] PERMISSIONS_STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
if (permission != PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(MainActivity.this,PERMISSIONS_STORAGE, 1);
}
File myDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Test");
myDir.mkdirs();
try
{
String FILENAME = "myFile.txt";
File file = new File (myDir, FILENAME);
String string = "hello world!";
FileOutputStream fos = new FileOutputStream(file);
fos.write(string.getBytes());
fos.close();
}
catch (IOException e) {
e.printStackTrace();
}
Old way of saving files might not work with new versions of android, starting with android10.
fun saveMediaToStorage(bitmap: Bitmap) {
//Generating a dummy file name
val filename = "${System.currentTimeMillis()}.jpg"
//Output stream
var fos: OutputStream? = null
//For devices running android >= Q
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//getting the contentResolver
context?.contentResolver?.also { resolver ->
//Content resolver will process the contentvalues
val contentValues = ContentValues().apply {
//putting file information in content values
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
//Inserting the contentValues to contentResolver and getting the Uri
val imageUri: Uri? =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
//Opening an outputstream with the Uri that we got
fos = imageUri?.let { resolver.openOutputStream(it) }
}
} else {
//These for devices running on android < Q
//So I don't think an explanation is needed here
val imagesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, filename)
fos = FileOutputStream(image)
}
fos?.use {
//Finally writing the bitmap to the output stream that we opened
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
context?.toast("Saved to Photos")
}
}
Reference- https://www.simplifiedcoding.net/android-save-bitmap-to-gallery/
since android 4.4 file saving has been changed. there is
ContextCompat.getExternalFilesDirs(context, name);
it retuns an array.
when name is null
the first value is like /storage/emulated/0/Android/com.my.package/files
the second value is like
/storage/extSdCard/Android/com.my.package/files
android 4.3 and less it retuns a single item array
parts of little messy code but it demonstrates how it works:
/** Create a File for saving an image or video
* #throws Exception */
private File getOutputMediaFile(int type) throws Exception{
// Check that the SDCard is mounted
File mediaStorageDir;
if(internalstorage.isChecked())
{
mediaStorageDir = new File(getFilesDir().getAbsolutePath() );
}
else
{
File[] dirs=ContextCompat.getExternalFilesDirs(this, null);
mediaStorageDir = new File(dirs[dirs.length>1?1:0].getAbsolutePath() );
}
// Create the storage directory(MyCameraVideo) if it does not exist
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
output.setText("Failed to create directory.");
Toast.makeText(this, "Failed to create directory.", Toast.LENGTH_LONG).show();
Log.d("myapp", "Failed to create directory");
return null;
}
}
// Create a media file name
// For unique file name appending current timeStamp with file name
java.util.Date date= new java.util.Date();
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",Locale.ENGLISH) .format(date.getTime());
File mediaFile;
if(type == MEDIA_TYPE_VIDEO) {
// For unique video file name appending current timeStamp with file name
mediaFile = new File(mediaStorageDir.getPath() + File.separator + slpid + "_" + pwsid + "_" + timeStamp + ".mp4");
}
else if(type == MEDIA_TYPE_AUDIO) {
// For unique video file name appending current timeStamp with file name
mediaFile = new File(mediaStorageDir.getPath() + File.separator + slpid + "_" + pwsid + "_" + timeStamp + ".3gp");
} else {
return null;
}
return mediaFile;
}
/** Create a file Uri for saving an image or video
* #throws Exception */
private Uri getOutputMediaFileUri(int type) throws Exception{
return Uri.fromFile(getOutputMediaFile(type));
}
//usage:
try {
file=getOutputMediaFileUri(MEDIA_TYPE_AUDIO).getPath();
} catch (Exception e1) {
e1.printStackTrace();
return;
}
This code is Working great & Worked on KitKat as well. Appreciate #RajaReddy PolamReddy
Added few more steps here and also Visible on Gallery as well.
public void SaveOnClick(View v){
File mainfile;
String fpath;
try {
//i.e v2:My view to save on own folder
v2.setDrawingCacheEnabled(true);
//Your final bitmap according to my code.
bitmap_tmp = v2.getDrawingCache();
File(getExternalFilesDir(Environment.DIRECTORY_PICTURES)+File.separator+"/MyFolder");
Random random=new Random();
int ii=100000;
ii=random.nextInt(ii);
String fname="MyPic_"+ ii + ".jpg";
File direct = new File(Environment.getExternalStorageDirectory() + "/MyFolder");
if (!direct.exists()) {
File wallpaperDirectory = new File("/sdcard/MyFolder/");
wallpaperDirectory.mkdirs();
}
mainfile = new File(new File("/sdcard/MyFolder/"), fname);
if (mainfile.exists()) {
mainfile.delete();
}
FileOutputStream fileOutputStream;
fileOutputStream = new FileOutputStream(mainfile);
bitmap_tmp.compress(CompressFormat.JPEG, 100, fileOutputStream);
Toast.makeText(MyActivity.this.getApplicationContext(), "Saved in Gallery..", Toast.LENGTH_LONG).show();
fileOutputStream.flush();
fileOutputStream.close();
fpath=mainfile.toString();
galleryAddPic(fpath);
} catch(FileNotFoundException e){
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
This is Media scanner to Visible in Gallery.
private void galleryAddPic(String fpath) {
Intent mediaScanIntent = new Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE");
File f = new File(fpath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
Click Here for full description and source code
public void saveImage(Context mContext, Bitmap bitmapImage) {
File sampleDir = new File(Environment.getExternalStorageDirectory() + "/" + "ApplicationName");
TextView tvImageLocation = (TextView) findViewById(R.id.tvImageLocation);
tvImageLocation.setText("Image Store At : " + sampleDir);
if (!sampleDir.exists()) {
createpathForImage(mContext, bitmapImage, sampleDir);
} else {
createpathForImage(mContext, bitmapImage, sampleDir);
}
}

android download image and then read it from sd-card using sqlite

my question is that i have a code that is suppose to receive a variable that contains a website that has an image so this variable changes every time i send a new link this code should go online and download the image and save it to the sd-card then i read it and display it
so my problem with the code is if im sending 2 links to it, it downloads 1 of the images and it always stores it with the second image name (example: im sending image1 and image2 the code downloads image1 two times and stores it as "image2") when i mount the sd-card and check the image directory there is only 1 image there named image2, i thought that doInBackground was causing the problem but im also using onPostExecute() so please if someone can help me i would be thankful for his help Note this is how i call it:
Note: i have no errors in the code // no red marks
This is all the code:
private void UpdateAds(String Bookinfo,TextView myText){
elhgsdatabase db = new elhgsdatabase(this);
if (Bookinfo != "didn't read titels"){
String debContent="";
String output ="";
int NUMBEROFFIELDS = 5;
String s = addressString;
long idx;
String [] buffer = new String[NUMBEROFFIELDS];
output = "";
int l = 0;
while (s.indexOf("[")>-1){
int fk = s.indexOf("[");
int fl = s.indexOf("]");
if(fk > -1){
buffer[l] = s.substring(fk+1, fl);
s = s.substring(fl+1);
l++;
if (l == NUMBEROFFIELDS){
//1. Query the database to check if the book exists
//---get all titles---
db.open();
Cursor c = db.getBookTitle (buffer[0]);
if (c.getCount()==1)
{ myText.setText("This Books Exist \n"); }
else if(c.getCount()==0)
{ String locLink;
locLink = getLocalLink(buffer[3], buffer[0]);
c.moveToFirst();
if (!locLink.equalsIgnoreCase("-1")){
idx= db.insertTitle(buffer[0], buffer[1], buffer[2], getDate(buffer[3]), buffer[4], locLink);
}
else { //there was a problem with retrieval-saving of the Book info locally
myText.setText("There was a problem with retrieval-saving of the Book info locally\n");
}
}//if(c.getCount()==0)
else{//The table has two Books with the same Name. Do something
myText.setText("The table has two Books with the same Name\n");
}
c.close();
l = 0;
}//if(l == NUMBEROFFIELDS)
} //if (fk>-1)
}//while
db.close();
} //of if(BookInfo...
else {
myText.setText("Nothing is Done\n");
}
}
//This method gets the local link field of the active book records
// it goes on the web, gets the content and stores it in a place
// and saves the path of that place in the database for that
//it returns -1 if something wrong happened during the process
public String getLocalLink(String image_URL, String BookName){
/** This is what we do with this method:
* Go online, according to the link, get the content, call the method to save, get the local link
* and return it
*/
setContentView(R.layout.main);
reviewImageLink = image_URL;
URL reviewImageURL;
String name = reviewImageLink.substring(reviewImageLink.lastIndexOf("/") + 1);
try {
reviewImageURL = new URL(reviewImageLink);
if (!hasExternalStoragePublicPicture(name)) {
isImage = false;
new DownloadImageTask().execute(reviewImageURL);
Log.v("log_tag", "if");
isImage = true;
File sdImageMainDirectory = new File(Environment.getExternalStorageDirectory(), getResources()
.getString(R.string.directory));
sdImageMainDirectory.mkdirs();
File file = new File(sdImageMainDirectory, name);
Log.v("log_tag", "Directory created");
}
} catch (MalformedURLException e) {
Log.v(TAG, e.toString());
}
return ("/sdcard/Hanud/"+BookName+".jpg");
}
private class DownloadImageTask extends AsyncTask<URL, Integer, Bitmap> {
// This class definition states that DownloadImageTask will take String
// parameters, publish Integer progress updates, and return a Bitmap
protected Bitmap doInBackground(URL... paths) {
URL url;
try {
url = paths[0];
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
int length = connection.getContentLength();
InputStream is = (InputStream) url.getContent();
byte[] imageData = new byte[length];
int buffersize = (int) Math.ceil(length / (double) 100);
int downloaded = 0;
int read;
while (downloaded < length) {
if (length < buffersize) {
read = is.read(imageData, downloaded, length);}
else if ((length - downloaded) <= buffersize) {
read = is.read(imageData, downloaded, length- downloaded);
}
else {read = is.read(imageData, downloaded, buffersize);}
downloaded += read;
publishProgress((downloaded * 100) / length);
}
Bitmap bitmap = BitmapFactory.decodeByteArray(imageData, 0,
length);
if (bitmap != null) {
Log.i(TAG, "Bitmap created");
} else {
Log.i(TAG, "Bitmap not created");
}
is.close();
return bitmap;
} catch (MalformedURLException e) {
Log.e(TAG, "Malformed exception: " + e.toString());
} catch (IOException e) {
Log.e(TAG, "IOException: " + e.toString());
} catch (Exception e) {
Log.e(TAG, "Exception: " + e.toString());
}
return null;
}
protected void onPostExecute(Bitmap result) {
String name = reviewImageLink.substring(reviewImageLink
.lastIndexOf("/") + 1);
if (result != null) {
hasExternalStoragePublicPicture(name);
saveToSDCard(result, name);
isImage = true;
} else {
isImage = false;
}
}
}
public void saveToSDCard(Bitmap bitmap, String name) {
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
mExternalStorageAvailable = mExternalStorageWriteable = true;
Log.v(TAG, "SD Card is available for read and write "
+ mExternalStorageAvailable + mExternalStorageWriteable);
saveFile(bitmap, name);
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
Log.v(TAG, "SD Card is available for read "
+ mExternalStorageAvailable);
} else {
mExternalStorageAvailable = mExternalStorageWriteable = false;
Log.v(TAG, "Please insert a SD Card to save your Video "
+ mExternalStorageAvailable + mExternalStorageWriteable);
}
}
private void saveFile(Bitmap bitmap, String name) {
String filename = name;
ContentValues values = new ContentValues();
File sdImageMainDirectory = new File(Environment
.getExternalStorageDirectory(), getResources().getString(
R.string.directory));
sdImageMainDirectory.mkdirs();
File outputFile = new File(sdImageMainDirectory, filename);
values.put(MediaStore.MediaColumns.DATA, outputFile.toString());
values.put(MediaStore.MediaColumns.TITLE, filename);
values.put(MediaStore.MediaColumns.DATE_ADDED, System
.currentTimeMillis());
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
Uri uri = this.getContentResolver().insert(
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values);
try {
OutputStream outStream = this.getContentResolver()
.openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.PNG, 95, outStream);
outStream.flush();
outStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private boolean hasExternalStoragePublicPicture(String name) {
File sdImageMainDirectory = new File(Environment
.getExternalStorageDirectory(), getResources().getString(
R.string.directory));
File file = new File(sdImageMainDirectory, name);
if (file != null) {
file.delete();
}
return file.exists();
}
public void showAllBooks( )
{
final elhgsdatabase db = new elhgsdatabase(this);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
// Get new entry
db.open();
long currTime = System.currentTimeMillis();
String p_query = "select * from ads where timeFrom<=?";
Cursor c = db.rawQuery(p_query, new String[] { Long.toString(currTime)});
if (c.moveToFirst())
{
do {
DisplayTitle(c);
} while (c.moveToNext());
}
db.close();
}
}, 5000); // 5000 miliseconds
}
public long getDate(String s){
String[] formats = new String[] {
"yyyy-MM-dd HH:mm:ss"
};
SimpleDateFormat sdf=null;
String st;
for (String format : formats) {
sdf = new SimpleDateFormat(format, Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("EST"));//UTC or EST
st = new String(sdf.format(new Date(0)));
System.err.format(format, st);
}
Calendar c = Calendar.getInstance();
Date dt;
try {
dt = sdf.parse(s);
c.setTime(dt);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return c.getTimeInMillis() ;
}
public void DisplayTitle(final Cursor c)
{
Toast.makeText(this,
"Title: " + c.getString(0) + "\n" +
"isbn: " + c.getString(1) + "\n" +
"Publisher: " + c.getString(2) + "\n" +
"Year: " + c.getString(3) + "\n" +
"Image On Line: " + c.getString(4) + "\n" +
"Image On SD " + c.getString(5) + "\n" ,
Toast.LENGTH_LONG).show();
String imageInSD = c.getString(5);
Bitmap bitmap = BitmapFactory.decodeFile(imageInSD);
myImageView=(ImageView)findViewById(R.id.imageview1);
myImageView.setImageBitmap(bitmap);
}
----------
I'm pretty sure you're setting the second image name to the reviewImageLink (not sure if this is a class variable or what) variable. Instead, try passing both the URL and the String to the AsyncTask. Instead of passing a URL... pass in an Object... where the first one is the URL and the second is the name, and use that in the onPostExecute.
You don't show how ImageLink is set up. But as the filename is constructed from it, I guess your problem has almost nothing to do with the code you showed here.

Categories

Resources