I'm on an Android 5.0 device where a usb mass storage is mounted at "/storage/usbotg" via USB OTG cable.
Unfortunately from my app I only have read access to that folder. Please note that I set the write external storage permission as I'm able to write to device storage.
I post the following code as reference:
string Root = "/storage/usbotg";
string[] Dirs = Directory.GetDirectories(Root);
string NewFolder = Path.Combine(Root, "NewFolder");
Directory.CreateDirectory(NewFolder);
This gives me an exception on the last line (but I'm able to list subdirectories in Dirs)
Exception:
System.UnauthorizedAccessException
Exception Message:
Access to the path "/storage/usbotg/NewFolder" is denied.
If I use:
string Root = "/storage/emulated/0";
everything is working fine and the "NewFolder" is created.
What I'm missing? How can I write to that folder?
I'm using Xamarin.Forms 2.5.0
Thanks for your help
Accessing data outside your application's private storage using the file system is more restricted with each Android version.
The recommended options are :
Use getExternalFilesDirs(), getExternalCacheDirs, ... : this gives you one or more directories specific to you application (usually directories named after the package name). This does not work for removable media, unless they're adopted.
Use the Storage Access Framework : ask the user (using ACTION_OPEN_DOCUMENT_TREE) to choose the storage root. You can then manage the content of the picked directory through the SAF API. You can persist the permission, so you only need to ask the user once. It seems that it is what ES File explore does to get write permission.
A (much) more detailed explanation by Mark Murphy.
Related
I'm trying to read a given directory recursively and find all folders and files. The directory and subdirectory is found, but no files.
The storage structure on my virtual device:
The code used to "walk through the given directory"
import android.util.Log
import java.io.File
// path = "/storage/1B10-1D17/ReadTest/"
class FileHelper {
fun walkTest(path: String){
File(path).walkTopDown().forEach {
// println(it)
Log.e("walkTest extension", it.extension)
if(it.isDirectory){
Log.e("walkTest", "Directory: ${it.name}")
}else{
Log.e("walkTest", "File: ${it.name}")
}
}
}
}
This will output the following:
E/walkTest extension:
E/walkTest: Directory: ReadTest
E/walkTest extension:
E/walkTest: Directory: SubFolder
In my android manifest file I have the following line and the permission is granted:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
What is the flaw in my code, how can I find the three pdf files stored in these directories?
Update 1/2
As can be seen in the screenshot below, the media access is granted:
If I run the following command (as suggested by dan1st), I get the following output, which shows that pdf file in place:
> adb shell ls /storage/1B10-1D17/ReadTest/
File_1.pdf
SubFolder
You generally have no access to arbitrary files on removable storage. This is not changed by READ_EXTERNAL_STORAGE.
Your proposed solution is to use MANAGE_EXTERNAL_STORAGE. Technically, this works, and it is a fine solution for personal-use apps. If your intention is to distribute this app, you will need to file paperwork with Google (and perhaps other app stores) justifying your use of this permission. Stores that disagree with your justification may elect to not distribute your app.
Alternatively, you can use the Storage Access Framework. Use ACTION_OPEN_DOCUMENT_TREE / ActivityResultContracts.OpenDocumentTree and let the user decide where on the user's device (or in the user's cloud storage) the user wants your app to access the user's content. You can then use DocumentFile to walk the document tree to find sub-trees and documents.
I'm trying to achieve some clean up tools. More and more manufacturers have forbidden rooting devices due to some "security reason", it's forbidden NOT to request for unlock.
After API 28, This code will make error:
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1); // Request permission or not, Will got same result
File rootFolder = Environment.getExternalStorageDirectory(); // That is working fine
rootFolder.listFiles(); // That will return null
Sure, I can use this:
android:requestLegacyExternalStorage="true"
But I belive that will be killed in future.
So, Any elegant way to manage SDCard?
On Android 10 Environment.getExternalStorageDirectory() and Environment.getExternalStoragePublicDirectory() will return storage paths but paths are not readable or writable.
For Android 10 you can continue to use paths provided by Environment.getExternalStorageDirectory() and Environment.getExternalStoragePublicDirectory() if you add android:requestLegacyExternalStorage="true" to application tag in manifest file. At runtime your app can call Environment.isExternalStorageLegacy() to check if the request has been done.
Another (not known) possibility (only for Android 10) is to add <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> to manifest file.
The user has to go to the advanced settings of the app and enable from Advanced settings Install unknown apps | Allow from this source.
The nice thing with this is that the user can switch the access rights. You can make it easier for the user if you implement an intent for
Settings.ACTION_APPLICATION_DETAILS_SETTINGS where he can change the settings.
A funny thing is that Environment.isExternalStorageLegacy() returns true then too.
Compiling for Android 11 both options do not work on an Android 11 device. (But they continue to work for Android 10 devices). The paths of Environment.getExternalStorageDirectory() and Environment.getExternalStoragePublicDirectory() are usable again in read mode and very often in write mode too. And this is great as one can simply list the contents of directories like Download or Pictures or DCIM/Camera again using the File class.
But adding <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> to manifest file and implementing an intent for
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION will give your app read/write access for all files even on removable micro sd card.
(Finally you can remove the google ban not being able to read/write your own micro sd card on your own Android device using your own app).
Environment.isExternalStorageManager() can be used to check if the permission is on/off.
As long as you do not try to upload your app to the play store you are fine.
use android:requestLegacyExternalStorage="true" in your Manifest below <application
My application will be used offline and I plan daily pull/push synchronizations via USB cable. My users are in a very primitive situation: no wifi, and no cell phone towers. My question is not about synchronization, but rather just getting access to the data so that I can synchronize.
I connect the cable, select USB for file transfer, and I can see Internal Shared Storage. But I cannot find my SqlLite database anywhere. I have tried using these paths for the database:
Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
Environment.GetFolderPath(Environment.SpecialFolder.Personal);
Android.App.Application.Context.FilesDir.AbsolutePath;
The app works fine with any of those paths, the data is stored and retrieved, but I cannot see the database from my PC.
I have also tried this but it blows up:
Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
I have tried debugging with Xamarin Live but then I get this error:
"You need to call SQLitePCL.raw.SetProvider();"
I have tried adding console.writeline and Log.Error to add in some diagnostics but I can't find any log files in Internal Shared Storage.
I have WRITE_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions in the manifest; even tho I think that is not necessary.
If I could store files in Internal Shared Storage then I could put the database there and access it to synchronize. And I could create a simple text logging facility to write a text log to the same place.
I have rarely asked for help in 40 years but I've been at this for days. Thanks!
To get files onto Internal Shared Storage accessible via USB took 3 steps:
1: Get runtime permissions
ActivityCompat.RequestPermissions(activity, new String[] { Manifest.Permission.WriteExternalStorage, Manifest.Permission.ReadExternalStorage }, 1);
2) use this path:
string extPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
extPath = Path.Combine(extPath, "RtTrace.txt");
File.AppendAllText(extPath, "new content" + System.Environment.NewLine);
3) Media scan the resulting file to make it visible:
MediaScannerConnection.ScanFile(Android.App.Application.Context, new String[] { extPath }, null, null);
The path on Environment.SpecialFolder.Personal refers to a private area where only the app (and the OS itself when you clear data from the app, for example) have access. I don't know about ApplicationData.
You can easily copy your app file (the protected one) to a public folder, like Downloads or create a new folder MyAppDirectory at the public storage space, that will allow access from other devices. Then, you can clear local data that you don't need anymore (after the sync process).
To create a new public folder on Android:
var folder = System.IO.Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, "MyAppDirectory");
System.IO.Directory.CreateDirectory(folder);
You'll get this:
Then, copy the file:
var newFile = Path.Combine(folder, "MySharedFile"); // The database, xml, json, text or any file you want to share
var sourceFullName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal),"MyOriginalFile");
System.IO.File.Copy(sourceFullName, newFile, true /* overwrite */);
I hope it helps.
SQLite db is stored in application private memory and can't be accessed over USB, try exporting contents of your DB to an external file in your internal memory or External memory to be able to access it via a USB chord.
I am trying to access external storage on my Android App in order to access a log file so I created:
new File(getExternalFilesDir(null),"log.txt");
And then created this file in the root of my Android device.
To test the existence of the file I ran:
if (log!=null)
{
return log.exists();
}
return false;
But regardless of the files presence or lack thereof it returns false.
To find the location of where I was referencing I ran:
System.out.println(log.getAbsolutePath());
Which returns
/storage/emulated/0/Android/data/com.supportmeplus.betatester/files/log.txt
Where is this? I'd just like to access a log file that can be accessed from outside of the app later on (on a computer). Does this have to do with the fact that i'm test-running the app with an emulator?
Thanks
I have a system application with all the necessary permissions. previously, i would download apks using the DownloadManager and then use reflection via PackageManager's installPackage() to install said apk.
unfortunately, following N's behaviour changes
http://developer.android.com/preview/behavior-changes.html :
The DownloadManager can no longer share privately stored files by filename. Legacy applications may end up with an unaccessible path when accessing COLUMN_LOCAL_FILENAME. Apps targeting Android N or higher trigger a SecurityException when attempting to access COLUMN_LOCAL_FILENAME. Legacy applications that set the download location to a public location by using setDestinationInExternalFilesDir(Context, String, String) or setDestinationInExternalPublicDir(String, String) can still access the path in COLUMN_LOCAL_FILENAME, however, this method is strongly discouraged. The preferred way of accessing a file exposed by the DownloadManager is using openFileDescriptor(Uri, String).
I am now getting a permission denied error when trying it the old way.
I need a Uri to pass to PM's installPackage(), and so i currently just copy the file to my application's private folder with a (depcrecated) WORLD_READABLE mode, so that the PM can later access it and install. This feels very hacky and i was wondering if anyone knows of a better way (The other option suggested by androiddev is to download the file to a public dir, but this would require me to add the WRITE_EXTERNAL_STORAGE permissions which is not an option.)