Get file path from Google drive in Lollipop (MediaStore.MediaColumns.DATA == null)

When the user clicks the "send file" button on the Google drive and selects my application. I want to get the file path of this file, and then allow the user to upload it to another location.

I check these similar SO posts for kitkat phones: Get the real path from URI, the new KitKat Android storage access system

Android - convert URI to file path on candy

However, a solution to this problem no longer works on Lollipop devices.

The problem is that MediaStore.MediaColumns.DATA returns null when the query is run in ContentResolver.

https://code.google.com/p/android/issues/detail?id=63651

You should use ContentResolver.openFileDescriptor () instead of trying to get the path to the source file system. The column "_data" is not part of the CATEGORY_OPENABLE contract, so it does not need to return it.

I read this CommonsWare blog post saying that I am “trying to use Uri directly with ContentResolver” which I don’t understand. How to use URI directly with ContentResolvers?

However, I still do not understand how best to approach these types of URIs.

The best solution I could find is to call openFileDescriptor and then copy filestream to a new file and then transfer this new path to my upload activity.

private static String getDriveFileAbsolutePath(Activity context, Uri uri) { if (uri == null) return null; ContentResolver resolver = context.getContentResolver(); FileInputStream input = null; FileOutputStream output = null; String outputFilePath = new File(context.getCacheDir(), fileName).getAbsolutePath(); try { ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r"); FileDescriptor fd = pfd.getFileDescriptor(); input = new FileInputStream(fd); output = new FileOutputStream(outputFilePath); int read = 0; byte[] bytes = new byte[4096]; while ((read = input.read(bytes)) != -1) { output.write(bytes, 0, read); } return new File(outputFilePath).getAbsolutePath(); } catch (IOException ignored) { // nothing we can do } finally { input.close(); output.close(); } return ""; } 

The only problem here is that I am losing the file name of this file. This seems a bit trickier just to get filePath from disk. Is there a better way to do this?

thank.

EDIT: Therefore, I can use a regular query to get the file name. I can then pass this into my getDriveAbsolutePath () method. Which brings me pretty close to what I want, the only problem right now is that I don't have enough file extensions. All the search queries I have done recommend using the file path to get the extensions, which I cannot do with openFileDescriptor (). Any help?

  String filename = ""; final String[] projection = { MediaStore.MediaColumns.DISPLAY_NAME }; ContentResolver cr = context.getApplicationContext().getContentResolver(); Cursor metaCursor = cr.query(uri, projection, null, null, null); if (metaCursor != null) { try { if (metaCursor.moveToFirst()) { filename = metaCursor.getString(0); } } finally { metaCursor.close(); } } 

However, I'm not quite sure if this is the “right” way to do this?

+5
android android-intent google-drive-sdk storage-access-framework
Mar 31 '15 at 9:11
source share
1 answer

The only problem here is that I am losing the file name of this file. This seems a bit trickier just to get filePath from disk. Is there a better way to do this?

You seem to miss an important point here. Files on Linux do not have to have a name. They can exist in memory (for example, android.os.MemoryFile ) or even be in a directory without a name (for example, files created with the O_TMPFILE flag). They must have a file descriptor.




Short description: file descriptors are better than simple files and should always be used instead, if you do not close them after you have too much burden. They can be used for the same things as File objects, and much more if you can use JNI. They are accessible by a special ContentProvider and can be accessed through the openFileDescriptor ContentResolver method (which receives the Uri associated with the target provider).

However, just talking people used for File objects to replace them with descriptors probably sound strange. Read the detailed explanation below if you would like to try it. If you do not, just go to the bottom of the answer for a “simple” solution.

EDIT: The answer below was written before Lollipop became widespread. Currently, there is a convenient class for direct access to Linux system calls, which makes the use of JNI to work with file descriptors optional.




Quick descriptor briefing

File descriptors come from the Linux open system call and the corresponding open() function in the C library. You do not need to have access to the file to work with the descriptor. Most access checks are simply skipped, but some important data, such as the type of access (read / write / read and write, etc.), is hardcoded into the descriptor and cannot be changed after it is created. File descriptors are represented by non-negative integers starting from 0. These numbers are local to each process and do not have any constant or system-wide value, they simply distinguish file handles from each other for this process (0, 1 and 2 are traditionally referred to as stdin , stdout and stderr ).

Each descriptor is represented by a reference to an entry in the descriptor table stored in the kernel of the OS. There are end-to-end and system-wide limits for the number of entries in this table, so quickly close your descriptors if you do not want your attempts to open things and create new descriptors in order to suddenly fail.

Work with descriptors

There are two kinds of library functions and system calls in Linux: working with names (for example, readdir() , stat() , chdir() , chown() , open() , link() ) and working on descriptors: getdents , fstat() , fchdir() , fchown() , fchownat() , openat() , linkat() , etc. You can easily call these functions and system calls after reading several manual pages and learning some kind of dark magic of JNI. This will improve the quality of your software through the roof! (just in case: I'm talking about reading and studying, and not just blindly using JNI all the time).

Java has a class for working with descriptors: java.io.FileDescriptor . can be used with FileXXXStream classes and thus indirectly with all IO I / O classes, including memory mapped files and random access files, channels and channel locks. This is a difficult class. Due to the requirement to be compatible with some native OSes, this cross-platform class does not provide a base integer. It can’t even be closed! Instead, you should close the corresponding I / O classes, which (again for compatibility reasons) share the same base descriptor with each other:

 FileInputStream fileStream1 = new FileInputStream("notes.db"); FileInputStream fileStream2 = new FileInputStream(fileStream1.getFD()); WritableByteChannel aChannel = fileStream1.getChannel(); // pass fileStream1 and aChannel to some methods, written by clueless people ... // surprise them (or get surprised by them) fileStream2.close(); 

There are no supported ways to get an integer value from FileDescriptor , but you can (almost) safely assume that older versions of the OS have a private integer descriptor that can be accessed through reflection.

Foot shot with descriptors

In the Android platform, there is a specialized class for working with the Linux file descriptor: android.os.ParcelFileDescriptor . Unfortunately, this is almost as bad as FileDescriptor. What for? For two reasons:

1) It has a finalize() method. Read this javadoc to find out what this means for your work. And you still have to close it if you don't want to run into sudden I / O errors.

2) Due to the fact that it is finalized, it is automatically closed by the virtual machine as soon as the reference to the class instance is beyond the scope. This is why finalize() for some infrastructure classes, especially MemoryFile is a mistake for Framework developers:

 public FileOutputStream giveMeAStream() { ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY); return new FileInputStream(fd.getDescriptor()); } ... FileInputStream aStream = giveMeAStream(); // enjoy having aStream suddenly closed during garbage collection 

Fortunately, there is a remedy for such horrors: a magic dup system call:

 public FileOutputStream giveMeAStream() { ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY); return new FileInputStream(fd.dup().getDescriptor()); } ... FileInputStream aStream = giveMeAStream(); // you are perfectly safe now... // Just kidding! Also close original ParcelFileDescriptor like this: public FileOutputStream giveMeAStreamProperly() { // Use try-with-resources block, because closing things in Java is hard. // You can employ Retrolambda for backward compatibility, // it can handle those too! try (ParcelFileDescriptor fd = ParcelFileDescriptor.open("myfile", MODE_READ_ONLY)) { return new FileInputStream(fd.dup().getDescriptor()); } } 

The dup syscall script clones an integer file descriptor, making the corresponding FileDescriptor independent of the source. Please note that the transfer of descriptors in different processes does not require manual duplication: the received descriptors do not depend on the original process. Passing the MemoryFile descriptor (if you received it with reflection) fulfills the dup request: with the shared memory shared in the original process, it will be inaccessible to everyone. In addition, you need to either dup in your own code or save the link to the created ParcelFileDescriptor until the receiver is executed using the MemoryFile .

Giving and receiving descriptors

There are two ways to give and receive file descriptors: by ensuring that the child process inherits the descriptors of the creator and through interprocess communication.

Providing children with the process of inheriting files, pipes, and sockets opened by the creator is a common practice in Linux, but requires formatting in Android native code - Runtime.exec() and ProcessBuilder close all additional descriptors after creating the child process. Make sure to close unnecessary descriptors if you choose to fork yourself.

The only IPC entities that currently support file descriptor transfers on Android are the Binder and Linux domain sockets.

Binder allows you to assign the ParcelFileDescriptor everything that allows a lot of objects, including placing them in the Bundles, returning from content providers, and transferring through AIDL services.

Note that most attempts to pass Bundles with descriptors outside the process, including calling startActivityForResult , will be rejected by the system, likely because closing those descriptors in time would be too complicated. A much better choice is to create a ContentProvider (which manages the descriptor life cycle for you and publishes files through the ContentResolver ) or writes the AIDL interface and closes the descriptor immediately after it is transferred. Also note that saving ParcelFileDescriptor doesn't make sense anywhere: it will only work until the death of the process and the corresponding integer are likely to point to something else as soon as your process is recreated.

Domain sockets are low level and a bit sick to use for descriptor transfer, especially when compared to providers and AIDL. However, they are a good (and only documented) option for their own processes. If you are forced to open files and / or move data using your own binary files (this usually applies to applications using root privileges), consider that you do not spend your efforts and CPU resources on complex communications with these binary files, instead write open assistant. [shameless announcement] By the way, you can use the one I wrote , instead of creating your own. [/ shameless announcement]




The answer to the exact question

I hope this answer gave you a good idea what is wrong with MediaStore.MediaColumns.DATA, and why creating this column is the wrong cathedral from the Android development team.

However, if you are still not sure whether you want this file name at all costs or just do not read the vast wall of text above, here is the JNI function, ready to go; inspired by Getting the file name from a file descriptor in C ( EDIT : now has a pure-Java version ):

 // src/main/jni/fdutil.c JNIEXPORT jstring Java_com_example_FdUtil_getFdPathInternal(JNIEnv *env, jint descriptor) { // The filesystem name may not fit in PATH_MAX, but all workarounds // (as well as resulting strings) are prone to OutOfMemoryError. // The proper solution would, probably, include writing a specialized // CharSequence. Too much pain, too little gain. char buf[PATH_MAX + 1] = { 0 }; char procFile[25]; sprintf(procFile, "/proc/self/fd/%d", descriptor); if (readlink(procFile, buf, sizeof(buf)) == -1) { // the descriptor is no more, became inaccessible etc. jclass exClass = (*env) -> FindClass(env, "java/io/IOException"); (*env) -> ThrowNew(env, exClass, "readlink() failed"); return NULL; } if (buf[PATH_MAX] != 0) { // the name is over PATH_MAX bytes long, the caller is at fault // for dealing with such tricky descriptors jclass exClass = (*env) -> FindClass(env, "java/io/IOException"); (*env) -> ThrowNew(env, exClass, "The path is too long"); return NULL; } if (buf[0] != '/') { // the name is not in filesystem namespace, eg a socket, // pipe or something like that jclass exClass = (*env) -> FindClass(env, "java/io/IOException"); (*env) -> ThrowNew(env, exClass, "The descriptor does not belong to file with name"); return NULL; } // doing stat on file does not give any guarantees, that it // will remain valid, and on Android it likely to be // inaccessible to us anyway let just hope return (*env) -> NewStringUTF(env, buf); } 

And here is the class that goes with it:

 // com/example/FdUtil.java public class FdUtil { static { System.loadLibrary(System.mapLibraryName("fdutil")); } public static String getFdPath(ParcelFileDescriptor fd) throws IOException { int intFd = fd.getFd(); if (intFd <= 0) throw new IOException("Invalid fd"); return getFdPathInternal(intFd); } private static native String getFdPathInternal(int fd) throws IOException; } 
+8
May 17 '15 at 5:44
source share



All Articles