/** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, * copy, modify, and distribute this software in source code or binary form for use * in connection with the web services and APIs provided by Facebook. * * As with any software that integrates with the Facebook platform, your use of * this software is subject to the Facebook Developer Principles and Policies * [http://developers.facebook.com/policy/]. This copyright notice shall be * included in all copies or substantial portions of the software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.facebook.share.widget; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import com.facebook.AccessToken; import com.facebook.FacebookCallback; import com.facebook.appevents.AppEventsLogger; import com.facebook.internal.AnalyticsEvents; import com.facebook.internal.AppCall; import com.facebook.internal.CallbackManagerImpl; import com.facebook.internal.DialogFeature; import com.facebook.internal.DialogPresenter; import com.facebook.internal.FacebookDialogBase; import com.facebook.internal.FragmentWrapper; import com.facebook.internal.NativeAppCallAttachmentStore; import com.facebook.internal.Utility; import com.facebook.share.Sharer; import com.facebook.share.internal.LegacyNativeDialogParameters; import com.facebook.share.internal.NativeDialogParameters; import com.facebook.share.internal.OpenGraphActionDialogFeature; import com.facebook.share.internal.ShareContentValidation; import com.facebook.share.internal.ShareDialogFeature; import com.facebook.share.internal.ShareFeedContent; import com.facebook.share.internal.ShareInternalUtility; import com.facebook.share.internal.WebDialogParameters; import com.facebook.share.model.ShareContent; import com.facebook.share.model.ShareLinkContent; import com.facebook.share.model.ShareMediaContent; import com.facebook.share.model.ShareOpenGraphContent; import com.facebook.share.model.SharePhoto; import com.facebook.share.model.SharePhotoContent; import com.facebook.share.model.ShareVideoContent; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * Provides functionality to share content via the Facebook Share Dialog */ public final class ShareDialog extends FacebookDialogBase<ShareContent, Sharer.Result> implements Sharer { /** * The mode for the share dialog. */ public enum Mode { /** * The mode is determined automatically. */ AUTOMATIC, /** * The native dialog is used. */ NATIVE, /** * The web dialog is used. */ WEB, /** * The feed dialog is used. */ FEED } private static final String FEED_DIALOG = "feed"; public static final String WEB_SHARE_DIALOG = "share"; private static final String WEB_OG_SHARE_DIALOG = "share_open_graph"; private static final int DEFAULT_REQUEST_CODE = CallbackManagerImpl.RequestCodeOffset.Share.toRequestCode(); private boolean shouldFailOnDataError = false; // Keep track of Mode overrides for logging purposes. private boolean isAutomaticMode = true; /** * Helper to show the provided {@link com.facebook.share.model.ShareContent} using the provided * Activity. No callback will be invoked. * * @param activity Activity to use to share the provided content * @param shareContent Content to share */ public static void show( final Activity activity, final ShareContent shareContent) { new ShareDialog(activity).show(shareContent); } /** * Helper to show the provided {@link com.facebook.share.model.ShareContent} using the provided * Fragment. No callback will be invoked. * * @param fragment android.support.v4.app.Fragment to use to share the provided content * @param shareContent Content to share */ public static void show( final Fragment fragment, final ShareContent shareContent) { show(new FragmentWrapper(fragment), shareContent); } /** * Helper to show the provided {@link com.facebook.share.model.ShareContent} using the provided * Fragment. No callback will be invoked. * * @param fragment android.app.Fragment to use to share the provided content * @param shareContent Content to share */ public static void show( final android.app.Fragment fragment, final ShareContent shareContent) { show(new FragmentWrapper(fragment), shareContent); } private static void show( final FragmentWrapper fragmentWrapper, final ShareContent shareContent) { new ShareDialog(fragmentWrapper).show(shareContent); } /** * Indicates whether it is possible to show the dialog for * {@link com.facebook.share.model.ShareContent} of the specified type. * * @param contentType Class of the intended {@link com.facebook.share.model.ShareContent} to * share. * @return True if the specified content type can be shown via the dialog */ public static boolean canShow(Class<? extends ShareContent> contentType) { return canShowWebTypeCheck(contentType) || canShowNative(contentType); } private static boolean canShowNative(Class<? extends ShareContent> contentType) { DialogFeature feature = getFeature(contentType); return feature != null && DialogPresenter.canPresentNativeDialogWithFeature(feature); } private static boolean canShowWebTypeCheck(Class<? extends ShareContent> contentType) { // If we don't have an instance of a ShareContent, then all we can do is check whether // this is a ShareLinkContent, which can be shared if configured properly. // The instance method version of this check is more accurate and should be used on // ShareDialog instances. // SharePhotoContent currently requires the user staging endpoint, so we need a user access // token, so we need to see if we have one final AccessToken accessToken = AccessToken.getCurrentAccessToken(); final boolean haveUserAccessToken = accessToken != null && !accessToken.isExpired(); return ShareLinkContent.class.isAssignableFrom(contentType) || ShareOpenGraphContent.class.isAssignableFrom(contentType) || (SharePhotoContent.class.isAssignableFrom(contentType) && haveUserAccessToken); } /** * Constructs a new ShareDialog. * @param activity Activity to use to share the provided content. */ public ShareDialog(Activity activity) { super(activity, DEFAULT_REQUEST_CODE); ShareInternalUtility.registerStaticShareCallback(DEFAULT_REQUEST_CODE); } /** * Constructs a new ShareDialog. * @param fragment android.support.v4.app.Fragment to use to share the provided content. */ public ShareDialog(Fragment fragment) { this(new FragmentWrapper(fragment)); } /** * Constructs a new ShareDialog. * @param fragment android.app.Fragment to use to share the provided content. */ public ShareDialog(android.app.Fragment fragment) { this(new FragmentWrapper(fragment)); } private ShareDialog(FragmentWrapper fragmentWrapper) { super(fragmentWrapper, DEFAULT_REQUEST_CODE); ShareInternalUtility.registerStaticShareCallback(DEFAULT_REQUEST_CODE); } // for ShareDialog use only ShareDialog(Activity activity, int requestCode) { super(activity, requestCode); ShareInternalUtility.registerStaticShareCallback(requestCode); } // for ShareDialog use only ShareDialog(Fragment fragment, int requestCode) { this(new FragmentWrapper(fragment), requestCode); } ShareDialog(android.app.Fragment fragment, int requestCode) { this(new FragmentWrapper(fragment), requestCode); } private ShareDialog(FragmentWrapper fragmentWrapper, int requestCode) { super(fragmentWrapper, requestCode); ShareInternalUtility.registerStaticShareCallback(requestCode); } @Override protected void registerCallbackImpl( final CallbackManagerImpl callbackManager, final FacebookCallback<Result> callback) { ShareInternalUtility.registerSharerCallback( getRequestCode(), callbackManager, callback); } @Override public boolean getShouldFailOnDataError() { return this.shouldFailOnDataError; } @Override public void setShouldFailOnDataError(boolean shouldFailOnDataError) { this.shouldFailOnDataError = shouldFailOnDataError; } /** * Call this to check if the Share Dialog can be shown in a specific mode. * * @param mode Mode of the Share Dialog * @return True if the dialog can be shown in the passed in Mode */ public boolean canShow(ShareContent content, Mode mode) { return canShowImpl(content, (mode == Mode.AUTOMATIC) ? BASE_AUTOMATIC_MODE : mode); } /** * Call this to show the Share Dialog in a specific mode * @param mode Mode of the Share Dialog */ public void show(ShareContent content, Mode mode) { isAutomaticMode = (mode == Mode.AUTOMATIC); showImpl(content, isAutomaticMode ? BASE_AUTOMATIC_MODE : mode); } @Override protected AppCall createBaseAppCall() { return new AppCall(getRequestCode()); } @Override protected List<ModeHandler> getOrderedModeHandlers() { ArrayList<ModeHandler> handlers = new ArrayList<>(); handlers.add(new NativeHandler()); handlers.add(new FeedHandler()); // Feed takes precedence for link-shares for Mode.AUTOMATIC handlers.add(new WebShareHandler()); return handlers; } private class NativeHandler extends ModeHandler { @Override public Object getMode() { return Mode.NATIVE; } @Override public boolean canShow(final ShareContent content, boolean isBestEffort) { if (content == null) { return false; } boolean canShowResult = true; if (!isBestEffort) { // The following features are considered best-effort and will not prevent the // native share dialog from being presented, even if the installed version does // not support the feature. // However, to let apps pivot to a different approach or dialog (for example, Web), // we need to be able to signal back when native support is lacking. if (content.getShareHashtag() != null) { canShowResult = DialogPresenter.canPresentNativeDialogWithFeature( ShareDialogFeature.HASHTAG); } if ((content instanceof ShareLinkContent) && (!Utility.isNullOrEmpty(((ShareLinkContent)content).getQuote()))) { canShowResult &= DialogPresenter.canPresentNativeDialogWithFeature( ShareDialogFeature.LINK_SHARE_QUOTES); } } return canShowResult && ShareDialog.canShowNative(content.getClass()); } @Override public AppCall createAppCall(final ShareContent content) { logDialogShare(getActivityContext(), content, Mode.NATIVE); ShareContentValidation.validateForNativeShare(content); final AppCall appCall = createBaseAppCall(); final boolean shouldFailOnDataError = getShouldFailOnDataError(); DialogPresenter.setupAppCallForNativeDialog( appCall, new DialogPresenter.ParameterProvider() { @Override public Bundle getParameters() { return NativeDialogParameters.create( appCall.getCallId(), content, shouldFailOnDataError); } @Override public Bundle getLegacyParameters() { return LegacyNativeDialogParameters.create( appCall.getCallId(), content, shouldFailOnDataError); } }, getFeature(content.getClass())); return appCall; } } private class WebShareHandler extends ModeHandler { @Override public Object getMode() { return Mode.WEB; } @Override public boolean canShow(final ShareContent content, boolean isBestEffort) { return (content != null) && ShareDialog.canShowWebTypeCheck(content.getClass()); } @Override public AppCall createAppCall(final ShareContent content) { logDialogShare(getActivityContext(), content, Mode.WEB); final AppCall appCall = createBaseAppCall(); ShareContentValidation.validateForWebShare(content); Bundle params; if (content instanceof ShareLinkContent) { params = WebDialogParameters.create((ShareLinkContent)content); } else if (content instanceof SharePhotoContent) { final SharePhotoContent photoContent = createAndMapAttachments((SharePhotoContent)content, appCall.getCallId()); params = WebDialogParameters.create(photoContent); } else { params = WebDialogParameters.create((ShareOpenGraphContent)content); } DialogPresenter.setupAppCallForWebDialog( appCall, getActionName(content), params); return appCall; } private String getActionName(ShareContent shareContent) { if (shareContent instanceof ShareLinkContent || shareContent instanceof SharePhotoContent) { return WEB_SHARE_DIALOG; } else if (shareContent instanceof ShareOpenGraphContent) { return WEB_OG_SHARE_DIALOG; } return null; } private SharePhotoContent createAndMapAttachments( final SharePhotoContent content, final UUID callId) { final SharePhotoContent.Builder contentBuilder = new SharePhotoContent.Builder().readFrom(content); final List<SharePhoto> photos = new ArrayList<>(); final List<NativeAppCallAttachmentStore.Attachment> attachments = new ArrayList<>(); for (int i = 0; i < content.getPhotos().size(); i++) { SharePhoto sharePhoto = content.getPhotos().get(i); final Bitmap photoBitmap = sharePhoto.getBitmap(); if (photoBitmap != null) { NativeAppCallAttachmentStore.Attachment attachment = NativeAppCallAttachmentStore.createAttachment(callId, photoBitmap); sharePhoto = new SharePhoto.Builder() .readFrom(sharePhoto) .setImageUrl(Uri.parse(attachment.getAttachmentUrl())) .setBitmap(null) .build(); attachments.add(attachment); } photos.add(sharePhoto); } contentBuilder.setPhotos(photos); NativeAppCallAttachmentStore.addAttachments(attachments); return contentBuilder.build(); } } private class FeedHandler extends ModeHandler { @Override public Object getMode() { return Mode.FEED; } @Override public boolean canShow(final ShareContent content, boolean isBestEffort) { return (content instanceof ShareLinkContent) || (content instanceof ShareFeedContent); } @Override public AppCall createAppCall(final ShareContent content) { logDialogShare(getActivityContext(), content, Mode.FEED); AppCall appCall = createBaseAppCall(); Bundle params; if (content instanceof ShareLinkContent) { ShareLinkContent linkContent = (ShareLinkContent)content; ShareContentValidation.validateForWebShare(linkContent); params = WebDialogParameters.createForFeed(linkContent); } else { ShareFeedContent feedContent = (ShareFeedContent)content; params = WebDialogParameters.createForFeed(feedContent); } DialogPresenter.setupAppCallForWebDialog( appCall, FEED_DIALOG, params); return appCall; } } private static DialogFeature getFeature( Class<? extends ShareContent> contentType) { if (ShareLinkContent.class.isAssignableFrom(contentType)) { return ShareDialogFeature.SHARE_DIALOG; } else if (SharePhotoContent.class.isAssignableFrom(contentType)) { return ShareDialogFeature.PHOTOS; } else if (ShareVideoContent.class.isAssignableFrom(contentType)) { return ShareDialogFeature.VIDEO; } else if (ShareOpenGraphContent.class.isAssignableFrom(contentType)) { return OpenGraphActionDialogFeature.OG_ACTION_DIALOG; } else if (ShareMediaContent.class.isAssignableFrom(contentType)) { return ShareDialogFeature.MULTIMEDIA; } return null; } private void logDialogShare(Context context, ShareContent content, Mode mode) { String displayType; if (isAutomaticMode) { mode = Mode.AUTOMATIC; } switch (mode) { case AUTOMATIC: displayType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_SHOW_AUTOMATIC; break; case WEB: displayType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_SHOW_WEB; break; case NATIVE: displayType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_SHOW_NATIVE; break; default: displayType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_SHOW_UNKNOWN; break; } String contentType; DialogFeature dialogFeature = getFeature(content.getClass()); if (dialogFeature == ShareDialogFeature.SHARE_DIALOG) { contentType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_CONTENT_STATUS; } else if (dialogFeature == ShareDialogFeature.PHOTOS) { contentType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_CONTENT_PHOTO; } else if (dialogFeature == ShareDialogFeature.VIDEO) { contentType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_CONTENT_VIDEO; } else if (dialogFeature == OpenGraphActionDialogFeature.OG_ACTION_DIALOG) { contentType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_CONTENT_OPENGRAPH; } else { contentType = AnalyticsEvents.PARAMETER_SHARE_DIALOG_CONTENT_UNKNOWN; } AppEventsLogger logger = AppEventsLogger.newLogger(context); Bundle parameters = new Bundle(); parameters.putString( AnalyticsEvents.PARAMETER_SHARE_DIALOG_SHOW, displayType ); parameters.putString( AnalyticsEvents.PARAMETER_SHARE_DIALOG_CONTENT_TYPE, contentType ); logger.logSdkEvent(AnalyticsEvents.EVENT_SHARE_DIALOG_SHOW, null, parameters); } }