Reputation: 141
In my app I have given facility to download reports in PDF formats. Everything was good until I was testing my app on Android 10 and below. Recently I updated a device to Android 11 and now I am not able to download any file in shared storage. I am getting Access Denied
error while downloading file.
I have given permission to read and write external storage
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
and my download path is
Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
I have also set android:requestLegacyExternalStorage="true"
in AndroidManifest.xml
My code to download file looks like this
public async void DownloadSupplierSample(object sender, EventArgs e)
{
try
{
string basepath;
if (Device.RuntimePlatform == Device.Android)
basepath = DependencyService.Get<IDownloadPath>().GetDownloadsPath();
else
basepath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "..", "Library");
PermissionStatus readstatus = await Permissions.CheckStatusAsync<Permissions.StorageRead>();
PermissionStatus writestatus = await Permissions.CheckStatusAsync<Permissions.StorageWrite>();
if (readstatus == PermissionStatus.Granted && writestatus == PermissionStatus.Granted)
{
DownloadSupplier(basepath);
}
else
{
var read = await Permissions.RequestAsync<Permissions.StorageRead>();
var write = await Permissions.RequestAsync<Permissions.StorageWrite>();
if (read == PermissionStatus.Granted && write == PermissionStatus.Granted)
{
DownloadSupplier(basepath);
}
}
await DisplayAlert("", "Sample is downaloaded at Downloads directory","Ok");
}
catch (Exception ex)
{
}
}
async void DownloadSupplier(string basepath)
{
using (var stream = await FileSystem.OpenAppPackageFileAsync("SupplierMaster.xlsx"))
{
string filename = $"SupplierMaster_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx";
string filepath = Path.Combine(basepath, filename);
byte[] bytes = Utils.Common.StreamToBytes(stream);
File.WriteAllBytes(filepath, bytes);
//using (Stream filestream = File.Create(filepath))
//{
// //stream.Seek(0, SeekOrigin.Begin);
// stream.CopyTo(filestream);
//}
}
}
I have tried setting flag from device as well from this link
How can I define scoped storage for my app ?
Any update how can I download PDF file in my downloads directory ?
Thank you.
Upvotes: 7
Views: 5010
Reputation: 586
I had the same issue a couple of months ago.
I found this on developer.android page:
(https://developer.android.com/reference/android/os/Environment#getExternalStorageDirectory())
The first alternative (getExternalFilesDir) won't work as you want, files won't appear in the download directory, this location is internal to the app.
The second alternative is for media files.
I ended up using the third alternative. I created a service class in Android project, something like this:
public class FilesManager : IFilesManager
{
private const int CREATE_FILE_REQUEST_CODE = 123;
private static Context _context;
public static void Init(Context context)
{
_context = context;
}
public async Task SaveFile(string fileName, byte[] content)
{
Intent intent = new Intent(Intent.ActionCreateDocument);
intent.AddCategory(Intent.CategoryOpenable);
intent.SetType(your_file_mimeType);
intent.PutExtra(Intent.ExtraTitle, fileName);
var activity = (MainActivity)_context;
var listener = new ActivityResultListener(activity);
activity.StartActivityForResult(intent, CREATE_FILE_REQUEST_CODE);
var result = await listener.Task;
if (result == null)
{
// Cancelled by user
return;
}
using (Stream os = _context.ContentResolver.OpenOutputStream(result.Data))
{
os?.Write(content);
os?.Close();
}
}
private class ActivityResultListener
{
private readonly TaskCompletionSource<Intent> Complete = new TaskCompletionSource<Intent>();
public Task<Intent> Task { get { return this.Complete.Task; } }
public ActivityResultListener(MainActivity activity)
{
// subscribe to activity results
activity.ActivityResult += OnActivityResult;
}
private void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if(requestCode != CREATE_FILE_REQUEST_CODE)
{
return;
}
// unsubscribe from activity results
var activity = (MainActivity)_context;
activity.ActivityResult -= OnActivityResult;
// process result
if (resultCode == Result.Ok)
{
Complete.TrySetResult(data);
}
else
{
Complete.TrySetResult(null);
}
}
}
}
In MainActivity.cs add:
public event Action<int, Result, Intent> ActivityResult;
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
ActivityResult?.Invoke(requestCode, resultCode, data);
}
You can call Init method after Forms.Init:
FilesManager.Init(this);
Note: This method requires user interaction. The user will be prompted to choose the location where to save the file.
Upvotes: 5
Reputation: 10346
How can I define scoped storage for my app ?Any update how can I download PDF file in my downloads directory ?
Android 10 introduced a new storage paradigm for apps called scoped storage which changes the way apps store and access files on a device's external storage. If you target Android 10 (API level 29) or higher, set the value of requestLegacyExternalStorage to true in your app's manifest file.
<application android:requestLegacyExternalStorage="true" android:label="FormsSample.Android" android:theme="@style/MainTheme"></application>
Then request runtime write and read EXTERNAL_STORAGE permission before access download folder.
public void requestpermission()
{
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.WriteExternalStorage }, 1);
}
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != (int)Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.ReadExternalStorage }, 1);
}
}
Create interface IAccessFileService in Xamarin.Forms shared code.
public interface IAccessFileService
{
void CreateFile(string FileName);
}
Implementing IAccessFileService interface on Android platform.You could use File.Exists(xx)
to check if the folder exists, and use Directory.CreateDirectory(xx)
to create the folder if not.
[assembly: Xamarin.Forms.Dependency(typeof(AccessFileImplement))]
namespace FormsSample.Droid
{
public class AccessFileImplement : IAccessFileService
{
public void CreateFile(string FileName)
{
string text = "hello world";
byte[] data = Encoding.ASCII.GetBytes(text);
string rootPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, Android.OS.Environment.DirectoryDownloads);
var filePathDir = Path.Combine(rootPath, "folder");
if (!File.Exists(filePathDir))
{
Directory.CreateDirectory(filePathDir);
}
string filePath = Path.Combine(filePathDir, FileName);
File.WriteAllBytes(filePath, data);
}
}
}
So using dependencyservice to call android CreateFile method.
private void btn1_Clicked(object sender, EventArgs e)
{
DependencyService.Get<IAccessFileService>().CreateFile("test.txt");
}
Upvotes: 3