Crawling Android File System gets stuck in possible SymLink loop - android

I'm trying to crawl the entire file system of an android device, both directories and files, without the benefit of NIO, to build a tree of it. If I had NIO then I could use WalkTree or similar, but I don't.
The problem I am having (on the Nexus 5 API 23 x86 emulator) is in /sys/bus/pci/devices and possibly other directories (eg /proc/self) - it doesn't complete before the app times out/quits/crashes (unknown which), possibly getting into some kind of loop or something (the path may change in a repetitive fashion but the canonical path varies little or not at all) .
However if I rule out Symbolic links then that problem goes away but I get what is only some of the files on the device rather than all - for example lacking files under /data (or /data/media/0) and those files not showing up elsewhere - not to mention it looks completely different from the file system that most file managers show. The former is strange as I'd understood Symbolic Links pointed to files and folders that were still present in the file system, but just made them look as if they were elsewhere.
What's the solution? Do I have to code exceptions or special handling for /sys/bus/pci/devices, /proc/self and others? I'd prefer to keep Symbolic Links being followed if I can, and I'd prefer to crawl as many files and folders as I can (so starting in a sub-folder is not preferred).
And a few related questions that might affect the approach I eventually take - if I DO keep SymLinks then does that mean that some things will be crawled twice or more? Is there a way to avoid that? Is there a way to detect when something is the TARGET of a SymLink, other than following the SymLink and checking the CanonicalPath?
Here's my code:
I get the root (I understand that in Android, the first and likely only root is the valid one):
File[] roots = File.listRoots();
String rootPath = "";
try {
rootPath = roots[0].getCanonicalPath();
} catch (IOException e) {
// do something
}
Then I start the crawl (note the boolean to choose whether to ignore simlinks or not):
try {
// check if the rootPath is null or empty, and then...
File rootFile = new File(rootPath);
rootNode = new FileFolderNode(rootFile, null, true, false); // last param may be true to ignore sim links
//FileFolderNode(String filePath, FileFolderNode parent, boolean addChildren, boolean ignoreSimLinks)
} catch (Exception e) {
// do something
}
That uses the FileFolderNode, which has constructor:
public FileFolderNode(File file, FileFolderNode parent, boolean addChildren, boolean ignoreSimLinks) throws IOException {
if (file == null)
throw new IOException("File is null in new FileFolderNode");
if (!file.exists())
throw new IOException("File '" + file.getName() + "' does not exist in new FileFolderNode");
// for now this uses isSymLink() from https://svn.apache.org/repos/asf/commons/_moved_to_git/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java adjusted a bit to remove Java 7 and Windows mentions
if (!ignoreSimLinks)
if (FileUtils.isSymlink(file))
return;
this.name = file.getName();
if (this.name.equals("") && ! file.getCanonicalPath().equals("/"))
throw new IOException("Name is empty in new FileFolderNode");
this.isDirectory = file.isDirectory();
if (this.isDirectory) {
this.children = new ArrayList<FileFolderNode>();
if (addChildren) {
File[] files = file.listFiles();
if (files == null) {
// do something
} else {
// add in children
for (File f : files) {
FileFolderNode child = null;
try {
child = new FileFolderNode(f, this, addChildren, ignoreSimLinks);
} catch (Exception e) {
child = null;
}
if (child != null)
children.add(child);
}
}
}
}
}
Given the lack of answers here, I've broken this question down into areas needing clarification, and am trying to get answers to those - please do see if you can help with those:
Get Android Filing System root
Android SymLinks to hidden or separate locations or partitions
Avoiding Android Symbolic Link loop

Related

NullPointerException: Attempt to get length of null array

I am developing an Android App. I have the below code to write a list of all files starting with XLR in a particular folder:
private List<File> getListFiles(File parentDir) {
ArrayList<File> inFiles = new ArrayList<File>();
File[] files = parentDir.listFiles();
for (File file : files) {
try {
if ((file.exists()) && (file != null)) {
if (file.isDirectory()) {
inFiles.addAll(getListFiles(file));
} else {
if (file.getName().startsWith("XLR")) {
inFiles.add(file);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return inFiles;
}
On Android 10 and 11 it seems to be a problem. For Android 10 I have legacy storage enabled and Android 11 I have got access to all files. So the issue is not file access.
The folder itself might not be in my app's folder, hence the need to be able to list out files
I know that there is an extensive page here on NPE's and I have tried my best to follow the advice there (I am checking that files exist and are not null), but still to no avail.
I feel that there's probably something so stupid, that a more experienced programmer would probably pick out in 2 seconds...
This problem would be easily solved just reading the documentation of the actual File package.
Documentation on the method File::listFiles:
An array of abstract pathnames denoting the files and directories in the directory denoted by this abstract pathname. The array will be empty if the directory is empty. Returns null if this abstract pathname does not denote a directory, or if an I/O error occurs.
As you can see, that method can actually return null and not only an empty array, so you should check that the value isn't null before even trying to iterate over it.

Unable to make new folder in Apps cache folder

I have written a caching system. It saves data to the cache folder of the App using platform / Android methods to find the folder. I have an issue that I cannot create a new folder in the Apps cache folder.
This error is reporting in Crashlytics (via non-fatal error reporting) for some devices and not for the majority.
The method that contains the issue is my getCacheFolder() method.
An example folder path that does not work on these devices: /storage/emulated/0/Android/data/app.package.name/cache/stream-cache.
Reported versions affected: 4.4.2; 5.0.1; 6.0, 6.0.1.
Reported devices affected: LENOVO YOGA Tablet 2-1050L, MOTOROLA MotoE2(4G-LTE), Samsung SM-N910F (accounts for 98% of faults reported).
My suspicions are that there might be an issue relating to removable sdcards.
#NonNull
public File getCacheFolder() {
// If this is the first run of this library function, we should get our cache folder
if (mCacheFolder == null || !mCacheFolder.exists()) {
File dir = null;
// Prioritise the external sdcard to store cached files
dir = mContext.get().getExternalCacheDir();
if(dir == null) {
// There was no external sdcard - instead use internal storage
dir = mContext.get().getCacheDir();
}
// Still couldn't get a cache location, go to fallback plan and throw exception
if(dir == null) {
throw new IllegalStateException("Could not create a location to cache. " +
"This leaves the caching library in a bad state.");
}
// Point to our specific caching folder for this system
mCacheFolder = new File(dir, "stream-cache");
}
// If it doesn't exist, we will need to make it
if (!mCacheFolder.exists()) {
// If we cannot make the directory, go to fallback plan and throw an exception
if (!mCacheFolder.mkdirs()) {
// HERE IS WHERE THE FAULT IS REPORTED
throw new IllegalStateException("Could not create a location to cache. " +
"This leaves the caching library in a bad state.: "+mCacheFolder);
}
}
return mCacheFolder;
}
Update 1
I updated the code to write out various information about the state:
#NonNull
public File getCacheFolder() {
if (mCacheFolder == null || !mCacheFolder.exists()) {
File dir = mContext.get().getExternalCacheDir();
if(dir == null || !Utils.isExternalStorageWritable()) {
dir = mContext.get().getCacheDir();
}
mCacheFolder = new File(dir, "stream-cache");
}
if (!mCacheFolder.exists()) {
if (!mCacheFolder.mkdirs()) {
// Problems writing to cache - test writing to file path.
File filePath = mContext.get().getExternalFilesDir(null);
File cachePath = new File(filePath, "stream-cache");
throw new IllegalStateException("Could not create a location to cache. " +
"This leaves the caching library in a bad state.: "+mCacheFolder+ ". " +
"filePath="+filePath+". " +
"cachePath="+cachePath+". " +
"cachePath-exists="+cachePath.exists()+". " +
"cachePath-mkdirs="+cachePath.mkdirs());
}
}
return mCacheFolder;
}
The important results from this:
filePath=null - Context.getExternalFilesDir(null); returns a null. Breaking the rest of the tests.
Instead of using
// Point to our specific caching folder for this system
mCacheFolder = new File(dir, "stream-cache");
try using this:
// Point to our specific caching folder for this system
mCacheFolder = new File(dir + "/stream-cache");
in your first code snippets.

Google Drive API - get list of files including folders

I need to list all images in a users drive cloud.
I use https://www.googleapis.com/drive/v2/files with the correct filter to query all images. I need to group the result in my app in folders. I know a file can have multiple parents, that's fine.
I would like to avoid making and calls (for every single file a single call) to get a files folder via https://developers.google.com/drive/v2/reference/files/get using the parent id from the first call.
Is there a network friendly way to get all files inclusive there folders?
EDIT
A simple solution would be to get all folders with ids in one query and lookup folder names in this result. Maybe that's somehow possible?
As you answered yourself in the comments above (but you can't match names, you have to match IDs; names aren't unique).
Step 1: get all your folders in one shot (paging results, filtering unneeded fields, skipping the trashed ones):
private static Drive mGOOSvc;
....
static ArrayList<ContentValues> getFolders() {
ArrayList<ContentValues> cvs = new ArrayList<>();
if (mGOOSvc != null) try {
Drive.Files.List qry = mGOOSvc.files().list()
.setQ("mimeType = 'application/vnd.google-apps.folder'")
.setFields("items(id,labels/trashed,parents/id,title),nextPageToken");
String npTok = null;
if (qry != null) do {
FileList gLst = qry.execute();
if (gLst != null) {
for (File gFl : gLst.getItems()) {
if (gFl.getLabels().getTrashed()) continue;
for (ParentReference parent : gFl.getParents())
cvs.add(newContentValues(gFl.getTitle(), gFl.getId(), parent.getId()));
}
npTok = gLst.getNextPageToken();
qry.setPageToken(npTok);
}
} while (npTok != null && npTok.length() > 0);
} catch (Exception e) { /* handle Exceptions */ }
return cvs;
}
Step 2: Parse the resulting ArrayList to build the tree structure (match ParentIds, handle multiple parents)
Step 3: Do the same for files with mime type ""image/jpeg", "image/png", ... "whatever img mimetype" (just modify the code above to get files) and parse again.
Of course the 'execute()' method will produce exceptions that should be handled as pointed out here.
... and you can take the 'not so network friendly' approach of iterating down the folder tree as seen in the 'testTree()' method here. Recursion is necessary if you have no knowledge how deep your tree structure is.
Good Luck

how to efficiently get number of subdirectories and files

In my little file explorer app I show the number of sub-elements in each directory like:
To set those numbers, I launch an AsyncTask (so the ListView won't get "laggy") from my Adapter's getView() method and that for each item in the list. However, when viewing some system directories (like "/" for example) with huge number of subdirs and files, the garbage collector is going insane and performance drops significantly (multiple instances of my AsyncTask still stay in memory even after the app gets finished).
I'm quite sure this is related to how I implemented the subdirs and subfiles check that I'm doing inside the AsyncTask, but the following recursive approach is the only thing I could think of:
//countHidden is a boolean indicating whether to count hidden files
private int[] getSubFilesCount(File root) {
int fcount = 0;
int dcount = 0;
File[] files = root.listFiles();
if (files != null)
for (File f : files) {
if (f.isDirectory()) {
getSubFilesCount(f);
if (f.isHidden()) {
if (countHidden)
dcount++;
} else {
dcount++;
}
} else {
if (f.isHidden()) {
if (countHidden)
fcount++;
} else {
fcount++;
}
}
}
int[] tcount = { fcount, dcount };
return tcount;
}
The question: is there any alternative to get the number of subdirectories and files that will work faster then the approach posted above?
You could do what's suggested here which is effectively the same as you're doing but slightly more succinct (if you don't mind me saying so). Obviously you'd have to do it for files and directories. I believe Java 7 provides a better way of doing this but unfortunately you're limited to Java 6 at the moment with Android.

Copy the shared preferences XML file from /data on Samsung device failed

There's an exporting feature in my application. It's just a copy operation since all my settings are store in shared preference.
I just copy the xml file from /data/data/package.name/shared_prefs/settings.xml to SD card. It works fine on my HTC desire. However, it might not work on Samsung devices, and i got the following error while I try to copy the file.
I/System.out( 3166): /data/data/package.name/shared_prefs/settings.xml (No such file or directory)
in the directory.
Anyone know how to fix it, or is there another simple way to store the shared preference ?
Thanks.
Never never never never never never never never never hardwire paths.
Unfortunately, there's no getSharedPreferenceDir() anywhere that I can think of. The best solution I can think of will be:
new File(getFilesDir(), "../shared_prefs")
This way if a device manufacturer elects to change partition names, you are covered.
Try this and see if it helps.
CommonsWare's suggestion would a be clever hack, but unfortunately it won't work.
Samsung does not always put the shared_prefs directory in the same parent directory as the getFilesDir().
I'd recommend testing for the existence of (hardcode it, except for package name):
/dbdata/databases/<package_name>/shared_prefs/package.name_preferences.xml and if it exists use it, otherwise fall back to either CommonsWare's suggestion of new File(getFilesDir(), "../shared_prefs") or just /data/data/<package_name>/shared_prefs/package.name_preferences.xml.
A warning though that this method could potentially have problems if a user switched from a Samsung rom to a custom rom without wiping, as the /dbdata/databases file might be unused but still exist.
More details
On some Samsung devices, such as the Galaxy S series running froyo, the setup is this:
/data/data/<package_name>/(lib|files|databases)
Sometimes there's a shared_prefs there too, but it's just Samsung's attempt to confuse you! Don't trust it! (I think it can happen as a left over from a 2.1 upgrade to 2.2, but it might be a left over from users switching roms. I don't really know, I just have both included in my app's bug report interface and sometimes see both files).
And:
/dbdata/databases/<package_name>/shared_prefs
That's the real shared_prefs directory.
However on the Galaxy Tab on Froyo, it's weird. Generally you have: /data/data/<package_name>/(lib|shared_prefs|files|databases)
With no /dbdata/databases/<package_name> directory, but it seems the system apps do have:
/dbdata/databases/<package_name>/yourdatabase.db
And added bonus is that /dbdata/databases/<package_name> is not removed when your app is uninstalled. Good luck using SharedPreferences if the user ever reinstalls your app!
Try using
context.getFilesDir().getParentFile().getAbsolutePath()
Best way to get valid path on all devices - run method Context.getSharedPrefsFile defined as:
/**
* {#hide}
* Return the full path to the shared prefs file for the given prefs group name.
*
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
*/
public abstract File getSharedPrefsFile(String name);
Because of it hidden need use reflection and use fallback on fail:
private File getSharedPrefsFile(String name) {
Context context = ...;
File file = null;
try {
if (Build.VERSION.SDK_INT >= 24) {
try {
Method m = context.getClass().getMethod("getSharedPreferencesPath", new Class[] {String.class});
file = (File)m.invoke(context, new Object[]{name});
} catch (Throwable e) {
Log.w("App TAG", "Failed call getSharedPreferencesPath", e);
}
}
if (file == null) {
Method m = context.getClass().getMethod("getSharedPrefsFile", new Class[] {String.class});
file = (File)m.invoke(context, new Object[]{name});
}
} catch (Throwable e) {
Log.w("App TAG", "Failed call getSharedPrefsFile", e);
file = new File(context.getFilesDir(), "../shared_prefs/" + name + ".xml");
}
return file;
}
On some Samsungs implements like this:
public File getSharedPrefsFile(String paramString) {
return makeFilename(getPreferencesDir(), paramString + ".xml");
}
private File getPreferencesDir() {
synchronized (this.mSync) {
if (this.mPreferencesDir == null) {
this.mPreferencesDir = new File("/dbdata/databases/" + getPackageName() + "/", "shared_prefs");
}
File localFile = this.mPreferencesDir;
return localFile;
}
}
On other Android like this:
public File getSharedPrefsFile(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
}
return mPreferencesDir;
}
}
private File getDataDirFile() {
if (mPackageInfo != null) {
return mPackageInfo.getDataDirFile();
}
throw new RuntimeException("Not supported in system context");
}
After while Google change API for level 24 and later:
https://android.googlesource.com/platform/frameworks/base/+/6a6cdafaec56fcd793214678c7fcc52f0b860cfc%5E%21/core/java/android/app/ContextImpl.java
I've tested in Samsung P1010 with:
//I'm in a IntentService class
File file = this.getDir("shared_prefs", MODE_PRIVATE);
I got:
"/data/data/package.name/app_shared_prefs"
It works fine to me. I can run ffmpeg in this folder.
Look:
Context.getDir
You have to create the shared_prefs directory:
try{
String dir="/data/data/package.name/shared_prefs";
// Create one directory
boolean success = (new File(dir)).mkdirs();
if (success) {
// now copy the file
}
}catch (Exception e){//Catch exception if any
System.err.println("Error: " + e.getMessage());
}
Also... the package of your app is package.name? Make sure you are referring to the right package.

Categories

Resources