Java tutorial
/** * 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)); } } } }