I wanted to know the best approach to passing information from an app to another app on the same devices in android.
For example:
I open google apps and I share a document with my App A.
Google App generated an intent and sends a content URI. From my
understanding, the content uri contains information about the file
(filename, file size, mimetype) and the ability to extract the
content which is located in the cache of the google app on the
device.
When App A opens, it reads the content URI. Ideally, it
should be able to extract the information from the content uri and
then render the image. What this means is that App A will display the image shared. In this example, google app shares a docement, and App A wants to open and display the document within it's own app.
The confusing part
From searching the web, it seems that some people actually try to
extract the file path from the content URI. This requires that you
have permission to access another app's cache or storage space
within the device. Let's say this is possible. It also makes some
assumptions that it's possible to extract the file path.
After reading some articles:
https://commonsware.com/blog/2016/03/14/psa-file-scheme-ban-n-developer-preview.html
https://commonsware.com/blog/2014/07/04/uri-not-necessarily-file.html
https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
it seems that, ideally you should never assume that you can extract the file path and that google has made some updates that makes this not possible.
Work around:
Eventhough i'm not able to extract the file path from the
contentUri, I'm able to read the bytes of what the contentUri is
pointing to. So I could save it to a file that is relevant to the
local cache of App A and pass that path along to get render or pass
the bytes back. This refers to App A displaying the content. That is passing the path or bytes and let's make the assumption that it knows how to display it given that information.
Question:
The work around does not seem ideal because technically you are
save the file again on the device. There are two locations with the
same content ( google app storage and App A's storage). You also
have to manage when to delete the App A's file that you created.
This doesn't really seem ideal and was wondering what the best
approach would be? Or is this the expected flow?
Also I don't know
if it's ideal to pass the bytes back up vs. just a file path.
Update
To be more specific, the app i'm creating is a hybrid where i'm using cordova plugin to interact with a web app. The web app has methods to process or display the shared document based on file path. So ideally I want to keep it consistent with just reading the file path so that the other platforms that the web app supports does not break.
Any advice appreciated,
D
Eventhough i'm not able to extract the file path from the contentUri, I'm able to read the bytes of what the contentUri is pointing to.
Correct. This is not significantly different than how you use an HTTPS URL, where you also do not have direct filesystem access to the content (in that case, resident on a different server).
So I could save it to a file that is relevant to the local cache of App A and pass that path along to get render or pass the bytes back.
Or, just consume the bytes. Again, drawing an analogy to an HTTPS URL, there is no requirement to save those bytes to disk to use them.
The work around does not seem ideal because technically you are save the file again on the device. There are two locations with the same content ( google app storage and App A's storage). You also have to manage when to delete the App A's file that you created.
Then do not save the file again on the device, and simply use the stream of bytes. Again, this is not significantly different than using an HTTPS URL.
This doesn't really seem ideal and was wondering what the best approach would be?
Do not write the bytes to disk. Just use them.
So ideally I want to keep it consistent with just reading the file path so that the other platforms that the web app supports does not break.
Your choices are:
Improve the Web app code, such that a local file path is one possible source of the data, or
Suffer the problems with making copies of that data
After all, bear in mind that the Uri you are given via ACTION_SEND does not have to be a content Uri. It could very easily be an http or https Uri.
Related
Is it somehow possible to get a File object from an DocumentFile? With some small trick I can get the real path of the file, but the File class is not allowed to read/write there...
I need to access media files from USB OTG or secondary storage and I need following functions:
get exif data from images => I really need that for
getting the location of the image
getting the real creation date of the image
actually, for displaying purpose, I need all exif data
ability to rotate images (this would be possible by creating a temp image, delete the old one and rename the temp image)
Any idea on how to achieve that?
Is it somehow possible to get a File object from an DocumentFile?
No.
With some small trick I can get the real path of the file
Not reliably. After all, the Storage Access Framework does not require there to be an actual file. Various DocumentProvider implementations work off of cloud services, where the data is not stored locally on the device until needed. Beyond that, whatever approach that you are using is dependent upon internal implementation that may vary by device, let alone Android OS version. And, to top it off, you still cannot necessarily access the data even if you derive a path, as you may not have filesystem access to that location (e.g., files held on internal storage by the DocumentProvider).
get exif data from images
Use the stream, along with EXIF code that can work with streams, such as this one or this one, found by searching the Internet for android exif stream.
ability to rotate images (this would be possible by creating a temp image, delete the old one and rename the temp image)
You don't have a choice to make a local copy, rotate the image, and then write the image back to the original DocumentFile using an OutputStream. Whether that "local copy" is only in RAM, or needs to be an actual file on disk, depends a bit on how you were planning on rotating it.
I am adding support for importing a specific type of file format from another (Windows) app. This particular format keeps the data in a pair of files. These files use the same filename, with different extensions, eg myfile.ext and myfile.ex2.
The scenario is as follows:
The user selects myfile.ext from dropbox, google drive or other
By knowing the filename/path, I want to resolve myfile.ex2 at the same location and open it
The problem is, that when using the URI provided from the file chooser, the URI's looks like this:
content://com.google.android.apps.docs.storage/document/acc%3D1%3Bdoc%3D154
There are several solutions here involving getting the real file name using getContentResolver(), and pick the original file name from the returned cursor.
Is there a generic way to obtain an absolute path with the actual file name, change extension and then open it ?
There are several solutions here involving getting the real file name using getContentResolver(), and pick the original file name from the returned cursor.
None of which will work reliably, particularly with the providers that you cited.
Is there a generic way to obtain an absolute path with the actual file name, change extension and then open it ?
No.
First, there is no absolute path, because the files in question do not have to be on the device. After all, "dropbox, google drive or other" are cloud services, at least for many values of "other". There is no requirement that just because a cloud service downloaded one file to the device, that is has to download all files to the device.
Second, there is no absolute path that you can necessarily use, because an intelligently-written "dropbox, google drive or other" will have the files on the app's portion of internal storage, which your app cannot access.
Third, a Uri is an opaque handle, just as a URL is. There is no requirement that the Web sites of "dropbox, google drive or other" provide Web URL relative addressing between two arbitrary files held by their services. Similarly, there is no requirement that the on-device file providers provide Uri relative addressing between two arbitrary files held by their services, or even to use filenames that are recognizable.
The main options that I see for you are:
Have the user pick each file individually.
Have the user combine the files, such as putting them in a ZIP archive.
Have the user pick each file, but in one Storage Access Framework ACTION_OPEN_DOCUMENT operation, via EXTRA_ALLOW_MULTIPLE (warning: only practical if your minSdkVersion is 19 or higher, and you will have to handle the case where the user screws up and does not choose exactly two files).
Have the user organize the files, such as putting them in a single directory/folder, and use the Storage Access Framework's ACTION_OPEN_DOCUMENT_TREE Intent (warning: only practical if your minSdkVersion is 21 or higher).
Switch to using direct Web service APIs for whatever service(s) you wish to integrate with, if they, through their APIs, offer something more amenable to you.
In my application I need to pass a dynamically decrypted file to a third-party application without saving it to the device.
Example: I have a self created encrypted file which contains both a pdf file and some requirements before the pdf file can be shown. If all requirements are true, that pdf file should be shown by a third-party PDF-reader.
So I need to start a new intent, but there is the problem. I have to give the URI of my pdf file, but I don't have a URI because I didn't save the file to the device.
Is there any way I can get this job done?
For very small PDFs, or PDFs encrypted with some sort of streaming encryption algorithm, you can create a pipe ContentProvider. Using a pipe, you basically pour data into an OutputStream, where the other side uses a Uri and ContentResolver to retrieve the corresponding InputStream.
However, the limits of heap space will severely constrain the size of the file, if you cannot process it in a streaming fashion (e.g., as you read the bytes in from HTTP, decrypt on the fly and pass the decrypted bytes to the OutputStream).
Here is a sample of creating such a ContentProvder.
Answer on my own question:
Together with the answer from CommensWare (and sample code) and this link I have found a semi-solution.
The sample code shows you how to make local files accessible for other applications with a content provider.
The second link describes the implementation of a delete method which deletes the file from the file structure even before it is completely opened by a third party application (and explains why this is possible).
So basically, after decryption, you create a file accessible from the content provider, open it with another application and delete it immediately.
For rooted phones this is still not a 100% solution because they could monitor local file structure changes and instantly copy the file after it has been created.
I'm looking for a solution (possibly implementing it) that would allow me to create a cache on my SD card (say, 25G) which would be used by some "caching layer" to store media on demand. The issue being that all of my media doesnt fit on a 32G SD card, so I want to be able to transparently have my missing data downloaded (ie: I dont want to have to download using another app) while I am attempting to play it in the stock media player or the Gallery or whatever. Does anyone know of an existing solution like this? I'm already familiar with the various dedicated apps out there which do this for music, but I'm looking more for a generic cache which caches anything that passes through it on an LRU basis. I want my NAS at home (or on S3, dropbox, Ubuntu One, whatever) to be an extension of my Android device(s).
If one doesnt exist, is it possible to chain ContentProviders? Could I create a CacheProvider which keeps track of what is currently in my cache and what isnt, and use the URIs from the CacheProvider to populate the MediaStore? All examples I can find so far show that what comes out of the MediaStore is always a URI to a file, so I suspect its not currently possible.
After a little POC, it appears that this is not possible using ContentProviders alone. While it is possible to chain providers together if you override ContentProvider.openfile(), since the MediaProvider currently assumes that everything in _data points to a file or an HTTP stream, the Music app will fail to play any content which sticks another content URI into _data. If MediaProvider were changed to handle content URIs then this would be possible, but I think the only way to achieve that would be to copy/paste the MediaStore framework and rename it all, or to do the change in a mod (like CyanogenMod).
On Android I understand an app can ingest an image file from the user's Downloads folder, and then use that image to direct content inside the app. Amazon MP3 Cloud Player is good example.
I understand that on iOS the Saved Photos folder can be accessed by apps as well, using the UIImagePickerController Class Reference. I understand that I can limit which media assets can be browsed, just video or just photos, but can I tell the app to only launch the media asset picker for the user ONLY if an exact-match to photo file extension is made in the first-place?
In other words: IF in the Saved Photos folder there is a .ONE or a .TWO file present, then launch the media browser, and only show .ONE or .TWO files, if not, do not launch the media browser.
I want to be able to use a custom file extension -not the standard PNG, JPG, etc...
Ultimately my goal is to use this custom image file as a kind of token that tells the app the user has done something in the browser to acquire the image file, and therefore is entitled to a special experience in the app.
What you want is not a UIImagePicker, because you need to enumerate user photos programmatically. Have a look at ALAssetsLibrary, with which you can enumerate ALAssets, which are proxy objects for saved photos (and videos).
Upon further reading: I don't think your custom extension idea would work. And it shouldn't, because you're exposing implementation details to the user by putting certain specialized files into a content library where they don't belong (which probably won't work). Do not misuse OS facilities like that. It will only get your app rejected from the app store (if it even works), create more headaches in implementation, and create very poor user experience. While this might not be enforced on Android, it is a bad idea even there, but on iOS, you won't even be able to release your app to the public.
See this question for how to declare your app to open specific document types (which means that when the user downloads a .yourSpecialFileType file in Safari, it will launch your app and hand the file over to you).