I'm trying to delete a temporary file after sharing it via android's Intent.ACTION_SEND feature. Right now I am starting the activity for a result and in OnActivityResult, I am deleting the file. Unfortunately this only works if I am debugging it with a breakpoint, but when I let it run freely and say, email the file, the email has no attachment.
I think what is happening is my activity is deleting the file before it had been emailed. What I don't get is why, shouldn't onActivityResult only be called AFTER the other activity is finished?
I have also tried deleting the file in onResume, but no luck.
Is there a better way to do this?
I noticed the same behavior with a similar approach. While watching logcat for errors I saw gmail complaining that it couldnt find the attachment. So, yes, it seems the intent returns BEFORE gmail has actually read the file for attachment.
I havent gotten around to a solution yet but it's likely going to be something like:
move the file to some directory so I know it's one I've decided to send
send it as attachment via ACTION_SEND
at next onResume for my start screen activity, delete files in the "sent" directory that are older than some time frame that's reasonably long enough for the send to actually have happened
Choosing an appropriate time frame might be tricky since it's probably the case that gmail (or other ACTION_SEND providers) dont actually read the file until it has a network connection. I'm thinking 24 hours should be reasonable and in my case I'm dealing with diagnostic logs so there's no real harm in deleting one too soon if the user has been off network for a long period of time.
If the content of your file is text and it's not obscenely large a simpler approach may be to read the contents of the file and use Intent.putExtra(android.content.Intent.EXTRA_TEXT, yourText) to inline it into the body of the message.
What I did is the following.
I used the:
myfile.deleteOnExit();
However, as D.R. mentioned in the comment below correct answer, this does not guarantee the file deletion. This is why I am also deleting the file after the Shared Activity returns. I delete the file if file exists. Because the app crashed sometimes, I put it inside try{} and it works.
I do not know why it does not work for you, but for me it works at least for Gmail attachement, TextSecure, Hangouts.
In class delcaration:
static File file;
In method that calles the Intent:
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/png");
// Compress the bitmap to PNG
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bytes);
// Temporarily store the image to Flash
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File (sdCard.getAbsolutePath() + "/FolderName");
dir.mkdirs();
// This file is static.
file = new File(dir, "FileName.png");
try {
file.createNewFile();
FileOutputStream fo = new FileOutputStream(file);
fo.write(bytes.toByteArray());
fo.flush();
fo.close();
} catch (IOException e) {
e.printStackTrace();
}
// Share compressed image
share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///"+file.getPath()));
/** START ACTIVITY **/
startActivityForResult(Intent.createChooser(share,"Share Image"),1);
// Delete Temporary file
file.deleteOnExit(); // sometimes works
In an extra method:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Because app crashes sometimes without the try->catch
try {
// if file exists in memory
if (file.exists()) {
file.delete();
}
} catch (Exception e) {
Log.d(LOG,"Some error happened?");
}
}
This is my working solution. Before startActivity() execute this:
FileObserver fo = new FileObserver(file,FileObserver.CLOSE_NOWRITE) {
#Override
public void onEvent(int event, #Nullable String path) {
if(event==FileObserver.CLOSE_NOWRITE) {
file.delete();
this.stopWatching();
// HERE: remove the saved reference to 'fo'
}
}
};
fo.startWatching();
// HERE: save a reference to 'fo'
Important: You should save the reference to 'fo' so that the FileObserver is not garbage-collected.
I have managed to get it to work with:
File tbd = new File(sharePath);
tbd.deleteOnExit();
This seems to delete the file when the activity closes.
Another potential answer would be to create a new thread when your app resumes, immediately mark the current time, sleep the thread for however long you feel is reasonable for the file to be sent, and when the thread resumes, only delete files created before the previously marked time. This will give you the ability to only delete what was in the storage location at the time your app was resumed, but also give time to gmail to get the email out. Code snippet: (I'm using C#/Xamarin, but you should get the idea)
public static void ClearTempFiles()
{
Task.Run(() =>
{
try
{
DateTime threadStartTime = DateTime.UtcNow;
await Task.Delay(TimeSpan.FromMinutes(DeletionDelayMinutes));
DirectoryInfo tempFileDir = new DirectoryInfo(TempFilePath);
FileInfo[] tempFiles = tempFileDir.GetFiles();
foreach (FileInfo tempFile in tempFiles)
{
if (tempFile.CreationTimeUtc < threadStartTime)
{
File.Delete(tempFile.FullName);
}
}
}
catch { }
});
}
#Martijn Pieters This answer is a different solution that handles multiple questions. If anything, the other questions that I posted on, should be marked as duplicates because they are the same question. I posted on each of them to ensure that whoever has this problem, can find the solution.
Related
I have made an app for Android which saves results for skeet shooting. During a session, the user either presses hit or miss. When the session is over, the user press save and the new result is appended to the json-object. After that the result is appended, it is saved to the phone via
public static void saveData(Context context) {
File path = context.getFilesDir();
File file = new File(path, "jsonUsr.json");
if (file.exists()) {
try {
FileOutputStream stream = new FileOutputStream(file);
String objString = usrObject.toString();
stream.write(objString.getBytes());
stream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Now, my friend who has my app used it when we were at a competition today. During the session when my app was running and he had started to fill in his result, he receives an sms. He opens the message and reads it. Then instead of reopen my app from the current apps running, he goes to the meny and presses the icon. Suddenly he discovers that all data is gone! Not just the current session, but all results he has entered. I cannot understand that, because there does not even exists in the code a call which deletes the saved json string file.
I have tried to imitate what he did on my phone, but it works perfectly. He has had a lot of problems with the memory with his phone. For a couple of days ago, it complained about that there were not enough memory for upgrading, so he moved things to the SD-card? Is it possible that the data has either been removed due to lack of memory or that it is moved to the SD card?
It is not so much to work with, but I do not have more. Since I cannot recreate it myself, it is hard to know exactly what has happened.
It is a good idea to always write to a new file in the same directory. If that write succeeds, move the new file onto the config file by changing its name. (this change is atomic)
That way, you will always end up with a valid file, even if the call to write fails for some reason (out of disk, toString() fails, etc.).
Having a problem writing out to a file, this code is taken directly from the android developer page and then tweaked a bit by me. Is there something i am missing? Quite new to Android development so sorry if it's something blatantly obvious.
send.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FileOutputStream outputStream;
String data = "hello";
File fileDir = new File("data.txt");
if (!fileDir.exists())
fileDir.mkdirs();
try {
outputStream = openFileOutput("data.txt",Context.MODE_PRIVATE);
outputStream.write(data.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
});
Basically, your problem is that you are trying to do it twice, once in a way that won't work, and once in a way that will, but hides the result.
File fileDir = new File("data.txt");
if (!fileDir.exists())
fileDir.mkdirs();
This would create a Java File object connected to a hypothetical file called "data.txt" located in the current working directory, which for an Android app is the root directory of the device - a place you most definitely are not allowed to write to. However, this may not obviously cause any errors, as the root directory exists so mkdirs() will do nothing, and you only create a File object, you don't actually try to create a file on "disk". Effectively this code does nothing for you - get rid of it.
Next you try something basically workable:
try {
outputStream = openFileOutput("data.txt",Context.MODE_PRIVATE);
outputStream.write(data.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
openFileOutput() is a method of a Context (Activity or Service) which creates an output stream to write to an actual file located in the private internal storage area of your app. This is all fine and good, and normally a good choice for storing typical data. However, it is not a place that you will be able to examine when running a release app on a secured device, as neither ADB based tools nor Mass Storage or MTP access over USB have rights to it. So it's entirely possible that this code worked, but you had no way to discover that fact. If you are on an emulator, you can access this location with ADB or the DDMS browser, and if your apk is a debug one, you can use the run-as command line tool in the shell.
If you want to share the data, you might consider putting it on the External Storage instead.
I want to know when a file is finished writing, and to do that I'm trying to use FileObserver. I'm doing it like this:
FileObserver observer = new FileObserver(imageUri.getPath()) {
#Override
public void onEvent(int event, String path) {
if(event == FileObserver.CLOSE_WRITE)
Log.d(TAG, "FILE: "+path);
}
};
observer.startWatching();
imageUri is a valid Uri. When the file is closed I get the following log entry:
FILE: null
Why is it null? It's possible that the user writes several files, so I need to know which one is triggering the event.
Thanks!
According to the documentation of onEvent():
The path, relative to the main monitored file or directory, of the file or directory which triggered the event
So I guess when path is null it is the the specified file or directory...
You need to keep track of the original path yourself. And append the path of onEvent() to this path to get the full path (unless you are tracking a file and its value is always null):
FileObserver observer = new FileObserver(imageUri.getPath()) {
public String basePath;
#Override
public void onEvent(int event, String path) {
String fullPath = basePath;
if(path != null) {
// Eventually add a '/' in between (depending on basePath)
fullPath += path;
}
Log.d(TAG, "FILE: "+fullPath);
}
};
observer.basePath = imageUri.getPath();
observer.startWatching();
I tried to keep the example as close to your code snippet as possible. But, it is much better to create a full-blown class extending FileObserver, so you can add an constructor to store the basePath and are not required to access the public field from outside the class/instance!
I just encountered something like this today. I had a FileObserver monitoring a folder for new files, which I then attempted to do something with the downloaded images. When I went to access the images by BitmapFactory.decodeFile(ImgPath), I would get sometimes get a NULL result. This seemed to happen on newer and faster devices, and never in debug when stepping through the event. I came to the conclusion that the file was still in use or not completely finished yet and I had to wait until the system unleashed its claws from the file.
I'm new to Android development and am not familiar with the proper way to do this yet, but avoided the NULL issue for the moment by inserting a Thread.sleep. I know this is terrible, but it worked as a temp solution for me.
I'm using the below code to transfer an image from one folder on external memory, to another..as specified by the user. The problem is, the photo gets copied to the destination folder fine..but I can't open or view it. I use a file manager named Astro to see if it was successfully moved, and it is..but I'm unable to open it both in Astro and in the resident Gallery app. I'm thinking something is wrong with my code and maybe I need read and/or decode to photo before I can move it, from what I understand about the File class it is just an abstraction. Any guidance on this would be greatly appreciated, here is the code I'm currently using.
File img = new File(imgViewPath);
File output = new
File(Environment.getExternalStorageDirectory().toString() + "/MyAppPics/" + moved,
img.getName());
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(output));
}
finally {
if (out != null) {
out.close();
}
}
}catch(Exception e){
e.printStackTrace();
}
You are not writing anything to the output stream. Read the bytes from the input stream and write it to the output stream, only then the files will get copied.
I have this code to take screenshots of layouts in Android. It is not throwing any errors, however, the screenshot is not being taken either. Can someone please help me figure out what I am doing wrong here? I am new with Eclipse and I am having a hard time figuring things out. Also if there is any other way to take screenshots can you post it as an answer to this thread? Thanks for your time!
private void getScreenshot()
{
View content = findViewById(R.id.testView);
content.setDrawingCacheEnabled(true);
content.buildDrawingCache(true);
Bitmap bitmap = Bitmap.createBitmap(content.getDrawingCache());
content.setDrawingCacheEnabled(false);
File file = new File( Environment.getExternalStorageDirectory() + "image.png");
try
{
file.createNewFile();
FileOutputStream ostream = new FileOutputStream(file);
bitmap.compress(CompressFormat.PNG, 100, ostream);
ostream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
Do you need to add path separator into your File? i.e.
File file = new File(Environment.getExternalStorageDirectory() +
File.separator + "image.png");
You should add a lot more logs and tests in your code to check whether it is behaving as you would expect, e.g.
Log the details of the file you are trying to create to be sure it is correct.
Once you've created the file, test that it exists, e.g. if (!file.exists())
The Bitmap.compress function returns a boolean, so you should check the return value and log it to see if it succeeded.
One other thought: maybe you need to call ostream.flush() (API docs here) to ensure the buffered data is written to the file?
I'm assuming you're writing this code for use within your app. You probably already know this, but DDMS provides a way to take screenshots in case you just want to take some yourself. Just make sure to select the device to enable the Screenshot menu option.