3

This question contained several sub-questions. I am forking these, starting by this question. I'll eventually clean up by deleting this question.

The following program will in theory share a hello-world text file. The code runs, but sharing to either Dropbox or to Gmail (by way of just two concrete examples) fails.

public class MainActivity extends Activity {
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String filename = "hellow.txt";
        String fileContents = "Hello, World!\n";
        byte[] bytes = fileContents.getBytes();
        FileOutputStream fos = null;
        try {
            fos = this.openFileOutput(filename, MODE_PRIVATE);
            fos.write(bytes);
        } catch (IOException e) {                       
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {                       
                e.printStackTrace();
            }
        } 

        File file = new File(filename);
        Intent shareIntent = new Intent();
        shareIntent.setAction(Intent.ACTION_SEND);
        shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
        shareIntent.setType("application/txt");
        startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));

        file.delete();
    }
}

Aside from adding a value for send_to in res/values/strings.xml, the only other pair of changes I did to the generic Hello, World that Eclipse creates is adding the following <provider> tag in AndroidManifest.xml:

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.mycorp.helloworldtxtfileprovider.MainActivity"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/my_paths" />
    </provider>

    <activity
        android:name="com.mycorp.helloworldtxtfileprovider.MainActivity"
        ...

... and adding the following in res/xml/my_paths.xml

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="." />
</paths>

My main question is the first, but while you're at this topic, a discussion of questions 2-4 would also be interesting.

  1. Why does the program above fail?
  2. Is it indeed the case that if one needs a custom ContentProvider, then one needs to extend that class, but if one just needs a FileProvider, then one can use that class without derivation?
  3. In this code, I needed to use filename twice—once with openFileOutput and another with new File(). Is there a way to avoid this duplication (that would guarantee that the same file is being referenced)?
  4. Is it safe to delete the file right after startActivity(..) is called, or is it necessary to devise a callback to wait learning that the file has been uploaded/shared. (The real file may take some time to share/upload.)

Edit

The code runs fine and shows a list of apps to send to.

If I select Dropbox, I can select the location just fine. Dropbox sends the notifications "Uploading to Dropbox" followed by "Upload failed: my_file.txt".

If I select Gmail, I can fill the recipient and the file appears to be attached, but after "sending message.." I get "Couldn't send attachment".

Community
  • 1
  • 1
Calaf
  • 10,113
  • 15
  • 57
  • 120
  • 3
    We'd prefer that you not go through and delete questions that others have taken the time to answer. Questions aren't just for your benefit, but for the many people in the future who might be looking for something similar. – Brad Larson Nov 26 '13 at 18:04
  • @BradLarson I had broken the one-question/one-post rule and am paying the price. My doing so left it open for some kind soul like the person who answered to try to help by giving sensible answers to the less important issues, without resolving the crux of the question (part 1). As I mentioned in the comments, I would be breaking the question into sub-parts. I tried a question with a focus on part 1. No one ventured an answer. I preferred to delete it. Ultimately, since asking the question I realized that the issue is much more complicated, when the user is in flight-mode for instance. TBC. – Calaf Nov 26 '13 at 18:27

2 Answers2

6

1.

Use FileProvider.getUriForFile(...) to construct the URI. This will direct the started activity to your FileProvider (which can then serve the file from your app's private files directory). Uri.fromFile(...) does not work because the started activity will try to directly access the private directory.

Set FLAG_GRANT_READ_URI_PERMISSION so that the started activity is granted read permission for the URI constructed by the FileProvider.

Finally, "text/plain" might work better than "application/txt" as MIME type.

I've had some problems getting this to work consistently across devices. This is my best bet so far (will edit if I ever refine it):

    final String auth = "org.volley.sndcard_android.fileprovider";
    final Uri uri = FileProvider.getUriForFile(activity, auth, file);
    final String type = activity.getContentResolver().getType(uriForFile);

    final Intent shareIntent = new Intent(Intent.ACTION_SEND);
    shareIntent.setDataAndType(uri, type);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uriForFile);
    shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    final Intent chooser = Intent.createChooser(shareIntent, "<title>");
    activity.startActivity(chooser);

Setting only the type works on my Nexus 5 but not on my Samsung tablet. It seems the Samsung tablet needs the URI as data in order to grant the permission. Also note that intent.getData() cancels any previous calls to intent.setType() and vice versa, so you have to use the combined method, as done above.

Gmail seems to interpret the additional data as a default To-address. Highly annoying! If anyone has a better solution, please share it (pun intended, I'm from Gothenburg).

volley
  • 6,651
  • 1
  • 27
  • 28
  • 1
    The only way I was able to attach files from my app's secure storage using FileProvider was manually granting and revoking permission for my files as in this answer http://stackoverflow.com/a/18332000/1082344 (working on Samsung Galaxy Tab 10.1 with android version 3.1, Galaxy Ace with android 2.3.4, and Galaxy S3 with android 4.1.2) – IsaacCisneros Feb 28 '14 at 11:05
0

2.Yes, it is indeed. You see that ContentProvider is an abstract class, so to use custom content provider one must have to extend it. As FileProvider is a subclass of ContentProvider (which is not abstract), programmers can use FileProvider without subclassing it.

3.For ensuring same file you can follow the sample code below -

String fileName = "my_file.txt";
String fileData = "sample file content";

// Get the file
File file = new File(this.getFilesDir(), fileName);
if (!file.exists()) {
    file.createNewFile();
}

// Write data to file
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(fileData);
fileWriter.close();

// Get the uri
Uri uri = Uri.fromFile(file);

// Share the file with corresponding uri
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("application/txt");
startActivity(Intent.createChooser(shareIntent, "Send To"));

4.No, it's not safe to delete the file right after you call startActivity(). Because startActivity() is non-blocking function call and it'll return immediately. You have to wait for the time the file is being shared. You can do that by using startActivityForResult() . See if it serves the purpose.

imranhasanhira
  • 357
  • 2
  • 7
  • It's stunning how I managed to ask part 2 in this form. In any case, replacing my code with yours doesn't help. Could you run it on your side and confirm it works? You missed a try-catch block so I'm guessing you didn't run it. – Calaf Sep 25 '13 at 18:35
  • The code was for getting the idea that you can use the same file object. You are close, I compiled the code successfully. I ran the code and it shows a list of apps that I can choose to share file with. I removed the try-catch block while posting. – imranhasanhira Sep 25 '13 at 18:53
  • The code I posted also runs fine and provides a list of apps to send to. I updated the question with the notifications I get from Dropbox and Gmail. Can you confirm that you are able to upload to one or both on your side? – Calaf Sep 25 '13 at 19:05
  • I couldn't get success of uploading it through the above code. However I used [Luke Sleeman's answer](http://stackoverflow.com/questions/18249007/how-to-use-support-fileprovider-for-sharing-content-to-other-apps) and now getting `java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider` for gmail, and for dropbox it is also another Permission error. – imranhasanhira Sep 25 '13 at 21:57
  • I experimented with many variations on Luke Sleeman's answer before posting this question. Thanks for contributing to 2, 3, and 4, but it's really question 1 that I'm after. – Calaf Sep 26 '13 at 00:41