com.facebook.widget.ShareButton.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.widget.ShareButton.java

Source

/**
 * 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.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import com.facebook.*;
import com.facebook.android.R;
import com.facebook.internal.AnalyticsEvents;
import com.facebook.model.GraphUser;
import com.facebook.internal.SessionAuthorizationType;
import com.facebook.internal.SessionTracker;
import com.facebook.internal.Utility;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * A Log In/Log Out button that maintains session state and logs in/out for the
 * app.
 * <p/>
 * This control will create and use the active session upon construction if it
 * has the available data (if the app ID is specified in the manifest). It will
 * also open the active session if it does not require user interaction (i.e. if
 * the session is in the {@link com.facebook.SessionState#CREATED_TOKEN_LOADED}
 * state. Developers can override the use of the active session by calling the
 * {@link #setSession(com.facebook.Session)} method.
 */
public class ShareButton extends Button {

    private static final String TAG = ShareButton.class.getName();
    private String applicationId = null;
    private SessionTracker sessionTracker;
    private GraphUser user = null;
    private Session userInfoSession = null; // the Session used to fetch the
    // current user info
    private boolean confirmLogout;
    private boolean fetchUserInfo;
    private UserInfoChangedCallback userInfoChangedCallback;
    private Fragment parentFragment;
    private LoginButtonProperties properties = new LoginButtonProperties();
    private String loginLogoutEventName = AnalyticsEvents.EVENT_LOGIN_VIEW_USAGE;

    static class LoginButtonProperties {
        private SessionDefaultAudience defaultAudience = SessionDefaultAudience.FRIENDS;
        private List<String> permissions = Collections.<String>emptyList();
        private SessionAuthorizationType authorizationType = null;
        private OnErrorListener onErrorListener;
        private SessionLoginBehavior loginBehavior = SessionLoginBehavior.SSO_WITH_FALLBACK;
        private Session.StatusCallback sessionStatusCallback;

        public void setOnErrorListener(OnErrorListener onErrorListener) {
            this.onErrorListener = onErrorListener;
        }

        public OnErrorListener getOnErrorListener() {
            return onErrorListener;
        }

        public void setDefaultAudience(SessionDefaultAudience defaultAudience) {
            this.defaultAudience = defaultAudience;
        }

        public SessionDefaultAudience getDefaultAudience() {
            return defaultAudience;
        }

        public void setReadPermissions(List<String> permissions, Session session) {
            if (SessionAuthorizationType.PUBLISH.equals(authorizationType)) {
                throw new UnsupportedOperationException(
                        "Cannot call setReadPermissions after setPublishPermissions has been called.");
            }
            if (validatePermissions(permissions, SessionAuthorizationType.READ, session)) {
                this.permissions = permissions;
                authorizationType = SessionAuthorizationType.READ;
            }
        }

        public void setPublishPermissions(List<String> permissions, Session session) {
            if (SessionAuthorizationType.READ.equals(authorizationType)) {
                throw new UnsupportedOperationException(
                        "Cannot call setPublishPermissions after setReadPermissions has been called.");
            }
            if (validatePermissions(permissions, SessionAuthorizationType.PUBLISH, session)) {
                this.permissions = permissions;
                authorizationType = SessionAuthorizationType.PUBLISH;
            }
        }

        private boolean validatePermissions(List<String> permissions, SessionAuthorizationType authType,
                Session currentSession) {
            if (SessionAuthorizationType.PUBLISH.equals(authType)) {
                if (Utility.isNullOrEmpty(permissions)) {
                    throw new IllegalArgumentException("Permissions for publish actions cannot be null or empty.");
                }
            }
            if (currentSession != null && currentSession.isOpened()) {
                if (!Utility.isSubset(permissions, currentSession.getPermissions())) {
                    Log.e(TAG, "Cannot set additional permissions when session is already open.");
                    return false;
                }
            }
            return true;
        }

        List<String> getPermissions() {
            return permissions;
        }

        public void clearPermissions() {
            permissions = null;
            authorizationType = null;
        }

        public void setLoginBehavior(SessionLoginBehavior loginBehavior) {
            this.loginBehavior = loginBehavior;
        }

        public SessionLoginBehavior getLoginBehavior() {
            return loginBehavior;
        }

        public void setSessionStatusCallback(Session.StatusCallback callback) {
            this.sessionStatusCallback = callback;
        }

        public Session.StatusCallback getSessionStatusCallback() {
            return sessionStatusCallback;
        }
    }

    /**
     * Specifies a callback interface that will be called when the button's
     * notion of the current user changes (if the fetch_user_info attribute is
     * true for this control).
     */
    public interface UserInfoChangedCallback {
        /**
         * Called when the current user changes.
         * 
         * @param user
         *            the current user, or null if there is no user
         */
        void onUserInfoFetched(GraphUser user);
    }

    /**
     * Callback interface that will be called when a network or other error is
     * encountered while logging in.
     */
    public interface OnErrorListener {
        /**
         * Called when a network or other error is encountered.
         * 
         * @param error
         *            a FacebookException representing the error that was
         *            encountered.
         */
        void onError(FacebookException error);
    }

    /**
     * Create the LoginButton.
     * 
     * @see View#View(Context)
     */
    public ShareButton(Context context) {
        super(context);
        initializeActiveSessionWithCachedToken(context);
        // since onFinishInflate won't be called, we need to finish
        // initialization ourselves
        finishInit();
    }

    /**
     * Create the LoginButton by inflating from XML
     * 
     * @see View#View(Context, AttributeSet)
     */
    public ShareButton(Context context, AttributeSet attrs) {
        super(context, attrs);

        if (attrs.getStyleAttribute() == 0) {
            // apparently there's no method of setting a default style in xml,
            // so in case the users do not explicitly specify a style, we need
            // to use sensible defaults.
            this.setGravity(Gravity.CENTER);
            this.setBackgroundResource(R.drawable.icon_share_fb);
        }
        parseAttributes(attrs);
        if (!isInEditMode()) {
            initializeActiveSessionWithCachedToken(context);
        }
    }

    /**
     * Create the LoginButton by inflating from XML and applying a style.
     * 
     * @see View#View(Context, AttributeSet, int)
     */
    public ShareButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        parseAttributes(attrs);
        initializeActiveSessionWithCachedToken(context);
    }

    /**
     * Sets an OnErrorListener for this instance of LoginButton to call into
     * when certain exceptions occur.
     * 
     * @param onErrorListener
     *            The listener object to set
     */
    public void setOnErrorListener(OnErrorListener onErrorListener) {
        properties.setOnErrorListener(onErrorListener);
    }

    /**
     * Returns the current OnErrorListener for this instance of LoginButton.
     * 
     * @return The OnErrorListener
     */
    public OnErrorListener getOnErrorListener() {
        return properties.getOnErrorListener();
    }

    /**
     * Sets the default audience to use when the session is opened. This value
     * is only useful when specifying write permissions for the native login
     * dialog.
     * 
     * @param defaultAudience
     *            the default audience value to use
     */
    public void setDefaultAudience(SessionDefaultAudience defaultAudience) {
        properties.setDefaultAudience(defaultAudience);
    }

    /**
     * Gets the default audience to use when the session is opened. This value
     * is only useful when specifying write permissions for the native login
     * dialog.
     * 
     * @return the default audience value to use
     */
    public SessionDefaultAudience getDefaultAudience() {
        return properties.getDefaultAudience();
    }

    /**
     * Set the permissions to use when the session is opened. The permissions
     * here can only be read permissions. If any publish permissions are
     * included, the login attempt by the user will fail. The LoginButton can
     * only be associated with either read permissions or publish permissions,
     * but not both. Calling both setReadPermissions and setPublishPermissions
     * on the same instance of LoginButton will result in an exception being
     * thrown unless clearPermissions is called in between.
     * <p/>
     * This method is only meaningful if called before the session is open. If
     * this is called after the session is opened, and the list of permissions
     * passed in is not a subset of the permissions granted during the
     * authorization, it will log an error.
     * <p/>
     * Since the session can be automatically opened when the LoginButton is
     * constructed, it's important to always pass in a consistent set of
     * permissions to this method, or manage the setting of permissions outside
     * of the LoginButton class altogether (by managing the session explicitly).
     * 
     * @param permissions
     *            the read permissions to use
     * 
     * @throws UnsupportedOperationException
     *             if setPublishPermissions has been called
     */
    public void setReadPermissions(List<String> permissions) {
        properties.setReadPermissions(permissions, sessionTracker.getSession());
    }

    /**
     * Set the permissions to use when the session is opened. The permissions
     * here can only be read permissions. If any publish permissions are
     * included, the login attempt by the user will fail. The LoginButton can
     * only be associated with either read permissions or publish permissions,
     * but not both. Calling both setReadPermissions and setPublishPermissions
     * on the same instance of LoginButton will result in an exception being
     * thrown unless clearPermissions is called in between.
     * <p/>
     * This method is only meaningful if called before the session is open. If
     * this is called after the session is opened, and the list of permissions
     * passed in is not a subset of the permissions granted during the
     * authorization, it will log an error.
     * <p/>
     * Since the session can be automatically opened when the LoginButton is
     * constructed, it's important to always pass in a consistent set of
     * permissions to this method, or manage the setting of permissions outside
     * of the LoginButton class altogether (by managing the session explicitly).
     * 
     * @param permissions
     *            the read permissions to use
     * 
     * @throws UnsupportedOperationException
     *             if setPublishPermissions has been called
     */
    public void setReadPermissions(String... permissions) {
        properties.setReadPermissions(Arrays.asList(permissions), sessionTracker.getSession());
    }

    /**
     * Set the permissions to use when the session is opened. The permissions
     * here should only be publish permissions. If any read permissions are
     * included, the login attempt by the user may fail. The LoginButton can
     * only be associated with either read permissions or publish permissions,
     * but not both. Calling both setReadPermissions and setPublishPermissions
     * on the same instance of LoginButton will result in an exception being
     * thrown unless clearPermissions is called in between.
     * <p/>
     * This method is only meaningful if called before the session is open. If
     * this is called after the session is opened, and the list of permissions
     * passed in is not a subset of the permissions granted during the
     * authorization, it will log an error.
     * <p/>
     * Since the session can be automatically opened when the LoginButton is
     * constructed, it's important to always pass in a consistent set of
     * permissions to this method, or manage the setting of permissions outside
     * of the LoginButton class altogether (by managing the session explicitly).
     * 
     * @param permissions
     *            the read permissions to use
     * 
     * @throws UnsupportedOperationException
     *             if setReadPermissions has been called
     * @throws IllegalArgumentException
     *             if permissions is null or empty
     */
    public void setPublishPermissions(List<String> permissions) {
        properties.setPublishPermissions(permissions, sessionTracker.getSession());
    }

    /**
     * Set the permissions to use when the session is opened. The permissions
     * here should only be publish permissions. If any read permissions are
     * included, the login attempt by the user may fail. The LoginButton can
     * only be associated with either read permissions or publish permissions,
     * but not both. Calling both setReadPermissions and setPublishPermissions
     * on the same instance of LoginButton will result in an exception being
     * thrown unless clearPermissions is called in between.
     * <p/>
     * This method is only meaningful if called before the session is open. If
     * this is called after the session is opened, and the list of permissions
     * passed in is not a subset of the permissions granted during the
     * authorization, it will log an error.
     * <p/>
     * Since the session can be automatically opened when the LoginButton is
     * constructed, it's important to always pass in a consistent set of
     * permissions to this method, or manage the setting of permissions outside
     * of the LoginButton class altogether (by managing the session explicitly).
     * 
     * @param permissions
     *            the read permissions to use
     * 
     * @throws UnsupportedOperationException
     *             if setReadPermissions has been called
     * @throws IllegalArgumentException
     *             if permissions is null or empty
     */
    public void setPublishPermissions(String... permissions) {
        properties.setPublishPermissions(Arrays.asList(permissions), sessionTracker.getSession());
    }

    /**
     * Clears the permissions currently associated with this LoginButton.
     */
    public void clearPermissions() {
        properties.clearPermissions();
    }

    /**
     * Sets the login behavior for the session that will be opened. If null is
     * specified, the default ({@link SessionLoginBehavior
     * SessionLoginBehavior.SSO_WITH_FALLBACK} will be used.
     * 
     * @param loginBehavior
     *            The {@link SessionLoginBehavior SessionLoginBehavior} that
     *            specifies what behaviors should be attempted during
     *            authorization.
     */
    public void setLoginBehavior(SessionLoginBehavior loginBehavior) {
        properties.setLoginBehavior(loginBehavior);
    }

    /**
     * Gets the login behavior for the session that will be opened. If null is
     * returned, the default ({@link SessionLoginBehavior
     * SessionLoginBehavior.SSO_WITH_FALLBACK} will be used.
     * 
     * @return loginBehavior The {@link SessionLoginBehavior
     *         SessionLoginBehavior} that specifies what behaviors should be
     *         attempted during authorization.
     */
    public SessionLoginBehavior getLoginBehavior() {
        return properties.getLoginBehavior();
    }

    /**
     * Set the application ID to be used to open the session.
     * 
     * @param applicationId
     *            the application ID to use
     */
    public void setApplicationId(String applicationId) {
        this.applicationId = applicationId;
    }

    /**
     * Gets the callback interface that will be called when the current user
     * changes.
     * 
     * @return the callback interface
     */
    public UserInfoChangedCallback getUserInfoChangedCallback() {
        return userInfoChangedCallback;
    }

    /**
     * Sets the callback interface that will be called when the current user
     * changes.
     * 
     * @param userInfoChangedCallback
     *            the callback interface
     */
    public void setUserInfoChangedCallback(UserInfoChangedCallback userInfoChangedCallback) {
        this.userInfoChangedCallback = userInfoChangedCallback;
    }

    /**
     * Sets the callback interface that will be called whenever the status of
     * the Session associated with this LoginButton changes. Note that updates
     * will only be sent to the callback while the LoginButton is actually
     * attached to a window.
     * 
     * @param callback
     *            the callback interface
     */
    public void setSessionStatusCallback(Session.StatusCallback callback) {
        properties.setSessionStatusCallback(callback);
    }

    /**
     * Sets the callback interface that will be called whenever the status of
     * the Session associated with this LoginButton changes.
     * 
     * @return the callback interface
     */
    public Session.StatusCallback getSessionStatusCallback() {
        return properties.getSessionStatusCallback();
    }

    /**
     * Provides an implementation for {@link Activity#onActivityResult
     * onActivityResult} that updates the Session based on information returned
     * during the authorization flow. The Activity containing this view should
     * forward the resulting onActivityResult call here to update the Session
     * state based on the contents of the resultCode and data.
     * 
     * @param requestCode
     *            The requestCode parameter from the forwarded call. When this
     *            onActivityResult occurs as part of Facebook authorization
     *            flow, this value is the activityCode passed to open or
     *            authorize.
     * @param resultCode
     *            An int containing the resultCode parameter from the forwarded
     *            call.
     * @param data
     *            The Intent passed as the data parameter from the forwarded
     *            call.
     * @return A boolean indicating whether the requestCode matched a pending
     *         authorization request for this Session.
     * @see Session#onActivityResult(Activity, int, int, Intent)
     */
    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
        Session session = sessionTracker.getSession();
        if (session != null) {
            return session.onActivityResult((Activity) getContext(), requestCode, resultCode, data);
        } else {
            return false;
        }
    }

    /**
     * Set the Session object to use instead of the active Session. Since a
     * Session cannot be reused, if the user logs out from this Session, and
     * tries to log in again, a new Active Session will be used instead.
     * <p/>
     * If the passed in session is currently opened, this method will also
     * attempt to load some user information for display (if needed).
     * 
     * @param newSession
     *            the Session object to use
     * @throws FacebookException
     *             if errors occur during the loading of user information
     */
    public void setSession(Session newSession) {
        sessionTracker.setSession(newSession);
        fetchUserInfo();
        setButtonText();
    }

    @Override
    public void onFinishInflate() {
        super.onFinishInflate();
        finishInit();
    }

    private void finishInit() {
        setOnClickListener(new LoginClickListener());
        setButtonText();
        if (!isInEditMode()) {
            sessionTracker = new SessionTracker(getContext(), new LoginButtonCallback(), null, false);
            fetchUserInfo();
        }
    }

    /**
     * Sets the fragment that contains this control. This allows the LoginButton
     * to be embedded inside a Fragment, and will allow the fragment to receive
     * the {@link Fragment#onActivityResult(int, int, android.content.Intent)
     * onActivityResult} call rather than the Activity.
     * 
     * @param fragment
     *            the fragment that contains this control
     */
    public void setFragment(Fragment fragment) {
        parentFragment = fragment;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (sessionTracker != null && !sessionTracker.isTracking()) {
            sessionTracker.startTracking();
            fetchUserInfo();
            setButtonText();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (sessionTracker != null) {
            sessionTracker.stopTracking();
        }
    }

    // For testing purposes only
    List<String> getPermissions() {
        return properties.getPermissions();
    }

    void setProperties(LoginButtonProperties properties) {
        this.properties = properties;
    }

    void setLoginLogoutEventName(String eventName) {
        loginLogoutEventName = eventName;
    }

    private void parseAttributes(AttributeSet attrs) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.com_facebook_login_view);
        confirmLogout = a.getBoolean(R.styleable.com_facebook_login_view_confirm_logout, true);
        fetchUserInfo = a.getBoolean(R.styleable.com_facebook_login_view_fetch_user_info, true);
        a.recycle();
    }

    private void setButtonText() {
    }

    private boolean initializeActiveSessionWithCachedToken(Context context) {
        if (context == null) {
            return false;
        }

        Session session = Session.getActiveSession();
        if (session != null) {
            return session.isOpened();
        }

        String applicationId = Utility.getMetadataApplicationId(context);
        if (applicationId == null) {
            return false;
        }

        return Session.openActiveSessionFromCache(context) != null;
    }

    private void fetchUserInfo() {
        if (fetchUserInfo) {
            final Session currentSession = sessionTracker.getOpenSession();
            if (currentSession != null) {
                if (currentSession != userInfoSession) {
                    Request request = Request.newMeRequest(currentSession, new Request.GraphUserCallback() {
                        @Override
                        public void onCompleted(GraphUser me, Response response) {
                            if (currentSession == sessionTracker.getOpenSession()) {
                                user = me;
                                if (userInfoChangedCallback != null) {
                                    userInfoChangedCallback.onUserInfoFetched(user);
                                }
                            }
                            if (response.getError() != null) {
                                handleError(response.getError().getException());
                            }
                        }
                    });
                    Request.executeBatchAsync(request);
                    userInfoSession = currentSession;
                }
            } else {
                user = null;
                if (userInfoChangedCallback != null) {
                    userInfoChangedCallback.onUserInfoFetched(user);
                }
            }
        }
    }

    private class LoginClickListener implements OnClickListener {

        @Override
        public void onClick(View v) {
            Context context = getContext();
            final Session openSession = sessionTracker.getOpenSession();

            if (openSession != null) {
                // If the Session is currently open, it must mean we need to log
                // out
                if (confirmLogout) {
                    // Create a confirmation dialog
                    String logout = getResources().getString(R.string.com_facebook_loginview_log_out_action);
                    String cancel = getResources().getString(R.string.com_facebook_loginview_cancel_action);
                    String message;
                    if (user != null && user.getName() != null) {
                        message = String.format(
                                getResources().getString(R.string.com_facebook_loginview_logged_in_as),
                                user.getName());
                    } else {
                        message = getResources()
                                .getString(R.string.com_facebook_loginview_logged_in_using_facebook);
                    }
                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
                    builder.setMessage(message).setCancelable(true)
                            .setPositiveButton(logout, new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    openSession.closeAndClearTokenInformation();
                                }
                            }).setNegativeButton(cancel, null);
                    builder.create().show();
                } else {
                    openSession.closeAndClearTokenInformation();
                }
            } else {
                Session currentSession = sessionTracker.getSession();
                if (currentSession == null || currentSession.getState().isClosed()) {
                    sessionTracker.setSession(null);
                    Session session = new Session.Builder(context).setApplicationId(applicationId).build();
                    Session.setActiveSession(session);
                    currentSession = session;
                }
                if (!currentSession.isOpened()) {
                    Session.OpenRequest openRequest = null;
                    if (parentFragment != null) {
                        openRequest = new Session.OpenRequest(parentFragment);
                    } else if (context instanceof Activity) {
                        openRequest = new Session.OpenRequest((Activity) context);
                    }

                    if (openRequest != null) {
                        openRequest.setDefaultAudience(properties.defaultAudience);
                        openRequest.setPermissions(properties.permissions);
                        openRequest.setLoginBehavior(properties.loginBehavior);

                        if (SessionAuthorizationType.PUBLISH.equals(properties.authorizationType)) {
                            currentSession.openForPublish(openRequest);
                        } else {
                            currentSession.openForRead(openRequest);
                        }
                    }
                }
            }

            AppEventsLogger logger = AppEventsLogger.newLogger(getContext());
            Bundle parameters = new Bundle();
            parameters.putInt("logging_in", (openSession != null) ? 0 : 1);
            logger.logSdkEvent(loginLogoutEventName, null, parameters);
        }
    }

    public void onClickLoginFb() {
        Log.e(TAG, "onClickLoginFb");
        Context context = getContext();
        final Session openSession = sessionTracker.getOpenSession();
        Session currentSession = sessionTracker.getSession();
        if (currentSession == null || currentSession.getState().isClosed()) {
            sessionTracker.setSession(null);
            Session session = new Session.Builder(context).setApplicationId(applicationId).build();
            Session.setActiveSession(session);
            currentSession = session;
        }
        if (!currentSession.isOpened()) {
            Session.OpenRequest openRequest = null;
            if (parentFragment != null) {
                openRequest = new Session.OpenRequest(parentFragment);
            } else if (context instanceof Activity) {
                openRequest = new Session.OpenRequest((Activity) context);
            }

            if (openRequest != null) {
                openRequest.setDefaultAudience(properties.defaultAudience);
                openRequest.setPermissions(properties.permissions);
                openRequest.setLoginBehavior(properties.loginBehavior);

                if (SessionAuthorizationType.PUBLISH.equals(properties.authorizationType)) {
                    currentSession.openForPublish(openRequest);
                } else {
                    currentSession.openForRead(openRequest);
                }
            }
        }

        AppEventsLogger logger = AppEventsLogger.newLogger(getContext());
        Bundle parameters = new Bundle();
        parameters.putInt("logging_in", (openSession != null) ? 0 : 1);
        logger.logSdkEvent(loginLogoutEventName, null, parameters);
    }

    private class LoginButtonCallback implements Session.StatusCallback {
        @Override
        public void call(Session session, SessionState state, Exception exception) {
            fetchUserInfo();
            setButtonText();

            // if the client has a status callback registered, call it,
            // otherwise
            // call the default handleError method, but don't call both
            if (properties.sessionStatusCallback != null) {
                properties.sessionStatusCallback.call(session, state, exception);
            } else if (exception != null) {
                handleError(exception);
            }
        }
    };

    void handleError(Exception exception) {
        if (properties.onErrorListener != null) {
            if (exception instanceof FacebookException) {
                properties.onErrorListener.onError((FacebookException) exception);
            } else {
                properties.onErrorListener.onError(new FacebookException(exception));
            }
        }
    }
}