I have an app that wants to have the ability to download files locally. A user can download a file (no restrictions to file type) and should be able to save it on the device so it can be used for other purposes. I would also like the user to be able to delete the file from the app (they would know which file is downloaded and which isn't, a ticker would indicate if it's backed up locally). Say it is a pdf file -- the user would want to open it with different apps or edit it if they have the ability to, or just share it via email. Considering we cannot opt out of scoped storage anymore (requirement to target 30), I got a couple of questions.
I've tried to use Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()) and also getExternalFilesDirPath(ctx, Environment.DIRECTORY_DOWNLOADS)). While the former is deprecated, it works for me in that the Files app can be used to navigate to the file in question (and possibly open/delete the file). On the other hand, I am not being able to delete this file from my app due to lack of permissions. Obviously, the latter path is unable to be navigated to by another app (or is it? I haven't found a way, hence the question).
The other thing I've considered using is the MediaStore API but I'm struggling to see how this is an improvement over the old ways in terms of function. Disregarding moving back to manual content resolver and cursor usage, how should arbitrary files be sorted? Should I manually sort by mime types and have different methods for saving for specific media types? It sounds exceptionally tedious and counter intuitive. If this is the way, so be it, I will implement it, but it does not sound like the way to go. On the positive side, it at least sounds like a solution due to the content resolver's CRUD abilities.
I'm working on a RN app that uses a 3rd party library for the download paths, which old/new versions, respectively, use different paths (rn fetch blob and rn fetch blob util). Additionally, MediaStore API doesn't have a RN implementation as of right now, so everything would have to be done from scratch, too.
What are my options? In the short term I'm considering disabling the erase feature from the app (at least for now). Anything I am missing and should consider? Thanks in advance.
Related
I have an old drawing app on Android, which stores drawings (.PNG files) out on a specific folder on external storage. With Scoped Storage in Android 11, I need to find a way to save files, but I can't seem to find a solution that meets my needs. My app has its own gallery UI, like many drawing apps, so that I can control the selection/view UX.
I've tried:
MediaStore: I inserted files with MediaStore. The issue I ran into here is that I couldn't figure out a way to query just the files that my app created. Querying mediastore always returns a bunch of stuff I don't want.
Storage Access Framework. My intuition with this framework, is that I would probably spend a lot of time and potentially regret going down that road. I don't like the idea of not controlling the file selector (gallery). I also don't like relying on intents for a critical part of my app.
MANAGE_EXTERNAL_FILES. Based on the criteria defined by Google, my app wouldn't qualify to use this, though it would be great (since I could keep my old code).
Use internal file storage. I coded this up, and it's really clean, but I hesitate to roll this out, because uninstalling would mean that users lose their documents. Note that i have a Share intent, so users can "export" files one at a time.
What I want:
To write files where they aren't removed when the user uninstalls my app.
To write my files somewhere that the user can back them up one way or another.
Simplicity. I don't want to confuse existing users by changing things drastically.
Am I missing something regarding my options here? Can Mediastore effectively partition my files so that I can show them in the gallery? Does internal storage seem like the best option for me?
Just create your files in the old fashioned way in your apps sub folder in public Documents directory.
No special permissions needed.
After analyzing my Android application with a security tool, it has detected a high level vulnerability "File unsafe delete check". I have investigated about this, and it seems that the problem is that the application uses "file.delete()".
That function is considered unsafe because data could theoretically be retrieved with a tool that scans all the storage device. So, if that way of deleting is "unsafe"... what is the "safe" way to delete files in Android? (to avoid getting that "security error" that is supposedly a "high level" one). What is the proper way to delete files in Android Development?
I am getting the same security warning in 2 different applications, one made with native Java and the other one with Xamarin Forms. Thank you very much!
what is the "safe" way to delete files in Android?
There is none for the vast majority of Android devices. You use delete() on File.
That function is considered unsafe because data could theoretically be retrieved with a tool that scans all the storage device
If the Android device happens to use a classic hard drive (spinning magnetic media), you can overwrite the data before deleting it. On any sort of flash media, that will be ineffective, as the physical location where the data is written can vary with each write operation ("wear leveling").
So, this really boils down to your objective:
If you feel that the user will be harmed if this data is available to be read, store it encrypted with a user-supplied passphrase.
If you are simply trying to avoid this warning, ask the developers of this "security tool" what they are expecting you to do. Or, find a better tool.
This is not an Android specific issue.
It has to do with how file systems work, and the physical storage media it self.
When you delete a file, regardless of API, what is actually deleted is the record in the files table.
The actual data on disk or flash storage remains.
There is a method for secure deletion:
Before deleting the file, overwrite its contents with garbage or zeros several times.
But, this method only works for magnetic media such as hard disks.
Android devices use NAND flash for storage.
Because the number of writes a NAND chip can take before it fails is a lot less than that of magnetic memory, these chips usually come with a mechanism that spreads out the write commands.
What this means is that even if you try to write random data or zeros over your file, there is no guarantee the actual data will be overwritten.
The writes may go to a different sector to avoid wear.
So, on one hand, for flash storage it is enough to overwrite the file once, but on the other hand, it is impossible to do correctly at application level.
If you want to make your application secure, you must make sure to store sensitive data encrypted.
Then, even if someone tries to read the raw storage, they wouldn't be able to recover the data.
Don't store user credentials (like passwords) in regular files on Android.
Use Android accounts API and let the OS manage security.
If you still need file storage but want to protect the data, encrypt it in memory and then write to file.
As said by the other answers the first thing to consider under a theoretical point of view is if there is really a need to store any sensitive information in files to be kept on customer side
If this is really the case, encryption is the real way to guarantee proper security. Files would be protected not only against the recovery after deletion but also during their known life on the device
That said, in the case of a vulnerability assessment - i.e. a static analysis of the code - it would not be immediate to detect that you are calling for a deletion [via file.delete()] of encrypted files. Or maybe you are just calling the deletion of files with nothing to hide
In both these cases the found vulnerability would just be a false positive. Which is part of the game because you can guess that it's quite complicated for an automated tool to understand if something really "deserves" protection or not
What you can do to get rid of the vulnerability is adding the logic to empty the files before calling file.delete(). You have a sample here for this purpose. This will solve the vulnerability detection you are experiencing
Is there a way to use the StorageActionFramework ACTION_OPEN_DOCUMENT intent to request info for a specific file? More specifically, I have a URI for a particular file, which I obtained from MediaStore. I would like to find out if it has write access and, if so, delete it. Further, I'd like to do this without any UI (none is needed in this particular context).
The documentation says how to do it for a class of files using intent.addCategory (Intent.CATEGORY_OPENABLE), for example. And to restrict it to a certain type of file, intent.setType ("image/*"), for example. But I don't see anything to restrict it to a specific file.
Also, I realize that once you receive a set of files from the StorageActionFramework, you can view properties for individual files.
It appears that if I could use ACTION_OPEN_DOCUMENT to get the SAF's URI, I could then use DocumentsContract.deleteDocument() to delete it.
More Context
In my situation, the user has invoked the camera app from within my app and taken one or more pictures. My code then queries the MediaStore to determine the file name(s) for the new image(s). Then I desire to move the file(s) to a directory specific to my app. This works fine for files located in "internal" and "external" storage but not for removable storage.
In the long run, this solution is inadequate as clearly it will use to much permanent storage (although that is mitigated by the fast pace which storage size is increasing). However my app does need control over image files which are taken via the app and, thus, leaving them on the removable storage will potentially break the app.
Another complicating factor is the lack of an Android API for taking multiple photos and saving them to a prescribed location. There is such an API for taking a single photo but that will not work for my application. Thus, I am relegated to letting the camera app save its files where it wants to and then moving them afterward.
Some other apps simply make a copy of photos and store the copy in a private directory. I could do that but that exacerbates the storage problem even more. The long term solution will probably be using cloud storage in combination with a local private cache.
Is there a way to use the StorageActionFramework ACTION_OPEN_DOCUMENT intent to request info for a specific file?
No. For starters, the Storage Access Framework has little to do with files.
The closest thing that would fit the overall structure would be if you could supply a starting Uri, to allow users to choose and open a document nearby that one. That would be a nice feature but is not supported.
The documentation says how to do it for a class of files using intent.addCategory (Intent.CATEGORY_OPENABLE), for example.
No. CATEGORY_OPENABLE means that the Uri that you get back should work with openInputStream(), openOutputStream(), and related methods, and that a query() on the Uri should be able to return the OpenableColumns. It has little to do with files.
And to restrict it to a certain type of file, intent.setType ("image/*")
That limits the content to a particular MIME type (or wildcard). It has little to do with files.
I would like to find out if it has write access and, if so, delete it
You might have write access to change the contents, via openOutputStream(). I am not aware that you have a means of deleting the underlying content.
Further, I'd like to do this without any UI
The only reason to use ACTION_OPEN_DOCUMENT is to show a UI, to allow the user to choose a piece of content.
My code then queries the MediaStore to determine the file name for the new image
Since there is no requirement for a camera app to update the MediaStore, this does not seem like it will be especially reliable.
Another complicating factor is the lack of an Android API for taking multiple photos and saving them to a prescribed location. There is such an API for taking a single photo but that will not work for my application
Since there is no requirement for a camera app to allow the user to take multiple photos in succession, this does not seem like it will be especially reliable.
I did some testing and, with an Android emulator, outputted a few pathways:
Internal Storage context.getFilesDir() has path /data/data/package_name/files
Private External Storage with context.getExternalFilesDir(null) has path /storage/emulated/0/Android/data/packagename/files/
Public External Storage with Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) has path /storage/emulated/0/Pictures/
In my app, I've defined an object that has a timestamp string and an array for images (more specifically, paths to those images), and this information is stored in a sqlite database.
But is this a bad idea? Because if someone wanted to move this app to another device, wouldn't the paths change? I'm a little confused as to how files can be moved around, how different devices impact this situation, etc. I am confused as to what the correct approach is for storing image references in the sqlite table. Do you store absolute paths like above? Or do you store a number representing whether the pic is internal/private external/public external and pull that way regardless of device/file structure?
I'm just trying to make sure I don't publish an app that lacks some critical feature simply because I misunderstood something. Like if I start using an app that can take pictures/store them internally, it's all on internal storage, all on this phone. But if I want to start saving pics to an SD card or something else, I have to change where I store the pics and how I reference them. And what if I upgrade my phone? I'd have to somehow carry/transfer all that stuff to a new infrastructure, etc. Or the "cloud", even.
In other words I am just trying to understand all the possible use cases, here, in terms of how Android app data is stored and transferred.
Your questions are a little broad. I will try to answer them in turns:
But is this a bad idea?
Not in itself. The issue here is how are you managing changes and the Android alterations that may occur in the future (as well as the versions you are currently supporting)
Because if someone wanted to move this app to another device, wouldn't the paths change?
Relative paths would never change. They were implemented (I am guessing around API 2~6) to ensure that hard disk management would not be necessary in apps. By getting the Enviroment.getExternalFilesDir(stuffs..) you would garantee a path to the same file, regardless of readl disk allocation.
how files can be moved around, how different devices impact this situation, etc.
Files from Desktops (PC, Mac, Linux, Ubuntu, etc) follow a contracted with Android devices, that simulate the same disk system. This way, a PC can save a file (png, mp3, custom, etc) in a "root folder" that becomes the same root for Enviroment.getExternalFilesDir(stuffs..) BUT data private to your application is never accessible (on non-rooted devices), to the outside system.
Do you store absolute paths like above?
You can. But I believe this goes against the system best-practices. Saving a relative path, then building a File object, in regards to that path is the correct approach. Do note that the File class in Android expects you to know a bit about the file you are refferencing.... either its extension, name regex, physical location, relational location, etc... you need to know at least one of those.
Finally, what I think you asked:
Can the user replicate the same content in different devices?
For this, you must ensure that the user is "know" for instance, using the Play Store account, you can then have a repository, and have devices syncronize with that repository. Or even add a "export" function, that copies files, and imports them somewhere else.
I'm using the Google Drive Android API (yes I know its still in developer preview), to create a file on Drive that can persist data across all of a user's devices.
https://developers.google.com/drive/android/create-file
So, use the app on your phone:
-Check that the file exists (filter by filename and not in trash)
--If exists then read and update local sqlite with data
--If not exists then create new file, and write data from sqlite. Then request sync.
-When user changes data, open file again, write contents and commit/sync.
I'm finding situations when testing where I run on my phone, and then on my tablet, and there happens to be multiple files created of the same name and my app gets confused when opening. I'm guessing there were duplicates because there was a delay in the sync and the 2nd device didn't find the existing file so created a new one.
So, now I'm thinking, I pull in all files with that name (not marked trash) and merge them, then mark trash on all but one of them and call that the current. This will leave many files in the trash as time passes.
Couple of questions:
Is there anyway to create a file with a unique name, or pull by some unique handle. The duplicates are making things a hassle to use this Drive product.
Is there anyway to delete a file, not mark for trash, but actually delete. After a merge, I don't want to leave hundreds of files marked for trash on someones drive after months of use.
Am I missing something obvious here? All I want to do is continually overwrite a file by the last device to save data and call that the master copy. Then let other devices know that they should refresh when they run. Right now, I have a network tool app, but I'm also creating a game and was hoping to leverage this same mechanism to save/persist game state across devices....at this point I'm not so confident that Drive is the proper means.
As you've discovered title's are not unique in Drive. However, each item in Drive does have a unique identifier. In the Android API, the DriveId is the representation of this. (In the web API its the resource id).
Once you create the file, you can save the DriveId in local preferences so that next time you can just look it up by the ID. This will guarantee that you will always have the same file on the same device.
On other devices, you can first do a query by title to determine if it exists already, and then save off the DriveId if it does.
Google Play Cloud Save is designed for lightweight persisting of save state across multiple devices including conflict resolution and was built originally for games (although any app can use it). This seems like a more likely fit for what you want to do than trying to work with the full file system approach that is Google Drive.