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.
Related
I am confused with the new app storage system in Android. I am not sure where my use case falls under and I need your help in telling me the right approach for this
My app captures images and generates pdf documents. Prior to Android 10, I used to store them in an app directory where the user can easily navigate to them through other files browsing app (like Files app on Samsung). In addition, these files can be accessed from within my app (so essentially read and write).
With the new storage, I am not sure how to accomplish the same thing. If I use the internal storage then user can't see them. If I use the media approach, well it seems it is only for Audio/video plus they will not be organized in a folder like I have them organized.
Am I missing something? How would I solve this problem?
Thank you
On an Android 11 device you can store your files in a subdirectory of the public Documents directory.
You can do that using classic File means or the media store or SAF.
Other apps can see them using SAF or the media store. Or with classic file means when requested all files access.
The user can see them using the default Files app on the device.
I have read about and think I understand the essentials of changes in Android 10 and 11. Gone are the days of accessing folders and files outside of the Android app sandbox willy nilly. That's fine. Just need a way forward and that's become difficult.
I have 2+ apps that share a local Sqlite database and related files in a folder. One or more of the apps in the group might be installed - no guarantee on which of the apps are present. On iOS and Windows (UWP) there is a nice "app group" (iOS name for it) style concept that supports this kind of arrangement formally in the platform. First one installed/run will create the local storage files. Last app in the group uninstalled and the OS cleans up the shared storage location. Android never had this concept so a common location was created outside of the app specific sandbox.
After studying the options available going forward, seems like the "Best" option was to use the Storage Access Framework (SAF) to get permission from the user for some common folder to use. Note that although there are many different "sharing" options in Android, none of them are great for this use case, and most are not friendly to cross platform Xamarin C# without wrapping them somehow. This "Best" option using SAF still requires the user to independently pick the SAME folder from each app that wants to share the local db/files. You know users are going to mess that up, but that's beside the point at the moment.
In testing this approach, I have been able to use the SAF picker to get the user to choose a folder. The Documents folder is what I've been choosing to test with as a folder. From there the app attempts to create a subfolder where all this shared "app group" content would go. Unfortunately simply doing a Directory.CreateDirectory(path) gives a System.IO.IOException: 'Read-only file system'. I checked am I am still able to do Directory.CreateDirectory(path) in the app sandbox (GetExternalFilesDir), just not the SAF chosen location.
I am also able to create a directory in the SAF location if I stick to the SAF API, such as illustrated in the Xamarin Android sample here: https://github.com/xamarin/monodroid-samples/blob/master/android5.0/DirectorySelection/DirectorySelectionFragment.cs#L169-L188.
Is there any way to treat the SAF location chosen by the user just like a normal file system and use System.IO operation to manipulate it? The app has been given permission but those ops don't seem to work in that location. Or is there a better overall approach to this problem that I've totally missed?
Normal Java File I/O does not work with Scoped Storage. File paths and File or Directory objects do not worked in Storage Access Framework, you have to do everything through the DocumentFile API. DocumentFile has the ability to create files and directories in locations that the user has granted your app access to through the File-picker dialog.
There IS a way for normal/traditional System.IO file I/O to work after converting the SAF content to a classic file system path. Using the FileUtil logic in this answer https://stackoverflow.com/a/36162691/1735721 I was first able to get permission to a folder from the user:
var intent = new Intent(Intent.ActionOpenDocumentTree);
StartActivityForResult(intent, 1);
The in OnActivityResult(_, _, Intent resultData) use the file util logic:
var folderPath = FileUtil.GetFullPathFromTreeUri(resultData.Data, this);
var filePath = Path.Combine(folderPath, "test.txt");
At that point filePath represents the path and filename in the chosen directory tree, and normal C# System.IO operations are available to the app for that file e.g. StreamWriter and StreamReader.
NOTE: I was creating "test.txt" directly in the chosen folder. This worked to create the file in "A" but then "B" couldn't read that same file (Unauthorized exception). At some point I created a subfolder and "test.txt" was created there instead...then both "A" and "B" could read and write the same file. Unfortunately, a couple days later, I couldn't repeat that. So as it stands this is only a partial solution.
In my app the user can select an existing file or just write a plain file path. Later the app uses the path to perform operations on selected file. I'm trying to study a strategy to face how filesystem access is changed in Android Q.
I see two problems:
I can't use anymore file paths or just use File interface but I need to use a Uri and I need persistent access to this Uri.
Uri are not "predicatable", i.e. if the file doesn't exist at the moment the user can't select anything and it can't guess what will be the Uri (right?)
For point one I could use ACTION_OPEN_DOCUMENT_TREE with persistent read/write on the root to have access to all files, but I have no idea how to manage point two. To be noted that the file is not created by my app but it could be created later from another app and my app just use it. How can I manage point 2?
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.
Android introduced the Multiple Users feature in 4.2 (Jelly Bean MR1) and its documentation states:
From your app’s point of view, each user is running on a completely separate device.
And here is a quote from the Environment.getExternalsStorageDirectory() and getExternalStoragePublicDirectory() methods doc:
On devices with multiple users (as described by UserManager), each user has their own isolated external storage. Applications only have access to the external storage for the user they're running as.
Could it be true that there really is no reliable way to communicate data between users on a single device without using the network as mediator? I'm looking for solutions that don't rely on quirks of how the device's file system is laid out by a manufacturer. Also, for security, the sharing should be internal to my app.
Even if file sharing is indeed impossible, is communication via intents somehow possible?
There are use cases for this. Use Case 1: let's say I'm writing an input method app that requires a 100MB dictionary file. I'd like to code things so that if User A downloads this file, then User B can access it also without needing to re-download. Use Case 2: let's say I'm writing a fun Leave My Wife a Note app that allows User A to type messages that will appear next time User B logs in (without using the network).
This thread on a separate site proposes a solution, but their method seems undocumented and possibly unreliable. And there are a few other SO questions that have a title similar to this one but are actually discussing different topics.
OBB Folder (/sdcard/Android/obb) is used to share files and folder between the multi users. But OBB folder not shown in my second user (One plus 5 mobile). So I have tried to create an OBB folder in Android folder (/sdcard/Android/) in second user and "BOOM" it worked. Now i am able to access the shared files in second user. Try this trick if OBB folder not shown in your second user.
OBB files (stored in /sdcard/Android/obb) and used as expansion files in Google Play are shared between all users by design, as they are fairly large. If you Input method uses expansion files, the downloaded data will be shared automatically. You can send broadcasts to other users but that requires the INTERACT_ACROSS_USERS permission, which is reserved for system applications.
I also had the same question, and have tried various approaches such as using /sdcard/Android/obb but it does not work in Android 10. So I followed below approach, and I am able to copy files seamlessly between users.
Login to the User from where you would like to copy files from (lets call U1)
Run FTP Server using any application of choice like MiXplorer / ES Explorer etc... Note down the details of the port#, username, password etc... and point it to /sdcard
Switch user, to where you want to copy files to (lets call U2)
Install the FTP browser. If you use MiXplorer / ES Explorer, they will allow you to add a FTP share
Use ftp://localhost:2121 assuming the port is 2121, if not change it accordingly and add the FTP share
Open the FTP share and you can see all the files & folders of U1 here
Copy across to your heart's content !