0

I am unable to write file on physical SD card. I want to ask user for permission to write to whole SD card. User grants the permission. Now I want to create file in specific directory on SD card (directory already exists). When I try to open output stream I get exception:

Java.Lang.SecurityException: Permission Denial: writing com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/0FFF170E%3AMyDir/MyFile.MyExt from pid=3563, uid=10082 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()

Here is code of Activity:

using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Net;
using Android.OS;
using Android.OS.Storage;

namespace MyApp
{
    [Activity(Label = "MyApp", MainLauncher = true)]
    public class MainActivity : Activity
    {
        private const int AccessRequest = 101;

        private TaskCompletionSource<string> AccessIntentTaskCompletionSource { get; set; }

        protected override async void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            SetContentView(Resource.Layout.Main);

            string uriWithPermission = await AccessIntentAsync();

            if (!string.IsNullOrEmpty(uriWithPermission))
            {
                string targetUri = uriWithPermission + "MyDir/MyFile.MyExt"; // MyDir exists on SD card

                // Example of targetUri:
                // content://com.android.externalstorage.documents/tree/0FFF-170E%3AMyDir/MyFile.MyExt

                WriteFileToUri(targetUri);
            }
        }

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);

            if (requestCode == AccessRequest)
            {
                string path = null;

                if (resultCode == Result.Ok)
                {
                    path = data.DataString;
                    Android.Net.Uri uriPath = data.Data;
                    ContentResolver.TakePersistableUriPermission(uriPath, ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission);
                }

                this.AccessIntentTaskCompletionSource?.SetResult(path);
            }
        }

        private async Task<string> AccessIntentAsync()
        {
            this.AccessIntentTaskCompletionSource = new TaskCompletionSource<string>();

            StorageManager sm = GetSystemService(StorageService) as StorageManager;
            if (sm != null)
            {
                StorageVolume volume = sm.StorageVolumes.FirstOrDefault(sv => !sv.IsPrimary); // Assume only one non-primary storage volume.

                if (volume != null)
                {
                    Intent intent = volume.CreateAccessIntent(null); // request access to the entire volume
                    StartActivityForResult(intent, AccessRequest);

                    return await this.AccessIntentTaskCompletionSource.Task;
                }
            }

            return null;
        }

        private void WriteFileToUri(string targetUri)
        {
            Android.Net.Uri outputUri = Uri.Parse(targetUri);

            byte[] source = System.Text.Encoding.UTF8.GetBytes("MyFileContent");

            using (System.IO.Stream outputStream = this.ContentResolver.OpenOutputStream(outputUri)) // throws Exception
            {
                outputStream.Write(source, 0, source.Length);
                outputStream.Close();
            }
        }
    }
}

Here is Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="MyApp.MyApp" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application android:allowBackup="true" android:label="@string/app_name"></application>
</manifest>

I have tested it on emulator and real device. Both with Android 7.1.

SD card is mounted in "For transferring photos and media" mode.

EDIT: Ask for permission android.permission.WRITE_EXTERNAL_STORAGE at run time does not work. It works only if I want to write to PRIMARY external storage.

  • Because you must get permissionin at run time see this [link](https://stackoverflow.com/questions/33162152/storage-permission-error-in-marshmallow) – Ahmad Nemati Apr 04 '18 at 07:52
  • Possible duplicate of [SecurityException: Permission Denial: reading (only on emulator)](https://stackoverflow.com/questions/32599132/securityexception-permission-denial-reading-only-on-emulator) – ADM Apr 04 '18 at 08:02
  • Getting permission android.permission.WRITE_EXTERNAL_STORAGE at run time only works, when I want to write on primary external storage. Not for physical SD card. – Tomas Ondrej Apr 04 '18 at 08:23

3 Answers3

0

You have to give runtime permission

if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
    Log.v(TAG,"Permission is granted");
    //File write logic here
    return true;
} else {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE)
}
ColdFire
  • 6,764
  • 6
  • 35
  • 51
0
string targetUri = uriWithPermission + "MyDir/MyFile.MyExt"; // MyDir exists

Even if MyDir exists your code will not work as you have seen as it is not the right uri for a file there.

Also if the user had choosed the MyDir directory and your coude would look like

string targetUri = uriWithPermission + "/MyFile.MyExt";

your code, using an outputstream to create a file, still would not work.

Instead you should use DocumentFile to obtain an instance for the choosen directory. After that you can use DocumentFile.createFile() to create a subdirectory or file.

You do not need the usual permissions in manifest and runtime for this.

greenapps
  • 11,154
  • 2
  • 16
  • 19
0

check following code it would be help you to understand file save in memory.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == CAMERA_CAPTURE_IMAGE_REQUEST_CODE && resultCode == RESULT_OK) {
        // Show the thumbnail on ImageView
        previewMedia();
    } else if (requestCode == REQUEST_GALLERY_PHOTO && resultCode == RESULT_OK) {
        // Show the thumbnail on ImageView
        try {
            imageUri = data.getData();
            String[] filePathColumn = {MediaStore.Images.Media.DATA};

            Cursor cursor = getActivity().getContentResolver().query(imageUri,
                    filePathColumn, null, null, null);
            cursor.moveToFirst();

            int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
            mCurrentPhotoPath = cursor.getString(columnIndex);
            cursor.close();
            bitmap = BitmapFactory.decodeFile(compressImage(mCurrentPhotoPath));
            preview_imageview.setImageBitmap(bitmap);
        } catch (Exception e) {
            e.getMessage();
        }

        // ScanFile so it will be appeared on Gallery
        MediaScannerConnection.scanFile(getActivity(),
                new String[]{imageUri.getPath()}, null,
                new MediaScannerConnection.OnScanCompletedListener() {
                    public void onScanCompleted(String path, Uri uri) {
                    }
                });
    }
}
Nitin Sharma
  • 100
  • 9