I'm attempting to take a very generic approach in providing sharing options for sharing images from my app's private storage, but I've encountered a problem that appears to be specific to sharing images to the Facebook app (com.facebook.katana):
I launch an intent with EXTRA_STREAM containing a Uri to an image inside the private directory of my app.
Through a ContentProvider, I provide access to the desired file by returning the following in openFile():
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
When sharing to Facebook, the image doesn't appear. Every other app, however, does work. Debugging calls into my ContentProvider, I see that Facebook does indeed query the ContentProvider, and openFile() is hit. Nevertheless, the image doesn't appear.
After much head scratching, I realized I was returning null as the MIME type. I changed the result of getType() to return "image/png", and that was it: Facebook accepted my image:
#Nullable
#Override
public String getType(#NonNull Uri uri) {
// It's absolutely imperative that we provide the MIME type, otherwise some apps like
// Facebook will simply ignore the file
return "image/png";
}
I would point out that you should return the actual MIME type of the file associated with the Uri; I'm returning PNG here because I'm lazy, and I know that all my images are of that type.
Related
I get an Uri and I do:
String s = Uri.decode(uri.toString());
But when I encode the string again I haven't got the same result:
Uri uri = Uri.parse(Uri.encode(s));
Uri received without decoding (just called toString()):
content://com.android.externalstorage.documents/document/primary%3APictures%2FScreenshots%2FScreenshot_20190807-153556.png
Uri created via parse/encode method:
content%3A%2F%2Fcom.android.externalstorage.documents%2Fdocument%2Fprimary%3APictures%2FScreenshots%2FScreenshot_20190807-153556.png
Is there a way to re-parse correctly the Uri?
Is there a way to re-parse correctly the Uri?
No. Hold onto the original Uri. This is not significantly different than converting an image to monochrome, then wondering how to get the original color image back. The decode() and encode() methods are not designed to decode and encode Uri values, but rather specific pieces (e.g., query parameter values).
I'm using the Universal Image Loader on my project. I want to do a specific action if the image was loaded from server and nothing if loaded from memory or disk cache.
So far the only way I found to do this is to ping memory and disk cache in advance to see if they contain the image. This is not very nice since in case it's not there, the normal flow to display image will repeat this checks so I'm duplicating efforts.
Is there a better way to do this? I saw in the code a LoadedFrom variable that is passed across different internal methods, but is not returned on public API.
YOu can use this codes to check image cached or not:
public static boolean checkCacheImageUri(String uri) {
List<String> findCacheKeysForImageUriThumbnail = MemoryCacheUtils.findCacheKeysForImageUri(
uri, ImageLoader.getInstance().getMemoryCache());
if (findCacheKeysForImageUriThumbnail != null
&& !findCacheKeysForImageUriThumbnail.isEmpty()) {
return true;
}
return false;
}
Given an absolute Uri and a relative Uri or relative path, how do you get the absolute Uri pointing to the relative location?
For example, suppose we have the Uri for file:///android_asset/dir, pointing to a location in our assets. Further suppose that elsewhere, we have a relative path of /foo. The absolute Uri for that relative path should be file:///android_asset/foo. Is there something on Uri, or elsewhere in the Android SDK, that I am missing that give me that result Uri?
Uri.withAppendedPath() is not the answer, as all it seems to do is handle trailing directory separators:
Uri abs=Uri.parse("file:///android_asset/");
Uri rel=Uri.withAppendedPath(abs, "/foo");
Assert.assertEquals("file:///android_asset/foo", rel.toString());
// actually returns file:///android_asset//foo
Uri abs2=Uri.parse("file:///android_asset/dir");
Uri rel2=Uri.withAppendedPath(abs2, "/foo");
Assert.assertEquals("file:///android_asset/foo", rel2.toString());
// actually returns file:///android_asset/dir//foo
Uri.Builder, via buildUpon() on Uri, is not an improvement:
Uri rel3=abs.buildUpon().appendPath("/foo").build();
Assert.assertEquals("file:///android_asset/foo", rel3.toString());
// actually returns file:///android_asset/%2Ffoo
Uri rel4=abs.buildUpon().appendEncodedPath("/foo").build();
Assert.assertEquals("file:///android_asset/foo", rel4.toString());
// actually returns file:///android_asset//foo
In a pinch I can try using java.net.URL and its URL(URL context, String spec) constructor, or just roll some code for it, but I was hoping to stay in the realm of Android Uri values if possible, just for any quirks differentiating URL and Uri.
Android doesn't make this easy.
In my case, I had to take a base url that may or may not have an included path:
http://www.myurl.com/myapi/
...and append a REST API method path, like:
api/where/agencies-with-coverage.json
...to produce the entire url:
http://www.myurl.com/myapi/api/where/agencies-with-coverage.json
Here's how I did it (compiled from various methods within the app - there may be a simpler way of doing this):
String baseUrlString = "http://www.myurl.com/myapi/";
String pathString = "api/where/agencies-with-coverage.json";
Uri.Builder builder = new Uri.Builder();
builder.path(pathString);
Uri baseUrl = Uri.parse(baseUrlString);
// Copy partial path (if one exists) from the base URL
Uri.Builder path = new Uri.Builder();
path.encodedPath(baseUrl.getEncodedPath());
// Then, tack on the rest of the REST API method path
path.appendEncodedPath(builder.build().getPath());
// Finally, overwrite builder with the full URL
builder.scheme(baseUrl.getScheme());
builder.encodedAuthority(baseUrl.getEncodedAuthority());
builder.encodedPath(path.build().getEncodedPath());
// Final Uri
Uri finalUri = builder.build();
In my case, the Builder classes for the API client code assembled the path prior to combining it with the baseURL, so that explains the order of things above.
If I've pulled together the above code correctly, it should handle port numbers as well as spaces in the URL string.
I pulled this source code from the OneBusAway Android app, specifically the ObaContext class. Note that this code on Github also handles the additional case where the user typed in a baseUrl (String serverName = Application.get().getCustomApiUrl() in the above code) that should override the region base URL (mRegion.getObaBaseUrl()), and the user-entered URL may not have http:// in front of it.
The unit tests that pass for the above code on Github, including cases where port numbers and spaces are included in the baseUrl and path, and the leading/trailing / may or may not be included, are here on Github. Related issues on Github where I was banging my head on the wall to try and get this all to work - 72, 126, 132.
I haven't tried this with non-HTTP URIs, but I believe it may work more generally.
There is an equivalent to urllib.parse.urljoin (Python) in Android URI.create(baseUrl).resolve(path).
import java.net.URI
URI.create("https://dmn92m25mtw4z.cloudfront.net/helpvids/f3_4/hls_480/480.m3u8")
.resolve("0.ts")
// output:
// https://dmn92m25mtw4z.cloudfront.net/helpvids/f3_4/hls_480/0.ts
Sean Barbeau answer returns wrong URL, it's just appending the 0.ts to the url.
In order to send an image to a server from my android application, I have to get mime type of the image (or, at least, its extension).
I get the image from a ACTION_GET_CONTENT intent. Some applications, like Dropbox, send a file:// scheme data, so I can guess the extension using MimeTypeMap.getFileExtensionFromUrl(), but some others, like Google Drive, send a content:// scheme, which not permit to do so.
I've tried many things before posting here, like this:
AssetFileDescriptor asset = getContentResolver().openAssetFileDescriptor(getIntent().getData(), "r");
FileInputStream stream = asset.createInputStream();
String mime = URLConnection.guessContentTypeFromStream(stream); // returns null
The only workaround I think is to decode the asset into a Bitmap, then generate a new compressed image (using Bitmap.compress()), but it will add some work to the phone, and it could change the format/quality of original image, so I reserve it only if there is no other solution.
Does anyone have an idea about my problem? Thanks a lot for help ;)
As suggested by #sh3psheep on Twitter, I've tried this, and it works for content:// schemes:
Uri data = getIntent().getData();
String mime = getContentResolver().getType(data); // returns correct MIME type, such as "image/png"
The only con I found is that it does not support file:// scheme, but we can handle it using method I described in question.
do you know a method or the method used by the evernote widget to retrieve the thumbnails that we see in a very convenient way (before retrieving the entire note) in the main interface or in the widget?
I saw the post method via http request, but it seems complicated when not sharing notes and perhaps there are more straightforward methods via a direct evernote API call or via reading files stored by the application(s)?
The widget pulls the thumbnails from the Evernote app's Content Provider.
Something like this should work.
In your manifest :
<permission android:name="evernote.permission.READ_DATA" android:protectionLevel="normal" />
In your java code:
final Uri AUTHORITY_URI = Uri.parse("content://com.evernote.evernoteprovider");
final Uri NOTE_URI = Uri.withAppendedPath(AUTHORITY_URI, "notes");
private FileDescriptor getNoteThumbnail(Context context, String noteGuid) throws FileNotFoundException {
Uri thumbnailUri = NOTE_URI.buildUpon().appendEncodedPath(noteGuid).appendPath("thumbnail").appendPath("data").build();
ContentResolver cr = context.getContentResolver();
return cr.openFileDescriptor(thumbnailUri, "r").getFileDescriptor();
}
The HTTP Post method is not too complex. I'm not familiar with Java but this is an example in python that should be pretty straightforward to port to android:
import requests
ACCESS_TOKEN="INSERT YOUR AUTH TOKEN OR DEV TOKEN HERE"
payload={'auth':ACCESS_TOKEN} #map auth token to param "auth"
r=requests.post('https://sandbox.evernote.com//shard/s1/thm/note/e679c010-d8b2-4644-9eag-56bd31c84be7.jpg?size=75',data=payload, stream=True) #returns a binary of the picture type in header
f=open('thumbnail.jpg','wb') #open file for binary writing
f.write(r.content) #write binary contents of request to a file
f.close() #close the file
The only parameter of the POST request is "auth" and it should contain your auth token (or dev token). The rest of the information comes from the URL itself and is of the form:
[domain].evernote.com/shard/[shard]/thm/note/[guid]
where
[domain] is sandbox (for the sandbox) and www (for production)
[shard] is the shard the account is on (should be something like s1)
[guid] is the notebook guid
with the optional parameters appended at the end of .jpg, .gif, .bmp or .png as well as the optional parameter at the end of the URL ?size=[1 to 299] (default is 300px square)
for example on the sandbox with shard s1, note guid "e669c090-d8b2-4324-9eae-56bd31c64af7" to return a jpg of size 150px square the URL would look like this:
https://sandbox.evernote.com/shard/s1/thm/note/e669c090-d8b2-4324-9eae-56bd31c64af7.jpg?size=75