-1

I have an issue regarding writing to external files in an Android app, tailored for Chrome OS, compiled with App Runtime for Chrome. This app function works as intended on two Android test device (one API 19, same as ARC, I believe), but always fails when tested on a Chromebook. The minSdkVersion of my app is 19.

My app is a text editor, and what I want to achieve is to load text using Intent.ACTION_OPEN_DOCUMENT, saving the file Uri in the process. Later, when I want to save, I can use this Uri to access the file and update its contents, without needing to disturb the user with a "Share" dialog every time they want to save the file (or if they have set up an Autosave preference. My app defaults to an ACTION_SEND Intent if there is a FileNotFoundException).

Here is the relevant code. Could anyone suggest a workaround, or any improvement I could make?

public class EditorHost extends AppCompatActivity implements EditorFragment.EditorInterface {

private static final int REQUEST_OPEN = 15;

//...

 @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (mEditorFragmentChild == null)
            mEditorFragmentChild = (EditorFragment) mFragmentManager.findFragmentByTag(EDITOR_FRAGMENT_TAG);
        switch (item.getItemId()) {
            case R.id.load_file:
                open();
                break;
            case R.id.save_file:
                if (mEditorFragmentChild.getUri() == null){
                    shareTask();
                    break;
                }
                mEditorFragmentChild.saveTask();
                break;
            case R.id.share:
                mIntent = new Intent(Intent.ACTION_SEND);
                mIntent.putExtra(Intent.EXTRA_TEXT, mEditorFragmentChild.mExtendedEditText.getText().toString());
                mIntent.setType("text/plain");
                startActivity(Intent.createChooser(mIntent, getString(R.string.share_title)));
                break;

@TargetApi(Build.VERSION_CODES.KITKAT)
private void open(){
    mIntent = new Intent().setType("text/plain");
    startActivityForResult(mIntent.setAction(Intent.ACTION_OPEN_DOCUMENT).addCategory(Intent.CATEGORY_OPENABLE), REQUEST_OPEN);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData){
    if (resultCode == Activity.RESULT_OK){
        Uri uri = null;

        if (resultData != null) {
            uri = resultData.getData();

            if (requestCode == REQUEST_OPEN) {
                Cursor c = getContentResolver().query(uri,null, null, null, null);

                if (c != null && c.moveToFirst()){
                    title = c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));

                    String[] cols = c.getColumnNames();
                    for (int i = 0; i < c.getColumnCount(); i++){
                        Log.e(LOG_TAG, cols[i]);
                    }

                    content = readFromFile(uri);

                    if (title == null || title.equals("")){
                        title = getString(R.string.app_name);
                    }

                    getSupportActionBar().setTitle(title);
                    mEditorFragmentChild.updateTextFromFile(content.trim());
                    mEditorFragmentChild.setUri(uri.toString());

                    if (!c.isClosed()) {
                        c.close();
                    }
                }
            }
        }
    }
}

and in the EditorFragment class:

public class EditorFragment extends Fragment implements ... {
//...
String getUri(){
    return mUri;
}

void setUri(String newUri){
    mUri = newUri;
}

void saveTask(){
    if (mUri == null || fromShareTask) {
        fromShareTask = false;
        return;
    }
    try {
        OutputStream outputStream = mAppCompatActivity.getContentResolver().openOutputStream(Uri.parse(mUri));
        outputStream.write(mExtendedEditText.getText().toString().getBytes());
        outputStream.close();
        //Toast.makeText(mAppCompatActivity, mAppCompatActivity.getString(R.string.file_updated_confirmation), Toast.LENGTH_SHORT).show();
    } catch (IOException e){
        e.printStackTrace();
        dispatchShareEvent();
        Toast.makeText(mAppCompatActivity, mAppCompatActivity.getString(R.string.save_failed), Toast.LENGTH_LONG).show();
        mExtendedEditText.setText(e.toString() + "\n\n" + exceptionStacktraceToString(e) + "\n\n" + "mUri is " + mUri);
    }
}

And finally, the error message (to repeat myself from above, this only occurs on my Chromebook and not an Android test device):

java.io.FileNotFoundException: Can't access data/data/org.chromium.arc/external/(etc.)...

Thanks!

EDIT:

Whilst I couldn't figure out how to get the stack trace from my Chromebook, I was able to add the following method from the accepted answer on this question to print it to an EditText. I also printed the Uri at the end. Here is the output:

java.io.FileNotFoundException: Can't access /data/data/org.chromium.arc/external/E5783234425D1719508FC512B2BEE2A5/1kTest.txt
java.io.FileNotFoundException: Can't access /data/data/org.chromium.arc/external/E5783234425D1719508FC512B2BEE2A5/1kTest.txt
at com.android.providers.media.MediaProvider.checkAccess(MediaProvider.java:4620)
at com.android.providers.media.MediaProvider.openFileAndEnforcePathPermissionsHelper(MediaProvider.java:4569)
at com.android.providers.media.MediaProvider.openFile(MediaProvider.java:4493)
at android.content.ContentProvider.openAssetFile(ContentProvider.java:1211)
at android.content.ContentProvider.openAssetFile(ContentProvider.java:1274)
at android.content.ContentProvider$Transport.openAssetFile(ContentProvider.java:314)
at android.content.ContentProvider$Transport.openAssetFile(Native Method)
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:926)
at android.content.ContentResolver.openOutputStream(ContentResolver.java:673)
at android.content.ContentResolver.openOutputStream(ContentResolver.java:649)
at com.werdpressed.partisan.filterforchrome.EditorFragment.saveTask(EditorFragment.java:370)
at com.werdpressed.partisan.filterforchrome.EditorHost.onOptionsItemSelected(EditorHost.java:119)
at android.app.Activity.onMenuItemSelected(Activity.java:2619)
at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:353)
at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:144)
at android.support.v7.internal.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:99)
at android.support.v7.app.AppCompatDelegateImplV7.onMenuItemSelected(AppCompatDelegateImplV7.java:538)
at android.support.v7.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:802)
at android.support.v7.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:153)
at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:949)
at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:939)
at android.support.v7.widget.ActionMenuView.invokeItem(ActionMenuView.java:598)
at android.support.v7.internal.view.menu.ActionMenuItemView.onClick(ActionMenuItemView.java:139)
at android.view.View.performClick(View.java:4440)
at android.view.View$PerformClick.run(View.java:18407)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:5181)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at android.os.Process$1.run(Process.java:418)

mUri is content://media/external/file/8

The error occurs in the following try/catch block:

try {
    OutputStream outputStream = mAppCompatActivity.getContentResolver().openOutputStream(Uri.parse(mUri));
    outputStream.write(mExtendedEditText.getText().toString().getBytes());
    outputStream.close();
    //Toast.makeText(mAppCompatActivity, mAppCompatActivity.getString(R.string.file_updated_confirmation), Toast.LENGTH_SHORT).show();
} catch (IOException e){
    e.printStackTrace();
    dispatchShareEvent();
    Toast.makeText(mAppCompatActivity, mAppCompatActivity.getString(R.string.save_failed), Toast.LENGTH_LONG).show();
    mExtendedEditText.setText(e.toString() + "\n" + exceptionStacktraceToString(e) + "\n\n" + "mUri is " + mUri);
}

Specifically, this is line 370 referenced in the error log:

OutputStream outputStream = mAppCompatActivity.getContentResolver().openOutputStream(Uri.parse(mUri));

Community
  • 1
  • 1
PPartisan
  • 8,173
  • 4
  • 29
  • 48
  • Please post the entire stack trace, and show where lines in that stack trace line up with the code in your question. As it stands, we do not know if you are getting this exception while reading, writing, or something else. Also, what does the `Uri` you get back look like? The exception looks like a `file://` `Uri`, which, if that's what you are getting, is a bug in ARC, as it should be a `content://` `Uri`. – CommonsWare May 12 '15 at 13:39
  • @CommonsWare I have added the error log to my answer - the error occurs whilst trying to open an `OutputStream` using the `Uri` returned from `ACTION_OPEN_DOCUMENT`. – PPartisan May 12 '15 at 14:20
  • Well, that's a `content://` `Uri`, but I have no idea what ARC's `MediaProvider.checkAccess()` is doing there. – CommonsWare May 12 '15 at 14:22

1 Answers1

2

I think you have simply found an interface to access external files we didn't think of (or felt wasn't as important to get working). We patch the filesystem to handle sdcard paths, but do not have any changes to MediaProvider to handle content Uri's like the one you tried to use.

Please feel free to file a bug with the sample code (or at least linking to this post) so we can prioritize fixing it with our other work.

Lloyd Pique
  • 916
  • 5
  • 5