FileObserver -> onEvent(event, path): path is NULL - android

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.

Related

Processing Android: java.lang.IllegalArgumentException: File contains a path sepator

Im currently trying to save some values in a text file in Processing Android (APDE). I want to later use this in another context, so it's important to use a complete file path. From Processing documentation for loadStrings():
... Alternatively, the file maybe be loaded from anywhere on the local
computer using an absolute path (something that starts with / on Unix
and Linux, or a drive letter on Windows)
So it must be possible.
I already searched for a answer, but never found something for Processing.
So my code is:
String[] saveData;
int score;
void setup(){
saveData=loadStrings("/storage/emulated/0/dataP/hi.txt");
score=parseInt(saveData[0]);
fullScreen();
frameRate(60);
noStroke();
noSmooth();
textAlign(CENTER);
textSize(height/20);
}
void draw(){
background(0);
fill(255) ;
text(score, width/2,height/2);
}
void mousePressed(){
score--;
saveData[0]=str(score);
println(saveData[0]);
saveStrings("/storage/emulated/0/hi.txt" ,saveData);
}
and I get the following error:
java.lang.IllegalArgumentException: File
/storage/emulated/0/dataP/hi.txt contains a path separator
I believe the confusion stems from the fact that loadStrings() method works differently for Java mode and Android mode. In Java mode, it is definitely possible to give loadStrings() an absolute Path with included separators, but in Android mode, loadStrings() will only work if you only specify a name without any separator (assumes by default to be looking into the data folder). Therefore, having any separator inside loadStrings() will throw the error.
One simple workaround you can try is to first create a separate path variable:
String path = "/storage/emulated/0/dataP/hi.txt";
And then give that as parameter to the loadStrings() method:
saveData = loadStrings(path);
If you were to use an SD card for storage, for example, you could do something like:
String SDCARD = Environment.getExternalStorageDirectory().getAbsolutePath();
File file = new File(SDCARD + File.separator + "mytext.txt");
String[] s = loadStrings(file.getPath());
As explained in the link in the comment I posted, loadStrings() and saveStrings() does not take absolute path as argument. What it means is that it can only access files with path "name.txt" and not "folder/name.txt". You have to do it using a FileInputStream and FileOutputStream if you must use absolute path. There are many examples of both these files on StackOverflow.

Android Studio: context.getFilesDir() returns a path [/data/user/0/com.example.filesexperimenting/files/] that I can not find. What am I missing?

I am trying to use Android's internal helpers to get a path from the system for my file first and then put my files where the system wants. Because tomorrow they might change their minds.
I made a simple program to explore this subject. Here is my code;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String path = letsMakeAfile(this, "myFile.txt");
}
private static String letsMakeAfile(Context context, String nameOfFile) {
String strOfFinalPath ="";
//ask the system what path to use...
String strOfContextPath = context.getFilesDir() + "/";
//line above doesnt work without ' + "/" ' or something on the end
//line above records this path: /data/user/0/com.example.filesexperimenting/files/
//this appears to be an invalid path unless "user" is a hidden directory
Log.d("IDIOT", "strOfContextPath: "+ strOfContextPath);
try
{
File file = new File(strOfContextPath, nameOfFile);
if (file.exists() == false) {
file.mkdirs();
//after this line "makes dirs" is file automatically still made and dropped in?
letsMakeAfile(context, nameOfFile);
//I assume not so Ive made a recursive call
}
else
;
//escape recursion....
strOfFinalPath = file.getAbsolutePath();
//Here I record the path where I hope the file is located
Log.d("IDIOT", "strOfFinalPath: "+ strOfFinalPath);
}
catch (Exception e) {
e.printStackTrace();
Log.d("IDIOT", "CATCH ERROR: "+ e.getLocalizedMessage());
}
//runs without a catch
return strOfFinalPath;
}
}
Logcat:
2019-04-09 09:59:22.901 16819-16819/? D/IDIOT: strOfContextPath: /data/user/0/com.example.filesexperimenting/files/
2019-04-09 09:59:22.901 16819-16819/? D/IDIOT: strOfFinalPath: /data/user/0/com.example.filesexperimenting/files
Ultimately I am getting a path of /data/user/0/com.example.filesexperimenting/files/ from context.getFilesDir() which appears to be an invalid path unless "user" is a hidden directory (then why can I see root?). In Device File Explorer under data the only other directories are app, data and local
What am I missing? I'll assume its something with file.makedirs()
Full disclosure, I am a student and there is not a lot out there on this so your replies, while obvious to you at your experience level, should help others. I have some experience with Java and more with C++ but Android is new to me. Thanks in advance!
So, in talking outside of StackExchange it appears that using java.io like I am trying to in the example can cause some problems because of the preset file directories that may be locked or restricted that Java io might not know about.
Android has it's own method openFileOutput(String name, int mode) that has the ability to create the app resource file and directory it belongs in.
Description copied from class: android.content.Context
Actions:
~Open a private file associated with this Context's application package for writing.
~Creates the file if it doesn't already exist.
~No additional permissions are required for the calling app to read or write the returned file.
Params:
~name – The name of the file to open; can not contain path separators.
~mode – Operating mode.
Returns: The resulting FileOutputStream.
Throws: java.io.FileNotFoundException
If you want to be able to navigate to the location of your saved files through the file explorer (either in Android Studio or the Files app on the phone) you should use Context.getExternalFilesDir().
Context.getFilesDir() returns a directory not accessible by anyone BUT the creating application. So if you would like to see what is in this file you would need to open it with the same application that wrote it. IE: Print the contents to the screen after you save it in your app.
Context.getExternalFilesDir() returns a directory completely accessible by anyone and any application. So files created and saved in this external directory can be seen by Android Studio's file explorer as the OP has screenshot or by any application installed on the phone.
What is nice about both of these methods is that as long as you are only accessing files you have created you never need to ask the user for storage permissions Read or Write. If you would like to write to someone else's external files dir then you do.
Source
Check if sdcard is mounted or not.
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)){
///mounted
}
Get the path of sd card
File dir= new File(android.os.Environment.getExternalStorageDirectory());
walkdir(dir);
ArrayList<String> filepath= new ArrayList<String>();
//list for storing all file paths
public void walkdir(File dir) {
File listFile[] = dir.listFiles();
if (listFile != null) {
for (int i = 0; i < listFile.length; i++) {
if (listFile[i].isDirectory()) {
// if its a directory need to get the files under that directory
walkdir(listFile[i]);
} else {
// add path of files to your arraylist for later use
//Do what ever u want
filepath.add( listFile[i].getAbsolutePath());
}
}
}
}
Try using this:
context.getFilesDir().getParentFile().getPath()

WARNING: File.mkdir() is ignored

I've created a basic Audio Recording App using Media Recorder API's. I'm trying to create a folder in the SD card and to save my recorded files to that folder. I'm getting this warning with following code.
File.mkdir() is ignored
// Assign random number to avoid audio file from overwriting
Long tsLong = System.currentTimeMillis()/1000;
// Specify a Location for the recorded file to be stored in the SD card
mFileName = Environment.getExternalStorageDirectory().getAbsolutePath() + "/AudioRecordTest/";
File path = new File(mFileName);
if(!path.exists()) {
path.mkdir();
}
mFileName += "/AudioRecordTest_" + tsLong.toString() + ".mp3";
How to solve this warning message. Feel free to modify the code if there is any other mistake.
Correct me if I'm wrong, but I expect that the (compilation) warning message really says this:
Result of File.mkdir() is ignored
... or something like that. It is telling you that you are ignoring the result of the mkdir() call that tells you whether or not a directory was created.
One way to avoid the warning would be to test the result and act appropriately. Another would be to simply assign the result to a temporary variable, ignore it, and (potentially) crash later because the directory wasn't created when it should have been.
(Guess which solution is better ...)
Feel free to modify the code if there is any other mistake.
Since you asked ... it is BAD STYLE to use Hungarian notation for Java variable names. Java is a strongly typed language where all variables have a clear declared types. You should not need the mental crutches of some ghastly identifier convention to tell you what a variable's type is intended to be.
As #Stephen C suggests, i handled in these ways
1)
boolean isDirectoryCreated= path.mkdir();
and ignore 'isDirectoryCreated'
2) (Recommended)
boolean isDirectoryCreated=path.exists();
if (!isDirectoryCreated) {
isDirectoryCreated= path.mkdir();
}
if(isDirectoryCreated) {
// do something
}
3)
if (!path.exists()) {
if(path.mkdir()){
// do something
}
}
If you want to ignore this warning, add this on the method:
#SuppressWarnings("all")
I created a utility function:
#SuppressWarnings("unused")
private static void IGNORE_RESULT(boolean b) {}
And then to use:
IGNORE_RESULT(path.mkdir());
I like this solution because it is self documenting. You are explicitly stating that you know there is a result and you are intentionally ignoring it. It should also optimize out to a NOP during compilation so there is no unnecessary test at runtime just to suppress a useless warning.

Writing to Internal Storage - File Not Found

I have read a ton of articles on writing to the internal storage of an android device, and I need some help figuring out what I am doing wrong. In the main activity of my app, I read a value from a file stored in the internal storage like this:
string ID = GetID();
Where GetID looks like this:
string GetID()
{
try
{
using (var i = new StreamReader (OpenFileInput (FILENAME)))
{
return i.ReadToEnd();
}
}
catch
{
return "";
}
}
If the file doesn't exist, "" is returned and the user is sent off to another activity to register. In that activity, I use this to call a function to write to internal storage like this:
WriteID (uniqueID);
Where WriteID looks like this:
void WriteID(string uniqueID)
{
using (var o = new StreamWriter (
OpenFileOutput (FILENAME, FileCreationMode.Private)))
o.Write (uniqueID);
}
This is where it gets weird for me, if I put these two functions in the same activity, write to the file and then call the function to read from it, I get uniqueID returned correctly. However if I stop the app and then restart it, I get a File Not Found exception thrown and nothing returned from the read function. However, if I create a new project, and use the same code, everything works as expected (file is created and written to, then on restart the data persists).
So I am thinking there is some setting that I have changed or some reference that I may be missing in my original app that causes the internal storage file to be removed when the app is stopped?
Any ideas as to why this works correctly in my test app, but not in the other?

Delete file after sharing via intent

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.

Categories

Resources