Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

File written using ACTION_CREATE_DOCUMENT is empty on Google Drive but not local storage

I have an app that exports a CSV file using the ACTION_CREATE_DOCUMENT intent to get a Uri to write to. This was working fine for some time, but recently when I went to save a file and selected Google Drive I noticed that the resulting file was empty (0 bytes). However, if I select the Downloads folder as the destination (local storage) the file is created and written to correctly.

Result of saving to Google Drive (correctly written file is 14 bytes)

I condensed the problem down to a simple test Activity (below) and verified that all parts of the code are in fact being executed (no caught exceptions, ParcelFileDescriptor is not null, etc...).

I tested this on a Android 7 and 8 emulators and an Android 8 physical device and all showed the same behavior, but an old Android 5 phone worked correctly (??).

The full version of the app has, and correctly requests, read and write external storage permissions to handle some third party file managers, but that isn't needed for Google Drive so I've omitted it from this example (behavior is the same with the permissions granted).

The uri I get looks like a valid content Uri (content://com.google.android.apps.docs.storage/document/acc%3...)

Has anyone else seen this issue? Am I doing something wrong here? How can I make this work with Google Drive again?

public class MainActivity extends AppCompatActivity {

    private static final int FILE_EXPORT_REQUEST_CODE = 12;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (resultCode != RESULT_OK)
            return;

        switch(requestCode) {
            case FILE_EXPORT_REQUEST_CODE:
                if( data != null ) {
                    Uri uri = data.getData();
                    if( uri != null ) {
                        new ExportCSV(this).execute(uri);
                    }
                }
                break;
        }
    }

    // test Activity has a single button that calls this
    public void saveFile(View v) {
        Intent exportIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        exportIntent.addCategory(Intent.CATEGORY_OPENABLE);
        exportIntent.setType("text/csv");
        String filename = "test.csv";
        exportIntent.putExtra(Intent.EXTRA_TITLE, filename);
        startActivityForResult(exportIntent, FILE_EXPORT_REQUEST_CODE);
    }

    private static class ExportCSV extends AsyncTask<Uri, Void, Boolean> {
        private final WeakReference<Context> context;

        ExportCSV(Context c) {
            context = new WeakReference<>(c);
        }

        @Override
        protected Boolean doInBackground(Uri... uris) {
            Uri uri = uris[0];
            Context c = context.get();

            if( c == null ) {
                return false;
            }

            String data = "colA,colB\n1,2\n";
            boolean success = false;

            try {
                ParcelFileDescriptor pfd = c.getContentResolver().openFileDescriptor(uri, "w");
                if( pfd != null ) {
                    FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
                    fileOutputStream.write(data.getBytes());
                    fileOutputStream.close();
                    success = true;
                }
            }
            catch(Exception e) {
                e.printStackTrace();
            }
            return success;
        }
    }
}
like image 221
Tyler V Avatar asked Jul 24 '18 03:07

Tyler V


2 Answers

The comment from @greenapps put me on the right path. Changing it to use getContentResolver().openOutputStream(...) fixed the problem:

try {
    OutputStream os = c.getContentResolver().openOutputStream(uri);
    if( os != null ) {
        os.write(data.getBytes());
        os.close();
    }
}
catch(IOException e) {
    e.printStackTrace();
}

Update: After awhile of using this in my app, the problem re-occurred (files written to Google Drive were 0 bytes, but could be written locally). Clearing the cache data for my app solved it again.

like image 74
Tyler V Avatar answered Oct 11 '22 18:10

Tyler V


Refer to official documentation at https://developer.android.com/guide/topics/providers/document-provider

On Android 4.3 and lower, if you want your app to retrieve a file from another app, it must invoke an intent such as ACTION_PICK or ACTION_GET_CONTENT. The user must then select a single app from which to pick a file and the selected app must provide a user interface for the user to browse and pick from the available files.

On Android 4.4 (API level 19) and higher, you have the additional option of using the ACTION_OPEN_DOCUMENT intent, which displays a system-controlled picker UI controlled that allows the user to browse all files that other apps have made available. From this single UI, the user can pick a file from any of the supported apps.

On Android 5.0 (API level 21) and higher, you can also use the ACTION_OPEN_DOCUMENT_TREE intent, which allows the user to choose a directory for a client app to access.

For me the following solution works perfect for both local as well as google drive on Android 9

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == RESULT_OK && data != null) {
        if (requestCode == REQUEST_SAVE_File) {
            try {
                stream = getContentResolver().openOutputStream(data.getData());
                ((BitmapDrawable) imageView.getDrawable()).getBitmap().compress(Bitmap.CompressFormat.PNG, 100, stream);
                strem.close(); ///very important
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}

And intent is created as follows

    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/png");
    intent.putExtra(Intent.EXTRA_TITLE, "my-image.png");

    startActivityForResult(intent, REQUEST_SAVE_FILE);

Make sure you close the stream after writing. Otherwise file may not be saved.

like image 21
mayank1513 Avatar answered Oct 11 '22 17:10

mayank1513