/**
 * Copyright 2010-present Facebook.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.widget;

import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.*;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.facebook.*;
import com.facebook.android.R;
import com.facebook.internal.Logger;
import com.facebook.internal.ServerProtocol;
import com.facebook.internal.Utility;
import com.facebook.internal.Validate;

/**
 * This class provides a mechanism for displaying Facebook Web dialogs inside a Dialog. Helper
 * methods are provided to construct commonly-used dialogs, or a caller can specify arbitrary
 * parameters to call other dialogs.
 */
public class WebDialog extends Dialog {
    private static final String LOG_TAG = Logger.LOG_TAG_BASE + "WebDialog";
    private static final String DISPLAY_TOUCH = "touch";
    private static final int API_EC_DIALOG_CANCEL = 4201;
    static final String REDIRECT_URI = "fbconnect://success";
    static final String CANCEL_URI = "fbconnect://cancel";
    static final boolean DISABLE_SSL_CHECK_FOR_TESTING = false;

    // width below which there are no extra margins
    private static final int NO_PADDING_SCREEN_WIDTH = 480;
    // width beyond which we're always using the MIN_SCALE_FACTOR
    private static final int MAX_PADDING_SCREEN_WIDTH = 800;
    // height below which there are no extra margins
    private static final int NO_PADDING_SCREEN_HEIGHT = 800;
    // height beyond which we're always using the MIN_SCALE_FACTOR
    private static final int MAX_PADDING_SCREEN_HEIGHT = 1280;

    // the minimum scaling factor for the web dialog (50% of screen size)
    private static final double MIN_SCALE_FACTOR = 0.5;
    // translucent border around the webview
    private static final int BACKGROUND_GRAY = 0xCC000000;

    public static final int DEFAULT_THEME = android.R.style.Theme_Translucent_NoTitleBar;

    private String url;
    private String expectedRedirectUrl = REDIRECT_URI;
    private OnCompleteListener onCompleteListener;
    private WebView webView;
    private ProgressDialog spinner;
    private ImageView crossImageView;
    private FrameLayout contentFrameLayout;
    private boolean listenerCalled = false;
    private boolean isDetached = false;
    private boolean isDismissed = false;

    /**
     * Interface that implements a listener to be called when the user's interaction with the
     * dialog completes, whether because the dialog finished successfully, or it was cancelled,
     * or an error was encountered.
     */
    public interface OnCompleteListener {
        /**
         * Called when the dialog completes.
         *
         * @param values on success, contains the values returned by the dialog
         * @param error  on an error, contains an exception describing the error
         */
        void onComplete(Bundle values, FacebookException error);
    }

    /**
     * Constructor which can be used to display a dialog with an already-constructed URL.
     *
     * @param context the context to use to display the dialog
     * @param url     the URL of the Web Dialog to display; no validation is done on this URL, but it should
     *                be a valid URL pointing to a Facebook Web Dialog
     */
    public WebDialog(Context context, String url) {
        this(context, url, DEFAULT_THEME);
    }

    /**
     * Constructor which can be used to display a dialog with an already-constructed URL and a custom theme.
     *
     * @param context the context to use to display the dialog
     * @param url     the URL of the Web Dialog to display; no validation is done on this URL, but it should
     *                be a valid URL pointing to a Facebook Web Dialog
     * @param theme   identifier of a theme to pass to the Dialog class
     */
    public WebDialog(Context context, String url, int theme) {
        super(context, theme);
        this.url = url;
    }

    /**
     * Constructor which will construct the URL of the Web dialog based on the specified parameters.
     *
     * @param context    the context to use to display the dialog
     * @param action     the portion of the dialog URL following "dialog/"
     * @param parameters parameters which will be included as part of the URL
     * @param theme      identifier of a theme to pass to the Dialog class
     * @param listener the listener to notify, or null if no notification is desired
     */
    public WebDialog(Context context, String action, Bundle parameters, int theme, OnCompleteListener listener) {
        super(context, theme);

        if (parameters == null) {
            parameters = new Bundle();
        }

        // our webview client only handles the redirect uri we specify, so just hard code it here
        parameters.putString(ServerProtocol.DIALOG_PARAM_REDIRECT_URI, REDIRECT_URI);

        parameters.putString(ServerProtocol.DIALOG_PARAM_DISPLAY, DISPLAY_TOUCH);

        Uri uri = Utility.buildUri(
                ServerProtocol.getDialogAuthority(),
                ServerProtocol.getAPIVersion() + "/" + ServerProtocol.DIALOG_PATH + action,
                parameters);
        this.url = uri.toString();
        onCompleteListener = listener;
    }

    /**
     * Sets the listener which will be notified when the dialog finishes.
     *
     * @param listener the listener to notify, or null if no notification is desired
     */
    public void setOnCompleteListener(OnCompleteListener listener) {
        onCompleteListener = listener;
    }

    /**
     * Gets the listener which will be notified when the dialog finishes.
     *
     * @return the listener, or null if none has been specified
     */
    public OnCompleteListener getOnCompleteListener() {
        return onCompleteListener;
    }

    @Override
    public void dismiss() {
        if (isDismissed) {
            // Some paths may cause dismiss() to be called recursively. Break the loop here.
            return;
        }
        isDismissed = true;

        // If dismiss() was called without sending a result to the listener, let's default to a "cancel" result.
        // This will be the case when the user taps the OS-back-button, or the cross-image, or outside the loading
        // interstitial.
        if (!listenerCalled) {
            sendCancelToListener();
        }

        if (webView != null) {
            webView.stopLoading();
        }
        if (!isDetached) {
            if (spinner.isShowing()) {
                spinner.dismiss();
            }
            super.dismiss();
        }
    }

    @Override
    public void onDetachedFromWindow() {
        isDetached = true;
        super.onDetachedFromWindow();
    }

    @Override
    public void onAttachedToWindow() {
        isDetached = false;
        super.onAttachedToWindow();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        spinner = new ProgressDialog(getContext());
        spinner.requestWindowFeature(Window.FEATURE_NO_TITLE);
        spinner.setMessage(getContext().getString(R.string.com_facebook_loading));
        spinner.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialogInterface) {
                dismiss();
            }
        });

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        contentFrameLayout = new FrameLayout(getContext());

        // First calculate how big the frame layout should be
        calculateSize();
        getWindow().setGravity(Gravity.CENTER);

        // resize the dialog if the soft keyboard comes up
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

        /* Create the 'x' image, but don't add to the contentFrameLayout layout yet
         * at this point, we only need to know its drawable width and height
         * to place the webview
         */
        createCrossImage();

        /* Now we know 'x' drawable width and height,
         * layout the webview and add it the contentFrameLayout layout
         */
        int crossWidth = crossImageView.getDrawable().getIntrinsicWidth();

        setUpWebView(crossWidth / 2 + 1);

        /* Finally add the 'x' image to the contentFrameLayout layout and
        * add contentFrameLayout to the Dialog view
        */
        contentFrameLayout.addView(crossImageView, new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));

        setContentView(contentFrameLayout);
    }

    protected void setExpectedRedirectUrl(String expectedRedirectUrl) {
        this.expectedRedirectUrl = expectedRedirectUrl;
    }

    protected Bundle parseResponseUri(String urlString) {
        Uri u = Uri.parse(urlString);

        Bundle b = Utility.parseUrlQueryString(u.getQuery());
        b.putAll(Utility.parseUrlQueryString(u.getFragment()));

        return b;
    }

    protected boolean isListenerCalled() {
        return listenerCalled;
    }

    protected WebView getWebView() {
        return webView;
    }

    private void calculateSize() {
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        // always use the portrait dimensions to do the scaling calculations so we always get a portrait shaped
        // web dialog
        int width = metrics.widthPixels < metrics.heightPixels ? metrics.widthPixels : metrics.heightPixels;
        int height = metrics.widthPixels < metrics.heightPixels ? metrics.heightPixels : metrics.widthPixels;

        int dialogWidth = Math.min(
                getScaledSize(width, metrics.density, NO_PADDING_SCREEN_WIDTH, MAX_PADDING_SCREEN_WIDTH),
                metrics.widthPixels);
        int dialogHeight = Math.min(
                getScaledSize(height, metrics.density, NO_PADDING_SCREEN_HEIGHT, MAX_PADDING_SCREEN_HEIGHT),
                metrics.heightPixels);

        getWindow().setLayout(dialogWidth, dialogHeight);
    }

    /**
     * Returns a scaled size (either width or height) based on the parameters passed.
     * @param screenSize a pixel dimension of the screen (either width or height)
     * @param density density of the screen
     * @param noPaddingSize the size at which there's no padding for the dialog
     * @param maxPaddingSize the size at which to apply maximum padding for the dialog
     * @return a scaled size.
     */
    private int getScaledSize(int screenSize, float density, int noPaddingSize, int maxPaddingSize) {
        int scaledSize = (int) ((float) screenSize / density);
        double scaleFactor;
        if (scaledSize <= noPaddingSize) {
            scaleFactor = 1.0;
        } else if (scaledSize >= maxPaddingSize) {
            scaleFactor = MIN_SCALE_FACTOR;
        } else {
            // between the noPadding and maxPadding widths, we take a linear reduction to go from 100%
            // of screen size down to MIN_SCALE_FACTOR
            scaleFactor = MIN_SCALE_FACTOR +
                    ((double) (maxPaddingSize - scaledSize))
                            / ((double) (maxPaddingSize - noPaddingSize))
                            * (1.0 - MIN_SCALE_FACTOR);
        }
        return (int) (screenSize * scaleFactor);
    }

    protected void sendSuccessToListener(Bundle values) {
        if (onCompleteListener != null && !listenerCalled) {
            listenerCalled = true;
            onCompleteListener.onComplete(values, null);

            dismiss();
        }
    }

    protected void sendErrorToListener(Throwable error) {
        if (onCompleteListener != null && !listenerCalled) {
            listenerCalled = true;
            FacebookException facebookException = null;
            if (error instanceof FacebookException) {
                facebookException = (FacebookException) error;
            } else {
                facebookException = new FacebookException(error);
            }
            onCompleteListener.onComplete(null, facebookException);

            dismiss();
        }
    }

    protected void sendCancelToListener() {
        sendErrorToListener(new FacebookOperationCanceledException());
    }

    private void createCrossImage() {
        crossImageView = new ImageView(getContext());
        // Dismiss the dialog when user click on the 'x'
        crossImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });
        Drawable crossDrawable = getContext().getResources().getDrawable(R.drawable.com_facebook_close);
        crossImageView.setImageDrawable(crossDrawable);
        /* 'x' should not be visible while webview is loading
         * make it visible only after webview has fully loaded
         */
        crossImageView.setVisibility(View.INVISIBLE);
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void setUpWebView(int margin) {
        LinearLayout webViewContainer = new LinearLayout(getContext());
        webView = new WebView(getContext()) {
            /* Prevent NPE on Motorola 2.2 devices
             * See https://groups.google.com/forum/?fromgroups=#!topic/android-developers/ktbwY2gtLKQ
             */
            @Override
            public void onWindowFocusChanged(boolean hasWindowFocus) {
                try {
                    super.onWindowFocusChanged(hasWindowFocus);
                } catch (NullPointerException e) {
                }
            }
        };
        webView.setVerticalScrollBarEnabled(false);
        webView.setHorizontalScrollBarEnabled(false);
        webView.setWebViewClient(new DialogWebViewClient());
        webView.getSettings().setJavaScriptEnabled(true);
        webView.loadUrl(url);
        webView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        webView.setVisibility(View.INVISIBLE);
        webView.getSettings().setSavePassword(false);
        webView.getSettings().setSaveFormData(false);

        webViewContainer.setPadding(margin, margin, margin, margin);
        webViewContainer.addView(webView);
        webViewContainer.setBackgroundColor(BACKGROUND_GRAY);
        contentFrameLayout.addView(webViewContainer);
    }

    private class DialogWebViewClient extends WebViewClient {
        @Override
        @SuppressWarnings("deprecation")
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Utility.logd(LOG_TAG, "Redirect URL: " + url);
            if (url.startsWith(WebDialog.this.expectedRedirectUrl)) {
                Bundle values = parseResponseUri(url);

                String error = values.getString("error");
                if (error == null) {
                    error = values.getString("error_type");
                }

                String errorMessage = values.getString("error_msg");
                if (errorMessage == null) {
                    errorMessage = values.getString("error_description");
                }
                String errorCodeString = values.getString("error_code");
                int errorCode = FacebookRequestError.INVALID_ERROR_CODE;
                if (!Utility.isNullOrEmpty(errorCodeString)) {
                    try {
                        errorCode = Integer.parseInt(errorCodeString);
                    } catch (NumberFormatException ex) {
                        errorCode = FacebookRequestError.INVALID_ERROR_CODE;
                    }
                }

                if (Utility.isNullOrEmpty(error) && Utility
                        .isNullOrEmpty(errorMessage) && errorCode == FacebookRequestError.INVALID_ERROR_CODE) {
                    sendSuccessToListener(values);
                } else if (error != null && (error.equals("access_denied") ||
                        error.equals("OAuthAccessDeniedException"))) {
                    sendCancelToListener();
                } else if (errorCode == API_EC_DIALOG_CANCEL) {
                    sendCancelToListener();
                } else {
                    FacebookRequestError requestError = new FacebookRequestError(errorCode, error, errorMessage);
                    sendErrorToListener(new FacebookServiceException(requestError, errorMessage));
                }
                return true;
            } else if (url.startsWith(WebDialog.CANCEL_URI)) {
                sendCancelToListener();
                return true;
            } else if (url.contains(DISPLAY_TOUCH)) {
                return false;
            }
            // launch non-dialog URLs in a full browser
            getContext().startActivity(
                    new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
            return true;
        }

        @Override
        public void onReceivedError(WebView view, int errorCode,
                String description, String failingUrl) {
            super.onReceivedError(view, errorCode, description, failingUrl);
            sendErrorToListener(new FacebookDialogException(description, errorCode, failingUrl));
        }

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            if (DISABLE_SSL_CHECK_FOR_TESTING) {
                handler.proceed();
            } else {
                super.onReceivedSslError(view, handler, error);

                handler.cancel();
                sendErrorToListener(new FacebookDialogException(null, ERROR_FAILED_SSL_HANDSHAKE, null));
            }
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            Utility.logd(LOG_TAG, "Webview loading URL: " + url);
            super.onPageStarted(view, url, favicon);
            if (!isDetached) {
                spinner.show();
            }
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            if (!isDetached) {
                spinner.dismiss();
            }
            /*
             * Once web view is fully loaded, set the contentFrameLayout background to be transparent
             * and make visible the 'x' image.
             */
            contentFrameLayout.setBackgroundColor(Color.TRANSPARENT);
            webView.setVisibility(View.VISIBLE);
            crossImageView.setVisibility(View.VISIBLE);
        }
    }

    private static class BuilderBase<CONCRETE extends BuilderBase<?>> {
        private Context context;
        private Session session;
        private String applicationId;
        private String action;
        private int theme = DEFAULT_THEME;
        private OnCompleteListener listener;
        private Bundle parameters;

        protected BuilderBase(Context context, String action) {
            Session activeSession = Session.getActiveSession();
            if (activeSession != null && activeSession.isOpened()) {
                this.session = activeSession;
            } else {
                String applicationId = Utility.getMetadataApplicationId(context);
                if (applicationId != null) {
                    this.applicationId = applicationId;
                } else {
                    throw new FacebookException("Attempted to create a builder without an open" +
                            " Active Session or a valid default Application ID.");
                }
            }
            finishInit(context, action, null);
        }

        protected BuilderBase(Context context, Session session, String action, Bundle parameters) {
            Validate.notNull(session, "session");
            if (!session.isOpened()) {
                throw new FacebookException("Attempted to use a Session that was not open.");
            }
            this.session = session;

            finishInit(context, action, parameters);
        }

        protected BuilderBase(Context context, String applicationId, String action, Bundle parameters) {
            if (applicationId == null) {
                applicationId = Utility.getMetadataApplicationId(context);
            }
            Validate.notNullOrEmpty(applicationId, "applicationId");
            this.applicationId = applicationId;

            finishInit(context, action, parameters);
        }

        /**
         * Sets a theme identifier which will be passed to the underlying Dialog.
         *
         * @param theme a theme identifier which will be passed to the Dialog class
         * @return the builder
         */
        public CONCRETE setTheme(int theme) {
            this.theme = theme;
            @SuppressWarnings("unchecked")
            CONCRETE result = (CONCRETE) this;
            return result;
        }

        /**
         * Sets the listener which will be notified when the dialog finishes.
         *
         * @param listener the listener to notify, or null if no notification is desired
         * @return the builder
         */
        public CONCRETE setOnCompleteListener(OnCompleteListener listener) {
            this.listener = listener;
            @SuppressWarnings("unchecked")
            CONCRETE result = (CONCRETE) this;
            return result;
        }

        /**
         * Constructs a WebDialog using the parameters provided. The dialog is not shown,
         * but is ready to be shown by calling Dialog.show().
         *
         * @return the WebDialog
         */
        public WebDialog build() {
            if (session != null && session.isOpened()) {
                parameters.putString(ServerProtocol.DIALOG_PARAM_APP_ID, session.getApplicationId());
                parameters.putString(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, session.getAccessToken());
            } else {
                parameters.putString(ServerProtocol.DIALOG_PARAM_APP_ID, applicationId);
            }

            return new WebDialog(context, action, parameters, theme, listener);
        }

        protected String getApplicationId() {
            return applicationId;
        }

        protected Context getContext() {
            return context;
        }

        protected int getTheme() {
            return theme;
        }

        protected Bundle getParameters() {
            return parameters;
        }

        protected WebDialog.OnCompleteListener getListener() {
            return listener;
        }

        private void finishInit(Context context, String action, Bundle parameters) {
            this.context = context;
            this.action = action;
            if (parameters != null) {
                this.parameters = parameters;
            } else {
                this.parameters = new Bundle();
            }
        }
    }

    /**
     * Provides a builder that allows construction of an arbitrary Facebook web dialog.
     */
    public static class Builder extends BuilderBase<Builder> {
        /**
         * Constructor that builds a dialog using either the active session, or the application
         * id specified in the application/meta-data.
         *
         * @param context the Context within which the dialog will be shown.
         * @param action the portion of the dialog URL following www.facebook.com/dialog/.
         *               See https://developers.facebook.com/docs/reference/dialogs/ for details.
         */
        public Builder(Context context, String action) {
            super(context, action);
        }

        /**
         * Constructor that builds a dialog for an authenticated user.
         *
         * @param context the Context within which the dialog will be shown.
         * @param session the Session representing an authenticating user to use for
         *                showing the dialog; must not be null, and must be opened.
         * @param action the portion of the dialog URL following www.facebook.com/dialog/.
         *               See https://developers.facebook.com/docs/reference/dialogs/ for details.
         * @param parameters a Bundle containing parameters to pass as part of the URL.
         */
        public Builder(Context context, Session session, String action, Bundle parameters) {
            super(context, session, action, parameters);
        }

        /**
         * Constructor that builds a dialog without an authenticated user.
         *
         * @param context the Context within which the dialog will be shown.
         * @param applicationId the application ID to be included in the dialog URL.
         * @param action the portion of the dialog URL following www.facebook.com/dialog/.
         *               See https://developers.facebook.com/docs/reference/dialogs/ for details.
         * @param parameters a Bundle containing parameters to pass as part of the URL.
         */
        public Builder(Context context, String applicationId, String action, Bundle parameters) {
            super(context, applicationId, action, parameters);
        }
    }

    /**
     * Provides a builder that allows construction of the parameters for showing
     * the <a href="https://developers.facebook.com/docs/reference/dialogs/feed">Feed Dialog</a>.
     */
    public static class FeedDialogBuilder extends BuilderBase<FeedDialogBuilder> {
        private static final String FEED_DIALOG = "feed";
        private static final String FROM_PARAM = "from";
        private static final String TO_PARAM = "to";
        private static final String LINK_PARAM = "link";
        private static final String PICTURE_PARAM = "picture";
        private static final String SOURCE_PARAM = "source";
        private static final String NAME_PARAM = "name";
        private static final String CAPTION_PARAM = "caption";
        private static final String DESCRIPTION_PARAM = "description";

        /**
         * Constructor that builds a Feed Dialog using either the active session, or the application
         * ID specified in the application/meta-data.
         *
         * @param context the Context within which the dialog will be shown.
         */
        public FeedDialogBuilder(Context context) {
            super(context, FEED_DIALOG);
        }

        /**
         * Constructor that builds a Feed Dialog using the provided session.
         *
         * @param context the Context within which the dialog will be shown.
         * @param session the Session representing an authenticating user to use for
         *                showing the dialog; must not be null, and must be opened.
         */
        public FeedDialogBuilder(Context context, Session session) {
            super(context, session, FEED_DIALOG, null);
        }

        /**
         * Constructor that builds a Feed Dialog using the provided session and parameters.
         *
         * @param context    the Context within which the dialog will be shown.
         * @param session    the Session representing an authenticating user to use for
         *                   showing the dialog; must not be null, and must be opened.
         * @param parameters a Bundle containing parameters to pass as part of the
         *                   dialog URL. No validation is done on these parameters; it is
         *                   the caller's responsibility to ensure they are valid. For more information,
         *                   see <a href="https://developers.facebook.com/docs/reference/dialogs/feed/">
         *                   https://developers.facebook.com/docs/reference/dialogs/feed/</a>.
         */
        public FeedDialogBuilder(Context context, Session session, Bundle parameters) {
            super(context, session, FEED_DIALOG, parameters);
        }

        /**
         * Constructor that builds a Feed Dialog using the provided application ID and parameters.
         *
         * @param context       the Context within which the dialog will be shown.
         * @param applicationId the application ID to use. If null, the application ID specified in the
         *                      application/meta-data will be used instead.
         * @param parameters    a Bundle containing parameters to pass as part of the
         *                      dialog URL. No validation is done on these parameters; it is
         *                      the caller's responsibility to ensure they are valid. For more information,
         *                      see <a href="https://developers.facebook.com/docs/reference/dialogs/feed/">
         *                      https://developers.facebook.com/docs/reference/dialogs/feed/</a>.
         */
        public FeedDialogBuilder(Context context, String applicationId, Bundle parameters) {
            super(context, applicationId, FEED_DIALOG, parameters);
        }

        /**
         * Sets the ID of the profile that is posting to Facebook. If none is specified,
         * the default is "me". This profile must be either the authenticated user or a
         * Page that the user is an administrator of.
         *
         * @param id Facebook ID of the profile to post from
         * @return the builder
         */
        public FeedDialogBuilder setFrom(String id) {
            getParameters().putString(FROM_PARAM, id);
            return this;
        }

        /**
         * Sets the ID of the profile that the story will be published to. If not specified, it
         * will default to the same profile that the story is being published from. The ID must be a friend who also
         * uses your app.
         *
         * @param id Facebook ID of the profile to post to
         * @return the builder
         */
        public FeedDialogBuilder setTo(String id) {
            getParameters().putString(TO_PARAM, id);
            return this;
        }

        /**
         * Sets the URL of a link to be shared.
         *
         * @param link the URL
         * @return the builder
         */
        public FeedDialogBuilder setLink(String link) {
            getParameters().putString(LINK_PARAM, link);
            return this;
        }

        /**
         * Sets the URL of a picture to be shared.
         *
         * @param picture the URL of the picture
         * @return the builder
         */
        public FeedDialogBuilder setPicture(String picture) {
            getParameters().putString(PICTURE_PARAM, picture);
            return this;
        }

        /**
         * Sets the URL of a media file attached to this post. If this is set, any picture
         * set via setPicture will be ignored.
         *
         * @param source the URL of the media file
         * @return the builder
         */
        public FeedDialogBuilder setSource(String source) {
            getParameters().putString(SOURCE_PARAM, source);
            return this;
        }

        /**
         * Sets the name of the URL being shared. This method only has effect if setLink is called.
         *
         * @param name the name
         * @return the builder
         */
        public FeedDialogBuilder setName(String name) {
            getParameters().putString(NAME_PARAM, name);
            return this;
        }

        /**
         * Sets the caption of the URL being shared. This method only has effect if setLink is called.
         *
         * @param caption the caption
         * @return the builder
         */
        public FeedDialogBuilder setCaption(String caption) {
            getParameters().putString(CAPTION_PARAM, caption);
            return this;
        }

        /**
         * Sets the description of the URL being shared. This method only has effect if setLink is called.
         *
         * @param description the description
         * @return the builder
         */
        public FeedDialogBuilder setDescription(String description) {
            getParameters().putString(DESCRIPTION_PARAM, description);
            return this;
        }
    }

    /**
     * Provides a builder that allows construction of the parameters for showing
     * the <a href="https://developers.facebook.com/docs/reference/dialogs/requests">Requests Dialog</a>.
     */
    public static class RequestsDialogBuilder extends BuilderBase<RequestsDialogBuilder> {
        private static final String APPREQUESTS_DIALOG = "apprequests";
        private static final String MESSAGE_PARAM = "message";
        private static final String TO_PARAM = "to";
        private static final String DATA_PARAM = "data";
        private static final String TITLE_PARAM = "title";

        /**
         * Constructor that builds a Requests Dialog using either the active session, or the application
         * ID specified in the application/meta-data.
         *
         * @param context the Context within which the dialog will be shown.
         */
        public RequestsDialogBuilder(Context context) {
            super(context, APPREQUESTS_DIALOG);
        }

        /**
         * Constructor that builds a Requests Dialog using the provided session.
         *
         * @param context the Context within which the dialog will be shown.
         * @param session the Session representing an authenticating user to use for
         *                showing the dialog; must not be null, and must be opened.
         */
        public RequestsDialogBuilder(Context context, Session session) {
            super(context, session, APPREQUESTS_DIALOG, null);
        }

        /**
         * Constructor that builds a Requests Dialog using the provided session and parameters.
         *
         * @param context    the Context within which the dialog will be shown.
         * @param session    the Session representing an authenticating user to use for
         *                   showing the dialog; must not be null, and must be opened.
         * @param parameters a Bundle containing parameters to pass as part of the
         *                   dialog URL. No validation is done on these parameters; it is
         *                   the caller's responsibility to ensure they are valid. For more information,
         *                   see <a href="https://developers.facebook.com/docs/reference/dialogs/requests/">
         *                   https://developers.facebook.com/docs/reference/dialogs/requests/</a>.
         */
        public RequestsDialogBuilder(Context context, Session session, Bundle parameters) {
            super(context, session, APPREQUESTS_DIALOG, parameters);
        }

        /**
         * Constructor that builds a Requests Dialog using the provided application ID and parameters.
         *
         * @param context       the Context within which the dialog will be shown.
         * @param applicationId the application ID to use. If null, the application ID specified in the
         *                      application/meta-data will be used instead.
         * @param parameters    a Bundle containing parameters to pass as part of the
         *                      dialog URL. No validation is done on these parameters; it is
         *                      the caller's responsibility to ensure they are valid. For more information,
         *                      see <a href="https://developers.facebook.com/docs/reference/dialogs/requests/">
         *                      https://developers.facebook.com/docs/reference/dialogs/requests/</a>.
         */
        public RequestsDialogBuilder(Context context, String applicationId, Bundle parameters) {
            super(context, applicationId, APPREQUESTS_DIALOG, parameters);
        }

        /**
         * Sets the string users receiving the request will see. The maximum length
         * is 60 characters.
         *
         * @param message the message
         * @return the builder
         */
        public RequestsDialogBuilder setMessage(String message) {
            getParameters().putString(MESSAGE_PARAM, message);
            return this;
        }

        /**
         * Sets the user ID or user name the request will be sent to. If this is not
         * specified, a friend selector will be displayed and the user can select up
         * to 50 friends.
         *
         * @param id the id or user name to send the request to
         * @return the builder
         */
        public RequestsDialogBuilder setTo(String id) {
            getParameters().putString(TO_PARAM, id);
            return this;
        }

        /**
         * Sets optional data which can be used for tracking; maximum length is 255
         * characters.
         *
         * @param data the data
         * @return the builder
         */
        public RequestsDialogBuilder setData(String data) {
            getParameters().putString(DATA_PARAM, data);
            return this;
        }

        /**
         * Sets an optional title for the dialog; maximum length is 50 characters.
         *
         * @param title the title
         * @return the builder
         */
        public RequestsDialogBuilder setTitle(String title) {
            getParameters().putString(TITLE_PARAM, title);
            return this;
        }
    }
}
