title: Using ContentResolver to write to external files [Chrome-ARC]
body: <p>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 <code>minSdkVersion</code> of my app is <code>19</code>.</p>
  
  <p>My app is a text editor, and what I want to achieve is to load text using <code>Intent.ACTION_OPEN_DOCUMENT</code>, saving the file <code>Uri</code> in the process. Later, when I want to save, I can use this <code>Uri</code> 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 <code>ACTION_SEND</code> Intent if there is a <code>FileNotFoundException</code>).</p>
  
  <p>Here is the relevant code. Could anyone suggest a workaround, or any improvement I could make?</p>
  
  <pre><code>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 &amp;&amp; c.moveToFirst()){
                      title = c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));
  
                      String[] cols = c.getColumnNames();
                      for (int i = 0; i &lt; 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();
                      }
                  }
              }
          }
      }
  }
  </code></pre>
  
  <p>and in the <code>EditorFragment</code> class:</p>
  
  <pre><code>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);
      }
  }
  </code></pre>
  
  <p>And finally, the error message (to repeat myself from above, this only occurs on my Chromebook and not an Android test device):</p>
  
  <p><code>java.io.FileNotFoundException: Can't access data/data/org.chromium.arc/external/(etc.)...</code></p>
  
  <p>Thanks!</p>
  
  <p><strong>EDIT:</strong></p>
  
  <p>Whilst I couldn't figure out how to get the stack trace from my Chromebook, I was able to add the <a href="https://stackoverflow.com/questions/10120709/difference-between-printstacktrace-and-tostring">following method from the accepted answer on this question</a> to print it to an <code>EditText</code>. I also printed the <code>Uri</code> at the end. Here is the output:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>The error occurs in the following <code>try/catch</code> block: </p>
  
  <pre><code>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);
  }
  </code></pre>
  
  <p>Specifically, this is line <code>370</code> referenced in the error log:</p>
  
  <p><code>OutputStream outputStream = mAppCompatActivity.getContentResolver().openOutputStream(Uri.parse(mUri));</code></p>