Search code examples
androidfacebookandroid-intentfacebook-android-sdk

Posting to Facebook in Android with ACTION_SEND


Officially, Facebook do not support ACTION_SEND fully and do not use the data supplied. Their policy is that the programmer should use their SDK to post on Facebook.

When issuing the intent the list produced includes FB. This is a problem since it expects parameters in a different format. Furthermore, they appears to be no way to remove FB from the list that pops-up even though they actually don't support ACTION_SEND correctly.

It is possible to remove it using an ActionChooser but that then means we would have to have a separate button for sharing via FB.

What is the best solution?


Solution

  • One possible solution:

    The basic idea is to "hijack" the Facebook intent and use it for my own purposes.

    First, I define an activity. This activity contains the code to post the message via the FB SDK.

    package il.co.anykey.apps.yavnehVeChachmaya.activities;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.net.Uri;
    import android.os.Bundle;
    
    import com.facebook.CallbackManager;
    import com.facebook.FacebookCallback;
    import com.facebook.FacebookException;
    import com.facebook.FacebookSdk;
    import com.facebook.share.Sharer;
    import com.facebook.share.model.ShareLinkContent;
    import com.facebook.share.widget.ShareDialog;
    
    /**
     * Created by jonathan.b on 22/07/2015.
     */
    
    public class PostToFBActivity extends Activity {
    
    public static final String EXTRA_CONTENT_URL = "content_url";
    public static final String EXTRA_IMAGE_URL = "image_url";
    
    
    
    private CallbackManager _fbCallbackManager;
    private ShareDialog _fbShareDialog;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        String contentTitle = getIntent().getStringExtra(android.content.Intent.EXTRA_SUBJECT);
        String contentDescription = getIntent().getStringExtra(Intent.EXTRA_TEXT);
        String contentURL = getIntent().getStringExtra(EXTRA_CONTENT_URL);
        String imageURL = getIntent().getStringExtra(EXTRA_IMAGE_URL);
        try {
    
            FacebookSdk.sdkInitialize(getApplicationContext());
            _fbCallbackManager = CallbackManager.Factory.create();
            _fbShareDialog = new ShareDialog(this);
            // this part is optional
            _fbShareDialog.registerCallback(_fbCallbackManager, new FacebookCallback<Sharer.Result>() {
                /**
                 * Called when the dialog completes without error.
                 * @param result Result from the dialog
                 */
                @Override
                public void onSuccess(Sharer.Result result) {
                    finish();
                }
    
                /**
                 * Called when the dialog is canceled.
                 */
                @Override
                public void onCancel() {
                    finish();
                }
    
                /**
                 * Called when the dialog finishes with an error.
                 *
                 * @param error The error that occurred
                 */
                @Override
                public void onError(FacebookException error) {
                    finish();
                }
            });
    
            if (ShareDialog.canShow(ShareLinkContent.class)) {
                ShareLinkContent.Builder builder = new ShareLinkContent.Builder();
    
                if (contentTitle != null)
                    builder.setContentTitle(contentTitle);
    
                if (contentDescription != null)
                    builder.setContentDescription(contentDescription);
    
                if (contentURL != null)
                    builder.setContentUrl(Uri.parse(contentURL));
    
                if (imageURL != null)
                    builder.setImageUrl(Uri.parse(imageURL));
    
                _fbShareDialog.show(builder.build());
    
            }
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
    
    }
    
    
    @Override
    protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        _fbCallbackManager.onActivityResult(requestCode, resultCode, data);
    }
    

    }

    As can be seen, the activity receives the data from the standard ACTION_SEND extras and also a couple of "extra extras". It then invokes the FB SDK to post it. The activity has no layout and exits once the post is complete.

    Next stage is to add the activity to the manifest as so:

        <activity android:name=".activities.PostToFBActivity"            android:noHistory="true" android:screenOrientation="portrait"
                  android:icon="@drawable/facebook_icon"                android:label="Facebook" android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>
        </activity>
    

    Now the activity will be able to respond to ACTION_SEND intents. In order to allow this intent to be accessible only from within this application we add the attribute android:exported="false". In addition, we add two more attributes:

              android:icon="@drawable/facebook_icon"                android:label="Facebook"
    

    These define what icon and text will be shown when the application-chooser pops-up. (@drawable/facebook_icon is a copy of the FB icon). The full significance will be explained below.

    The next stage is to create an IntentChooser which EXCLUDES the Facebook packages (if they are installed).

    private Intent generateCustomChooserIntent(Intent prototype, String[] forbiddenChoices) {
    List<Intent> targetedShareIntents = new ArrayList<Intent>();
    List<HashMap<String, String>> intentMetaInfo = new ArrayList<HashMap<String, String>>();
    Intent chooserIntent;
    
    Intent dummy = new Intent(prototype.getAction());
    dummy.setType(prototype.getType());
    List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(dummy, 0);
    
    if (!resInfo.isEmpty()) {
        for (ResolveInfo resolveInfo : resInfo) {
            if (resolveInfo.activityInfo == null || Arrays.asList(forbiddenChoices).contains(resolveInfo.activityInfo.packageName))
                continue;
    
            HashMap<String, String> info = new HashMap<String, String>();
            info.put("packageName", resolveInfo.activityInfo.packageName);
            info.put("className", resolveInfo.activityInfo.name);
            info.put("simpleName", String.valueOf(resolveInfo.activityInfo.loadLabel(getPackageManager())));
            intentMetaInfo.add(info);
        }
    
        if (!intentMetaInfo.isEmpty()) {
            // sorting for nice readability
            Collections.sort(intentMetaInfo, new Comparator<HashMap<String, String>>() {
                @Override
                public int compare(HashMap<String, String> map, HashMap<String, String> map2) {
                    return map.get("simpleName").compareTo(map2.get("simpleName"));
                }
            });
    
            // create the custom intent list
            for (HashMap<String, String> metaInfo : intentMetaInfo) {
                Intent targetedShareIntent = (Intent) prototype.clone();
                targetedShareIntent.setPackage(metaInfo.get("packageName"));
                targetedShareIntent.setClassName(metaInfo.get("packageName"), metaInfo.get("className"));
                targetedShareIntents.add(targetedShareIntent);
            }
    
            chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), getString(R.string.share));
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
            return chooserIntent;
        }
    }
    
    return Intent.createChooser(prototype, getString(R.string.share));
    

    }

    This code is not mine and comes here: https://gist.github.com/mediavrog/5625602

    Finally, I invoke the ACTION_SEND intent in the normal way with the extra extras I added:

                intent = new Intent(Intent.ACTION_SEND);
            intent.setType("text/plain");
            intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "You Subject");
            intent.putExtra(Intent.EXTRA_TEXT, "You Text.");
            intent.putExtra(PostToFBActivity.EXTRA_CONTENT_URL,"http://your.site.url");
            intent.putExtra(PostToFBActivity.EXTRA_IMAGE_URL,"you.image.url");
    
            String[] blacklist = new String[]{"com.facebook.orca", "com.facebook.katana"};
            startActivity(generateCustomChooserIntent(intent, blacklist));
    

    This will bring up an intent choose where an entry appears for Facebook.

    enter image description here

    However, this is not a real FB intent. Instead it is the one added in our manifest. Since we have set the icon and label of FB, it will appear to the user as if they are invoking FB whereas they are actually using PostToFBActivity.

    I have tested it and it definitely works. Interstingly enough, it works even on a device that doesn't have FB installed.

    I have only used it to post a message with a link to an image. It should be possible to add code that will allow posting of an image from the device.