0

I am willing to create an app that sends pictures in reply to the android.intent.action.GET_CONTENT intent. My use case is the Messenger app from the play store, the most common SMS/MMS app, I guess.

I tried to send the picture, but it didn't work well. When sending the MMS to Android phones, they get it properly, however iPhones seem to display it as a fake video that never plays.
I know that it may be caused by my or the foreign operator MMSC server, that thinks it's smart and transcodes the data to what it guesses is a good format.

However, when using the same intent to another app (tried Google's Photos app, and Solid Explorer), it works well with both Android and iPhones.

My guess is that Photos and Solid Explorers send the data back in a proper format, that the MMS apps sends to the MMSC properly, which delivers the picture as-is.

Here's what I tried:

  1. Send a simple Uri of my file in the cache (through Content#getExternalCacheDir(): not working
  2. Send an Uri of my file using a StreamProvider, using CommonWare's CWAC lib, by setting a LocalPathStrategy with Context#getExternalCacheDir() as the root path: not working

Both strategies end up with the image sent back to the MMS app properly, which displays it and the button becomes "Send MMS"; then on Android it's received as a picture, and on iOS it's a fake video that doesn't work.

How should I send the data back to the calling app?

Just to actually explain what I did, here is the first strategy:

Intent result = new Intent();
result.setData(Uri.fromFile(localImage));
setResult(Activity.RESULT_OK, result);
finish();

Here's the second:

Intent result = new Intent();
result.setData(PROVIDER
        .buildUpon()
        .appendPath(StreamProvider.getUriPrefix(AUTHORITY))
        .appendPath(localImage.getName())
        .build());
result.setFlags(FLAG_GRANT_READ_URI_PERMISSION);
setResult(Activity.RESULT_OK, result);
finish();

I think I can pass the bitmap bytes as data in the intent, but I didn't figure out a way to do this.

Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125
  • 1
    Test with something other than MMS and see what the differences are between the approaches. For example, send it as an email attachment. Or, create an `ACTION_GET_CONTENT` client, test the various scenarios, and compare what you get back. Also, you might consider attaching the MIME type to the response `Intent`, in case that helps. – CommonsWare Sep 22 '16 at 22:52
  • I'll go with the client route, that is awesome. I should've thought about that! Thanks. – Benoit Duffez Sep 23 '16 at 07:23
  • The only difference between my intent and Solid Explorer's is that I have flags set to 1 (`FLAG_GRANT_READ_URI_PERMISSION`), and they have the MIME type defined. I'm guessing that their provider doesn't have the same level security as yours for the flags. I have adedd the MIME type, still fails. Google Photos has flags=1, and an additional clip data, which is a `text/uri-list` of one item containing the same URI as the content. So since my result intent is almost the same as Solid Explorer's and Google Photos', I'm guessing that it's on the provider side. Is there a MIME type to set there? – Benoit Duffez Sep 23 '16 at 09:27
  • I have checked that my provider does indeed return a proper MIME type when calling `getType` on the URI. However I couldn't set breakpoints on this part of the code so I don't know if this method gets actually called. I'm stuck now. – Benoit Duffez Sep 23 '16 at 09:59
  • 1
    What version of Android are you running on? If it is Android 4.2-4.4, you should go the route that Google Photos does, and [use `ClipData` to grant read access to your content](https://commonsware.com/blog/2016/08/31/granting-permissions-uri-intent-extra.html). That being said, this would not explain why `Uri.fromFile()` did not work for you for bullet #1. "I don't know if this method gets actually called" -- from your test client, call `getType()` on a `ContentResolver` for your `Uri`. – CommonsWare Sep 23 '16 at 10:59
  • Android 7. I'll try for the method call thanks. – Benoit Duffez Sep 23 '16 at 10:59
  • I'm sorry, for the `getType` being called or not, it's not that I don't know if it works — when called, it does. What I was wondering is if the MMS app did use it. It has to call something on the content provider or on the returned intent that provides more information to the URI, so that it properly sends the MMS to the MMSC, which doesn't do crap about the pic when sending to iPhones. And that is not `getType`, because it works properly and my MMS are still fake videos on iPhones. – Benoit Duffez Sep 23 '16 at 11:19
  • Sorry, but at this point, I am out of ideas. – CommonsWare Sep 23 '16 at 11:24
  • Well apparently the `ClipData` path made it work. I was digging into the AOSP MMS app to see what they did with the URIs and the intent... I'm glad I don't have to do that :) Thanks a lot for your quick support. If you don't write an answer I'll do it for you, however you should do it :) – Benoit Duffez Sep 23 '16 at 11:38
  • Well, I am glad that it is working. I'll need to do some more research as to why `ClipData` is necessary here. I feel another blog post coming up... Feel free to answer this if you want, or else I'll post one next week sometime, when I have a chance to do some more work on this. – CommonsWare Sep 23 '16 at 15:15

1 Answers1

1

Yes, it looks like setResult() also needs an Intent with FLAG_GRANT_READ_URI_PERMISSION and/or FLAG_GRANT_WRITE_URI_PERMISSION, if you are using a ContentProvider for the result Uri.

addFlags() works to add these flags to the Intent, at least back to API Level 19. I have not tested older than this, so there may be versions where you have to use the ClipData trick:

if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT) {
  i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
else if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN) {
  ClipData clip=
    ClipData.newUri(getContentResolver(), "A photo", outputUri);

  i.setClipData(clip);
  i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
else {
  List<ResolveInfo> resInfoList=
    getPackageManager()
      .queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);

  for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    grantUriPermission(packageName, outputUri,
      Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  }
}
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491