I'm currently building a .Net Maui App that is only targeting android. I need to save some data in the public external storage that I can access and copy to my PC and that persists even if I uninstall the app. I choose the Documents directory.
The following does the job:
string dir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments).AbsolutePath
But I get a warning from my IDE that GetExternalStoragePublicDirectory is deprecated.
I've found several postings that
Android.App.Application.Context.GetExternalFilesDir(Android.OS.Environment.DirectoryDocuments)
will also work, but that does not return the same result.
The deprecated method returns
/storage/emulated/0/Documents
while the other one returns
/storage/emulated/0/Android/data/com.companyname.myappname/files/Documents
Hence the appname is in the path, this is not persistent in case the app is uninstalled. So what is the correct way to get the public external documents directory?
I had done a sample to test the MediaStore and the Context api. According to your description, you want to save some data to the file in the Document folder.
For the Context
All the files or directories you get from it belong to the application, when user uninstall the app from the devices, all of these will be deleted from the device. So this doesn't meet the requirement.
For the MediaStore
This api can be used to create file in the public folder, but the file can just be accessed by the app which create it. When user uninstall the application, you can't access it either.
And the google suggest the developer use the ACTION_CREATE_DOCUMENT to let the user create a file by himself and then user can open it by the ACTION_OPEN_DOCUMENT.
So even though the Android.OS.Environment.GetExternalStoragePublicDirectory method has marked as deprecated, it is the easier way to get the result you want.
For more information, you can check this case and the official document.
Related
I want to show a file browser to the user, let him choose an arbitrary file he wants, and read it using the ZipFile class. The class requires a File object, not an InputStream.
If my research is correct, the in-built file choosers (ACTION_OPEN_DOCUMENT, etc) only provide a stream, not a File, right? Even if I get the file name from the chooser and have acquired READ_EXTERNAL_STORAGE from the user, it seems that in recent Android versions, the API only allows "only media" and reading files of other types seems to fail.
If I have also acquired MANAGE_EXTERNAL_STORAGE, then it works, but that shows a scary warning like following to the user.
Allow this app to access, modify, and delete files on the device or any connected storage devices? This app may access files without asking you.
That seems to be an unnecessarily broad permission when all I want to is a read-only access to File for a file that the user has explicitly chosen in the file browser. There is no other way than acquiring MANAGE_EXTERNAL_STORAGE, is that right? I want to make it sure, because it sounds weird. Even a very restricted environment like a web browser, the browser does not require any special permission or show any warning for reading a file that the user has explicitly selected, and the same was true for UWP app.
If my research is correct, the in-built file choosers (ACTION_OPEN_DOCUMENT, etc) only provide a stream, not a File, right?
Correct. After all, the user can choose something that you cannot access via the filesystem, either due to permissions or due to the fact that it simply isn't on the filesystem (e.g., cloud storage, SMB/CIFS file server).
IOW, what you refer to as "file choosers" are not limited to what you seem to be thinking of as files.
If I have also acquired MANAGE_EXTERNAL_STORAGE, then it works
I do not know what "it" is. You cannot hack into Google Drive to get a File object using MANAGE_EXTERNAL_STORAGE, for example. Nor can you get a File object for a document contained as a BLOB in an encrypted database, etc.
There is no other way than acquiring MANAGE_EXTERNAL_STORAGE, is that right?
You cannot do what you want even with MANAGE_EXTERNAL_STORAGE. A Uri is not a file.
Your options are:
Copy the content identified by the Uri to some file that you control (e.g., in getCacheDir()), so you can use ZipFile.
Relax the ZipFile constraint. For example, perhaps you could use ZipInputStream, or find a third-party ZIP reader that supports InputStream (such as this or this). You can get an InputStream on the content identified by the Uri via openInputStream() on ContentResolver.
Relax the "in-built file choosers" constraint, and implement your own "file chooser" with files that your app can read on the filesystem. That way, you are guaranteed that you can use File, since you used File to get to them in the first place.
We are implementing a backup/restore system for the app. We used Google Drive API as documented in the Android Guide
We also store image URIs in the database. In the restoring process, we get URIs but lost permission to reach images when we re-install the app. We have the following exception.
Failed query: java.lang.SecurityException: Permission Denial: opening
provider ... requires that you obtain access using
ACTION_OPEN_DOCUMENT or related APIs
We get image URI via "ACTION_OPEN_DOCUMENT" but can't persist it after deleting/install the application. What are the related APIs? Do we need to move images in an app-specific folder and backup images itself also?
Thank you for reading this, any help is appreciated
What are the related APIs?
ACTION_OPEN_DOCUMENT_TREE and ACTION_CREATE_DOCUMENT are probably what they are referring to.
Do we need to move images in an app-specific folder and backup images itself also?
We cannot really answer that — that is a business decision as much as a technical one. However, if your app is uninstalled and reinstalled:
You lose access to content identified by Uri values that you obtained from the Storage Access Framework
On Android 10+, you lose access to any files that your older app installation created that survived the uninstall process (e.g., they were in a shared collection)
How you work around that is up to you. In addition to your proposal, you could:
Have all the images be stored in a single directory tree (obtained via ACTION_OPEN_DOCUMENT_TREE), store relative paths in your database, and request access to that tree again after your app is restored
Remove all of the Uri values from the database before backing it up and live without access to those images if the database is restored
Store the image data in the database itself (typically not recommended but technically possible)
I am working on logs inside android, I thought two ways for storing logs, and one is on external directory as a text file or a log file while other is to store in database. I found database method more useful in my case. My question is if I UN-install and reinstall the app will the database will be affected? In case of yes what should I do? I cannot place the logs online. How to take the backup or safe that database so it won’t be affected in case of UN- installation.
My question is if I uninstall and reinstall the app will the database will be affected?
Yes, your database will by default be stored in the application's data directory, which is deleted along with your application on uninstallation.
You can instead write a file of a filetype of your choosing (whether that's a simple text file, or a database file) to the external storage. You can obtain the directory path using:
Environment.getExternalStoragePublicDirectory(type) for obtaining a directory path of files of a specific type, such as images or videos;
Environment.getExternalStorageDirectory() for obtaining the primary external storage directory, under which you could create a new path.
I would nevertheless discourage you from doing this, because it would require your users to manually dispose of any files after uninstalling your application. Perhaps you should reconsider the justification of choosing for this solution.
If you uninstall the app, then the database (and all other data stored in the apps private storage for that matter) will be gone.
You could store your logs in the public external storage, but this will expose your logs to other applications as well as the user.
One possible approach could be to use application private storage for your 'live' logs, and make periodic backups to the public storage. In case of a new installation, you can check your designated backup location and attempt to restore previous logs from the backup.
I have a lite version of an application that uses a SQLite database. I want to copy that database over to the full version of the application when the user installs the full version.
I have written some code to perform the file copy, but the lite database file always comes up as unreadable. The file is there and I can point to it, but I can't read it to perform the copy.
In the Android documentation, we read:
You can save files directly on the
device's internal storage. By default,
files saved to the internal storage
are private to your application and
other applications cannot access them
(nor can the user).
Note the words, "by default".
Is there a way that I can override that default and make the SQLite file readable by my other application?
Thank you.
I believe you have 2 options.
Set the sql database to be world readable on creation. You can do this by setting the appropriate mode parameter in the call to openFileOutput() or openOrCreateDatabase().
Set the sharedUserId attribute in the manifest of both of your applications so that they have the same user ID. This treats both applications as the same user, giving both applications access to the same private set of files.
The two apps have the same sharedUserId. When I use this code in app1
context.openFileOutput("/data/data/org.me.app2/files/shared-data.dat", MODE_PRIVATE)
I get an exception telling me that the file contains a path separator.
I am trying to write a file from app1 into app2's storage. (I do of course need to make sure that app2's files directory exists first)
Ideally, I would write to a user specific directory instead of an app specific directory, but I do not know if that can be done
First of all, NEVER use a full path to internal storage like /data/data. Let the operating system give you the path (for example, via Context.getFilesDir() or Environment.getExternalStorageState()). Don't make assumption on where the data is.
Secondly - you already are doing that! Unlike File, Context.openFileOutput already prepends /data/data/[package] to your path, so you don't need to specify that. Just specify the file name.
If you really feel that it's safe and necessary, and if both apps share the same user ID using android:sharedUserId in the manifest, you can get a context of the other app by using Context.createPackageContext() and use CONTEXT_RESTRICTED, then use openFileOutput with only the file name.
Open a FileOutputStream of the needed file, relative to this path:
String filePath = getPackageManager().
getPackageInfo("com.your2ndApp.package", 0).
applicationInfo.dataDir;
Since this is months old I assume you've already solved your problem, but I'll contribute anyway.
Sharing data between apps is what ContentProviders are for. Assuming that you know how to write a ContentProvider and access it, you can access files via ParcelFileDescriptor, which includes constants for the mode in which you create the files.
What you need now is to limit access so that not everybody can read the files through the content provider, and you do that via android permissions. In the manifest of one your apps, the one that will host the files and the content provider, write something like this:
<permission android:name="com.example.android.provider.ACCESS" android:protectionLevel="signature"/>
and in both apps add this:
<uses-permission android:name="com.example.android.provider.ACCESS" />
by using protectionLevel="signature", only apps signed by you can access your content provider, and thus your files.
You should not be overwriting other applications files. That said you have two solutions
Use public external storage (like the SD card) to share the file between the apps.
If the other app is not yours then you can't write to its /data directory, without root that is. Anything is possible with root, just don't expect your users to all have root access.
Edit: Developer owns both applications
Thanks for Roman Kurik for pointing this out. A link to his post on SO
From the android docs
android:sharedUserId
The name of a Linux user ID that will
be shared with other applications. By
default, Android assigns each
application its own unique user ID.
However, if this attribute is set to
the same value for two or more
applications, they will all share the
same ID — provided that they are also
signed by the same certificate.
Application with the same user ID can
access each other's data and, if
desired, run in the same process.
So this is exactly the way user id's work in linux, essentially you are the owner of both and have read/write access to both.