8

When another application is sending a file to my app, I get a Uri via the intent.getExtras().get(EXTRA_STREAM) property. I can then get the bytes of the file using an inputstream : new BufferedInputStream(activity.getContentResolver().openInputStream(uri));

Everything's OK and working so far. Now I'd like to show some kind of progress to my user, but I'm not sure of how to get the total number of bytes of the file without reading the stream completely beforehand (which would defeat the whole purpose of the progress bar) ...

I tried ParcelFileDescriptor fileDesc = activity.getContentResolver().openFileDescriptor(uri, "r"); but this only works with uris of type file://....

For example If I receive a file from Skydrive I get a content://.. Uri, as in : content://com.microsoft.skydrive.content.external/external_property/10C32CC94ECB90C4!155/Sunset.480p.mp4

On such Uri I get (unsurprisingly) a "FileNotFoundException : Not a whole file" exception.

Any sure fire way to get the total size of the stream of data I will get ?

Sébastien Nussbaumer
  • 6,202
  • 5
  • 40
  • 58
  • What about showing an indeterminate progress bar? Of course this does not provide very much feedback about the current operation as a progress bar would do, but consider this as an alternative in case other solutions don't work. – Andy Res Sep 18 '13 at 09:32
  • @AndyRes : that's exactly what I chose to do when other solutions don't work :) – Sébastien Nussbaumer Sep 18 '13 at 13:43
  • With the huge variety of Uris you might receive, the best you could do is to support fetching the total size of as much content types as possible, and, of course, fallback to an indeterminate progress bar. Remember you might as well receive a uri pointing to some live stream that is literally indeterminate. – Sherif elKhatib Sep 18 '13 at 20:58

2 Answers2

10

Even though InputStream.available() is (almost) never a recommended way of getting file size, it might be a viable solution in your case.

The content is already available locally. A server is not involved. So, the following should return the exact file size:

try {

    InputStream inputStream = getContentResolver().openInputStream(uri);

    Log.i("TEST", "File Size: " + inputStream.available());

} catch (FileNotFoundException fnfe) {

    fnfe.printStackTrace();

} catch (IOException ioe) {

    ioe.printStackTrace();

}

I tested this with SkyDrive and Dropbox. The file sizes returned were correct.

Vikram
  • 51,313
  • 11
  • 93
  • 122
  • Thanks @user2558882 : yes this works, as you said. To make it more fireproof, I'm first trying with the ParcelFileDescriptor method, if it doesn't work I'm falling back on InputStream.available(). Looks pretty good to me, will wait for other answers till the end of the bounty, just to make sure there aren't more "canonical/sure" answers, but otherwise, the bounty's for you – Sébastien Nussbaumer Sep 13 '13 at 14:24
  • May I ask why is it that "it might be a viable solution in your case"? – Sherif elKhatib Sep 19 '13 at 07:18
  • @SherifelKhatib : well if you try it, you will see that it works in a lot of cases, so if you add proper error handling to fallback on an indeterminate progress bar it looks pretty viable to me :) – Sébastien Nussbaumer Sep 19 '13 at 10:32
  • @SherifelKhatib I cannot point to any official sources on this. By design, when a server is involved, direct access to data/file isn't possible -> InputStream returns an estimate. But in this case, content/file is available locally: only problem being that you're given a content URI in place of a filepath. Resolving the filepath/file from content URI isn't possible because the file is being cached to app's internal storage. Since there is little or no blocking involved with local files, InputStream.available() _should_ return the correct file size. – Vikram Sep 20 '13 at 06:36
  • I understand that this isn't a compelling argument. But, this is (probably) the only option you have when working with private content uris. @SébastienNussbaumer Thank you for pitching in. – Vikram Sep 20 '13 at 06:40
1

There is no general solution for getting the size of a stream, other than reading the entire stream. This is easily proven: One could create a web server that, for some URL, generates a random stream of text that is terminated at a random time. (In fact, I'm sure such URLs exist, whether by design or not :-) In such a case, the size of the stream isn't known until the last byte has been generated, never mind received.

So, the stream size, if it is sent by the server at all, has to be sent in an application-specific manner.

I've never worked with SkyDrive, but a google search for its API turned up this link, which has the following example for Android Java apps:

public void readFile() {
    String fileId =  "file.a6b2a7e8f2515e5e.A6B2A7E8F2515E5E!141";
       client.getAsync(fileId, new LiveOperationListener() {
        public void onError(LiveOperationException exception, LiveOperation operation) {
               resultTextView.setText("Error reading file: " + exception.getMessage());
           }
           public void onComplete(LiveOperation operation) {
            JSONObject result = operation.getResult();
            String text = "File info:" +
                "\nID = " + result.optString("id") +
                "\nName = " + result.optString("name");
               resultTextView.setText(text);
           }
       });
}

Based on other examples on that page, I would guess that something like result.optString("size") (or maybe result.optInt("size") ?) would give you the size of the file.

Dan Breslau
  • 11,472
  • 2
  • 35
  • 44
  • Thanks Dan. I'm well aware of the fact that streams can be variable/interrupted/etc. In my case it's not that important if the stream is interrupted, as the length of the stream is only used for UI purposes. For the second part of your answer, I'm not working at all with the SkyDrive API, I have just an application that can receive arbitrary data as a stream (see [the Android ContentProvider model](http://developer.android.com/guide/topics/providers/content-providers.html)). I said Skydrive, but it could be DropBox, a PDF viewer, well any application ... so your answer is not relevant – Sébastien Nussbaumer Sep 13 '13 at 14:05