Blame | Last modification | View Log | RSS feed
/*** 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;import android.annotation.SuppressLint;import android.app.Activity;import android.content.*;import android.content.pm.ResolveInfo;import android.os.*;import android.support.v4.app.Fragment;import android.support.v4.content.LocalBroadcastManager;import android.text.TextUtils;import android.util.Log;import com.facebook.internal.NativeProtocol;import com.facebook.internal.SessionAuthorizationType;import com.facebook.internal.Utility;import com.facebook.internal.Validate;import com.facebook.model.GraphMultiResult;import com.facebook.model.GraphObject;import com.facebook.model.GraphObjectList;import org.json.JSONException;import org.json.JSONObject;import java.io.*;import java.lang.ref.WeakReference;import java.util.*;/*** <p>* Session is used to authenticate a user and manage the user's session with* Facebook.* </p>* <p>* Sessions must be opened before they can be used to make a Request. When a* Session is created, it attempts to initialize itself from a TokenCachingStrategy.* Closing the session can optionally clear this cache. The Session lifecycle* uses {@link SessionState SessionState} to indicate its state. Once a Session has* been closed, it can't be re-opened; a new Session must be created.* </p>* <p>* Instances of Session provide state change notification via a callback* interface, {@link Session.StatusCallback StatusCallback}.* </p>*/public class Session implements Serializable {private static final long serialVersionUID = 1L;/*** The logging tag used by Session.*/public static final String TAG = Session.class.getCanonicalName();/*** The default activity code used for authorization.** @see #openForRead(OpenRequest)* open*/public static final int DEFAULT_AUTHORIZE_ACTIVITY_CODE = 0xface;/*** If Session authorization fails and provides a web view error code, the* web view error code is stored in the Bundle returned from* {@link #getAuthorizationBundle getAuthorizationBundle} under this key.*/public static final String WEB_VIEW_ERROR_CODE_KEY = "com.facebook.sdk.WebViewErrorCode";/*** If Session authorization fails and provides a failing url, the failing* url is stored in the Bundle returned from {@link #getAuthorizationBundle* getAuthorizationBundle} under this key.*/public static final String WEB_VIEW_FAILING_URL_KEY = "com.facebook.sdk.FailingUrl";/*** The action used to indicate that the active session has been set. This should* be used as an action in an IntentFilter and BroadcastReceiver registered with* the {@link android.support.v4.content.LocalBroadcastManager}.*/public static final String ACTION_ACTIVE_SESSION_SET = "com.facebook.sdk.ACTIVE_SESSION_SET";/*** The action used to indicate that the active session has been set to null. This should* be used as an action in an IntentFilter and BroadcastReceiver registered with* the {@link android.support.v4.content.LocalBroadcastManager}.*/public static final String ACTION_ACTIVE_SESSION_UNSET = "com.facebook.sdk.ACTIVE_SESSION_UNSET";/*** The action used to indicate that the active session has been opened. This should* be used as an action in an IntentFilter and BroadcastReceiver registered with* the {@link android.support.v4.content.LocalBroadcastManager}.*/public static final String ACTION_ACTIVE_SESSION_OPENED = "com.facebook.sdk.ACTIVE_SESSION_OPENED";/*** The action used to indicate that the active session has been closed. This should* be used as an action in an IntentFilter and BroadcastReceiver registered with* the {@link android.support.v4.content.LocalBroadcastManager}.*/public static final String ACTION_ACTIVE_SESSION_CLOSED = "com.facebook.sdk.ACTIVE_SESSION_CLOSED";private static final Object STATIC_LOCK = new Object();private static Session activeSession;private static volatile Context staticContext;// Token extension constantsprivate static final int TOKEN_EXTEND_THRESHOLD_SECONDS = 24 * 60 * 60; // 1// dayprivate static final int TOKEN_EXTEND_RETRY_SECONDS = 60 * 60; // 1 hourprivate static final String SESSION_BUNDLE_SAVE_KEY = "com.facebook.sdk.Session.saveSessionKey";private static final String AUTH_BUNDLE_SAVE_KEY = "com.facebook.sdk.Session.authBundleKey";private static final String PUBLISH_PERMISSION_PREFIX = "publish";private static final String MANAGE_PERMISSION_PREFIX = "manage";@SuppressWarnings("serial")private static final Set<String> OTHER_PUBLISH_PERMISSIONS = new HashSet<String>() {{add("ads_management");add("create_event");add("rsvp_event");}};private String applicationId;private SessionState state;private AccessToken tokenInfo;private Date lastAttemptedTokenExtendDate = new Date(0);private AuthorizationRequest pendingAuthorizationRequest;private AuthorizationClient authorizationClient;// The following are not serialized with the Session objectprivate volatile Bundle authorizationBundle;private final List<StatusCallback> callbacks;private Handler handler;private AutoPublishAsyncTask autoPublishAsyncTask;// This is the object that synchronizes access to state and tokenInfoprivate final Object lock = new Object();private TokenCachingStrategy tokenCachingStrategy;private volatile TokenRefreshRequest currentTokenRefreshRequest;private AppEventsLogger appEventsLogger;/*** Serialization proxy for the Session class. This is version 1 of* serialization. Future serializations may differ in format. This* class should not be modified. If serializations formats change,* create a new class SerializationProxyVx.*/private static class SerializationProxyV1 implements Serializable {private static final long serialVersionUID = 7663436173185080063L;private final String applicationId;private final SessionState state;private final AccessToken tokenInfo;private final Date lastAttemptedTokenExtendDate;private final boolean shouldAutoPublish;private final AuthorizationRequest pendingAuthorizationRequest;SerializationProxyV1(String applicationId, SessionState state,AccessToken tokenInfo, Date lastAttemptedTokenExtendDate,boolean shouldAutoPublish, AuthorizationRequest pendingAuthorizationRequest) {this.applicationId = applicationId;this.state = state;this.tokenInfo = tokenInfo;this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;this.shouldAutoPublish = shouldAutoPublish;this.pendingAuthorizationRequest = pendingAuthorizationRequest;}private Object readResolve() {return new Session(applicationId, state, tokenInfo,lastAttemptedTokenExtendDate, shouldAutoPublish, pendingAuthorizationRequest);}}/*** Serialization proxy for the Session class. This is version 2 of* serialization. Future serializations may differ in format. This* class should not be modified. If serializations formats change,* create a new class SerializationProxyVx.*/@SuppressWarnings("UnusedDeclaration")private static class SerializationProxyV2 implements Serializable {private static final long serialVersionUID = 7663436173185080064L;private final String applicationId;private final SessionState state;private final AccessToken tokenInfo;private final Date lastAttemptedTokenExtendDate;private final boolean shouldAutoPublish;private final AuthorizationRequest pendingAuthorizationRequest;private final Set<String> requestedPermissions;SerializationProxyV2(String applicationId, SessionState state,AccessToken tokenInfo, Date lastAttemptedTokenExtendDate,boolean shouldAutoPublish, AuthorizationRequest pendingAuthorizationRequest,Set<String> requestedPermissions) {this.applicationId = applicationId;this.state = state;this.tokenInfo = tokenInfo;this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;this.shouldAutoPublish = shouldAutoPublish;this.pendingAuthorizationRequest = pendingAuthorizationRequest;this.requestedPermissions = requestedPermissions;}private Object readResolve() {return new Session(applicationId, state, tokenInfo,lastAttemptedTokenExtendDate, shouldAutoPublish, pendingAuthorizationRequest, requestedPermissions);}}/*** Used by version 1 of the serialization proxy, do not modify.*/private Session(String applicationId, SessionState state,AccessToken tokenInfo, Date lastAttemptedTokenExtendDate,boolean shouldAutoPublish, AuthorizationRequest pendingAuthorizationRequest) {this.applicationId = applicationId;this.state = state;this.tokenInfo = tokenInfo;this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;this.pendingAuthorizationRequest = pendingAuthorizationRequest;handler = new Handler(Looper.getMainLooper());currentTokenRefreshRequest = null;tokenCachingStrategy = null;callbacks = new ArrayList<StatusCallback>();}/*** Used by version 2 of the serialization proxy, do not modify.*/private Session(String applicationId, SessionState state,AccessToken tokenInfo, Date lastAttemptedTokenExtendDate,boolean shouldAutoPublish, AuthorizationRequest pendingAuthorizationRequest,Set<String> requestedPermissions) {this.applicationId = applicationId;this.state = state;this.tokenInfo = tokenInfo;this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;this.pendingAuthorizationRequest = pendingAuthorizationRequest;handler = new Handler(Looper.getMainLooper());currentTokenRefreshRequest = null;tokenCachingStrategy = null;callbacks = new ArrayList<StatusCallback>();}/*** Initializes a new Session with the specified context.** @param currentContext The Activity or Service creating this Session.*/public Session(Context currentContext) {this(currentContext, null, null, true);}Session(Context context, String applicationId, TokenCachingStrategy tokenCachingStrategy) {this(context, applicationId, tokenCachingStrategy, true);}Session(Context context, String applicationId, TokenCachingStrategy tokenCachingStrategy,boolean loadTokenFromCache) {// if the application ID passed in is null, try to get it from the// meta-data in the manifest.if ((context != null) && (applicationId == null)) {applicationId = Utility.getMetadataApplicationId(context);}Validate.notNull(applicationId, "applicationId");initializeStaticContext(context);if (tokenCachingStrategy == null) {tokenCachingStrategy = new SharedPreferencesTokenCachingStrategy(staticContext);}this.applicationId = applicationId;this.tokenCachingStrategy = tokenCachingStrategy;this.state = SessionState.CREATED;this.pendingAuthorizationRequest = null;this.callbacks = new ArrayList<StatusCallback>();this.handler = new Handler(Looper.getMainLooper());Bundle tokenState = loadTokenFromCache ? tokenCachingStrategy.load() : null;if (TokenCachingStrategy.hasTokenInformation(tokenState)) {Date cachedExpirationDate = TokenCachingStrategy.getDate(tokenState, TokenCachingStrategy.EXPIRATION_DATE_KEY);Date now = new Date();if ((cachedExpirationDate == null) || cachedExpirationDate.before(now)) {// If expired or we require new permissions, clear out the// current token cache.tokenCachingStrategy.clear();this.tokenInfo = AccessToken.createEmptyToken();} else {// Otherwise we have a valid token, so use it.this.tokenInfo = AccessToken.createFromCache(tokenState);this.state = SessionState.CREATED_TOKEN_LOADED;}} else {this.tokenInfo = AccessToken.createEmptyToken();}}/*** Returns a Bundle containing data that was returned from Facebook during* authorization.** @return a Bundle containing data that was returned from Facebook during* authorization.*/public final Bundle getAuthorizationBundle() {synchronized (this.lock) {return this.authorizationBundle;}}/*** Returns a boolean indicating whether the session is opened.** @return a boolean indicating whether the session is opened.*/public final boolean isOpened() {synchronized (this.lock) {return this.state.isOpened();}}public final boolean isClosed() {synchronized (this.lock) {return this.state.isClosed();}}/*** Returns the current state of the Session.* See {@link SessionState} for details.** @return the current state of the Session.*/public final SessionState getState() {synchronized (this.lock) {return this.state;}}/*** Returns the application id associated with this Session.** @return the application id associated with this Session.*/public final String getApplicationId() {return this.applicationId;}/*** Returns the access token String.** @return the access token String, or null if there is no access token*/public final String getAccessToken() {synchronized (this.lock) {return (this.tokenInfo == null) ? null : this.tokenInfo.getToken();}}/*** <p>* Returns the Date at which the current token will expire.* </p>* <p>* Note that Session automatically attempts to extend the lifetime of Tokens* as needed when Facebook requests are made.* </p>** @return the Date at which the current token will expire, or null if there is no access token*/public final Date getExpirationDate() {synchronized (this.lock) {return (this.tokenInfo == null) ? null : this.tokenInfo.getExpires();}}/*** <p>* Returns the list of permissions associated with the session.* </p>* <p>* If there is a valid token, this represents the permissions granted by* that token. This can change during calls to* {@link #requestNewReadPermissions}* or {@link #requestNewPublishPermissions}.* </p>** @return the list of permissions associated with the session, or null if there is no access token*/public final List<String> getPermissions() {synchronized (this.lock) {return (this.tokenInfo == null) ? null : this.tokenInfo.getPermissions();}}/*** <p>* Returns whether a particular permission has been granted* </p>** @param permission The permission to check for* @return true if the permission is granted, false otherwise*/public boolean isPermissionGranted(String permission) {List<String> grantedPermissions = getPermissions();if (grantedPermissions != null) {return grantedPermissions.contains(permission);}return false;}/*** <p>* Returns the list of permissions that have been requested in this session but not granted* </p>** @return the list of requested permissions that have been declined*/public final List<String> getDeclinedPermissions() {synchronized (this.lock) {return (this.tokenInfo == null) ? null : this.tokenInfo.getDeclinedPermissions();}}/*** <p>* Logs a user in to Facebook.* </p>* <p>* A session may not be used with {@link Request Request} and other classes* in the SDK until it is open. If, prior to calling open, the session is in* the {@link SessionState#CREATED_TOKEN_LOADED CREATED_TOKEN_LOADED}* state, and the requested permissions are a subset of the previously authorized* permissions, then the Session becomes usable immediately with no user interaction.* </p>* <p>* The permissions associated with the openRequest passed to this method must* be read permissions only (or null/empty). It is not allowed to pass publish* permissions to this method and will result in an exception being thrown.* </p>* <p>* Any open method must be called at most once, and cannot be called after the* Session is closed. Calling the method at an invalid time will result in* UnsuportedOperationException.* </p>** @param openRequest the open request, can be null only if the Session is in the* {@link SessionState#CREATED_TOKEN_LOADED CREATED_TOKEN_LOADED} state* @throws FacebookException if any publish or manage permissions are requested*/public final void openForRead(OpenRequest openRequest) {open(openRequest, SessionAuthorizationType.READ);}/*** <p>* Logs a user in to Facebook.* </p>* <p>* A session may not be used with {@link Request Request} and other classes* in the SDK until it is open. If, prior to calling open, the session is in* the {@link SessionState#CREATED_TOKEN_LOADED CREATED_TOKEN_LOADED}* state, and the requested permissions are a subset of the previously authorized* permissions, then the Session becomes usable immediately with no user interaction.* </p>* <p>* The permissions associated with the openRequest passed to this method must* be publish or manage permissions only and must be non-empty. Any read permissions* will result in a warning, and may fail during server-side authorization. Also, an application* must have at least basic read permissions prior to requesting publish permissions, so* this method should only be used if the application knows that the user has already granted* read permissions to the application; otherwise, openForRead should be used, followed by a* call to requestNewPublishPermissions. For more information on this flow, see* https://developers.facebook.com/docs/facebook-login/permissions/.* </p>* <p>* Any open method must be called at most once, and cannot be called after the* Session is closed. Calling the method at an invalid time will result in* UnsuportedOperationException.* </p>** @param openRequest the open request, can be null only if the Session is in the* {@link SessionState#CREATED_TOKEN_LOADED CREATED_TOKEN_LOADED} state* @throws FacebookException if the passed in request is null or has no permissions set.*/public final void openForPublish(OpenRequest openRequest) {open(openRequest, SessionAuthorizationType.PUBLISH);}/*** Opens a session based on an existing Facebook access token. This method should be used* only in instances where an application has previously obtained an access token and wishes* to import it into the Session/TokenCachingStrategy-based session-management system. An* example would be an application which previously did not use the Facebook SDK for Android* and implemented its own session-management scheme, but wishes to implement an upgrade path* for existing users so they do not need to log in again when upgrading to a version of* the app that uses the SDK.* <p/>* No validation is done that the token, token source, or permissions are actually valid.* It is the caller's responsibility to ensure that these accurately reflect the state of* the token that has been passed in, or calls to the Facebook API may fail.** @param accessToken the access token obtained from Facebook* @param callback a callback that will be called when the session status changes; may be null*/public final void open(AccessToken accessToken, StatusCallback callback) {synchronized (this.lock) {if (pendingAuthorizationRequest != null) {throw new UnsupportedOperationException("Session: an attempt was made to open a session that has a pending request.");}if (state.isClosed()) {throw new UnsupportedOperationException("Session: an attempt was made to open a previously-closed session.");} else if (state != SessionState.CREATED && state != SessionState.CREATED_TOKEN_LOADED) {throw new UnsupportedOperationException("Session: an attempt was made to open an already opened session.");}if (callback != null) {addCallback(callback);}this.tokenInfo = accessToken;if (this.tokenCachingStrategy != null) {this.tokenCachingStrategy.save(accessToken.toCacheBundle());}final SessionState oldState = state;state = SessionState.OPENED;this.postStateChange(oldState, state, null);}autoPublishAsync();}/*** <p>* Issues a request to add new read permissions to the Session.* </p>* <p>* If successful, this will update the set of permissions on this session to* match the newPermissions. If this fails, the Session remains unchanged.* </p>* <p>* The permissions associated with the newPermissionsRequest passed to this method must* be read permissions only (or null/empty). It is not allowed to pass publish* permissions to this method and will result in an exception being thrown.* </p>** @param newPermissionsRequest the new permissions request*/public final void requestNewReadPermissions(NewPermissionsRequest newPermissionsRequest) {requestNewPermissions(newPermissionsRequest, SessionAuthorizationType.READ);}/*** <p>* Issues a request to add new publish or manage permissions to the Session.* </p>* <p>* If successful, this will update the set of permissions on this session to* match the newPermissions. If this fails, the Session remains unchanged.* </p>* <p>* The permissions associated with the newPermissionsRequest passed to this method must* be publish or manage permissions only and must be non-empty. Any read permissions* will result in a warning, and may fail during server-side authorization.* </p>** @param newPermissionsRequest the new permissions request*/public final void requestNewPublishPermissions(NewPermissionsRequest newPermissionsRequest) {requestNewPermissions(newPermissionsRequest, SessionAuthorizationType.PUBLISH);}/*** <p>* Issues a request to refresh the permissions on the session.* </p>* <p>* If successful, this will update the permissions and call the app back with* {@link SessionState#OPENED_TOKEN_UPDATED}. The session can then be queried to see the granted and declined* permissions. If this fails because the user has removed the app, the session will close.* </p>*/public final void refreshPermissions() {Request request = new Request(this, "me/permissions");request.setCallback(new Request.Callback() {@Overridepublic void onCompleted(Response response) {PermissionsPair permissionsPair = handlePermissionResponse(response);if (permissionsPair != null) {// Update our token with the refreshed permissionssynchronized (lock) {tokenInfo = AccessToken.createFromTokenWithRefreshedPermissions(tokenInfo,permissionsPair.getGrantedPermissions(), permissionsPair.getDeclinedPermissions());postStateChange(state, SessionState.OPENED_TOKEN_UPDATED, null);}}}});request.executeAsync();}/*** Internal helper class that is used to hold two different permission lists (granted and declined)*/static class PermissionsPair {List<String> grantedPermissions;List<String> declinedPermissions;public PermissionsPair(List<String> grantedPermissions, List<String> declinedPermissions) {this.grantedPermissions = grantedPermissions;this.declinedPermissions = declinedPermissions;}public List<String> getGrantedPermissions() {return grantedPermissions;}public List<String> getDeclinedPermissions() {return declinedPermissions;}}/*** This parses a server response to a call to me/permissions. It will return the list of granted permissions.* It will optionally update a session with the requested permissions. It also handles the distinction between* 1.0 and 2.0 calls to the endpoint.** @param response The server response* @return A list of granted permissions or null if an error*/static PermissionsPair handlePermissionResponse(Response response) {if (response.getError() != null) {return null;}GraphMultiResult result = response.getGraphObjectAs(GraphMultiResult.class);if (result == null) {return null;}GraphObjectList<GraphObject> data = result.getData();if (data == null || data.size() == 0) {return null;}List<String> grantedPermissions = new ArrayList<String>(data.size());List<String> declinedPermissions = new ArrayList<String>(data.size());// Check if we are dealing with v2.0 or v1.0 behavior until the server is updatedGraphObject firstObject = data.get(0);if (firstObject.getProperty("permission") != null) { // v2.0for (GraphObject graphObject : data) {String permission = (String) graphObject.getProperty("permission");if (permission.equals("installed")) {continue;}String status = (String) graphObject.getProperty("status");if(status.equals("granted")) {grantedPermissions.add(permission);} else if (status.equals("declined")) {declinedPermissions.add(permission);}}} else { // v1.0Map<String, Object> permissionsMap = firstObject.asMap();for (Map.Entry<String, Object> entry : permissionsMap.entrySet()) {if (entry.getKey().equals("installed")) {continue;}if ((Integer)entry.getValue() == 1) {grantedPermissions.add(entry.getKey());}}}return new PermissionsPair(grantedPermissions, declinedPermissions);}/*** Provides an implementation for {@link Activity#onActivityResult* onActivityResult} that updates the Session based on information returned* during the authorization flow. The Activity that calls open or* requestNewPermissions should forward the resulting onActivityResult call here to* update the Session state based on the contents of the resultCode and* data.** @param currentActivity The Activity that is forwarding the onActivityResult call.* @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.*/public final boolean onActivityResult(Activity currentActivity, int requestCode, int resultCode, Intent data) {Validate.notNull(currentActivity, "currentActivity");initializeStaticContext(currentActivity);synchronized (lock) {if (pendingAuthorizationRequest == null || (requestCode != pendingAuthorizationRequest.getRequestCode())) {return false;}}Exception exception = null;AuthorizationClient.Result.Code code = AuthorizationClient.Result.Code.ERROR;if (data != null) {AuthorizationClient.Result result = (AuthorizationClient.Result) data.getSerializableExtra(LoginActivity.RESULT_KEY);if (result != null) {// This came from LoginActivity.handleAuthorizationResult(resultCode, result);return true;} else if (authorizationClient != null) {// Delegate to the auth client.authorizationClient.onActivityResult(requestCode, resultCode, data);return true;}} else if (resultCode == Activity.RESULT_CANCELED) {exception = new FacebookOperationCanceledException("User canceled operation.");code = AuthorizationClient.Result.Code.CANCEL;}if (exception == null) {exception = new FacebookException("Unexpected call to Session.onActivityResult");}logAuthorizationComplete(code, null, exception);finishAuthOrReauth(null, exception);return true;}/*** Closes the local in-memory Session object, but does not clear the* persisted token cache.*/public final void close() {synchronized (this.lock) {final SessionState oldState = this.state;switch (this.state) {case CREATED:case OPENING:this.state = SessionState.CLOSED_LOGIN_FAILED;postStateChange(oldState, this.state, new FacebookException("Log in attempt aborted."));break;case CREATED_TOKEN_LOADED:case OPENED:case OPENED_TOKEN_UPDATED:this.state = SessionState.CLOSED;postStateChange(oldState, this.state, null);break;case CLOSED:case CLOSED_LOGIN_FAILED:break;}}}/*** Closes the local in-memory Session object and clears any persisted token* cache related to the Session.*/public final void closeAndClearTokenInformation() {if (this.tokenCachingStrategy != null) {this.tokenCachingStrategy.clear();}Utility.clearFacebookCookies(staticContext);Utility.clearCaches(staticContext);close();}/*** Adds a callback that will be called when the state of this Session changes.** @param callback the callback*/public final void addCallback(StatusCallback callback) {synchronized (callbacks) {if (callback != null && !callbacks.contains(callback)) {callbacks.add(callback);}}}/*** Removes a StatusCallback from this Session.** @param callback the callback*/public final void removeCallback(StatusCallback callback) {synchronized (callbacks) {callbacks.remove(callback);}}@Overridepublic String toString() {return new StringBuilder().append("{Session").append(" state:").append(this.state).append(", token:").append((this.tokenInfo == null) ? "null" : this.tokenInfo).append(", appId:").append((this.applicationId == null) ? "null" : this.applicationId).append("}").toString();}void extendTokenCompleted(Bundle bundle) {synchronized (this.lock) {final SessionState oldState = this.state;switch (this.state) {case OPENED:this.state = SessionState.OPENED_TOKEN_UPDATED;postStateChange(oldState, this.state, null);break;case OPENED_TOKEN_UPDATED:break;default:// Silently ignore attempts to refresh token if we are not openLog.d(TAG, "refreshToken ignored in state " + this.state);return;}this.tokenInfo = AccessToken.createFromRefresh(this.tokenInfo, bundle);if (this.tokenCachingStrategy != null) {this.tokenCachingStrategy.save(this.tokenInfo.toCacheBundle());}}}private Object writeReplace() {return new SerializationProxyV1(applicationId, state, tokenInfo,lastAttemptedTokenExtendDate, false, pendingAuthorizationRequest);}// have a readObject that throws to prevent spoofingprivate void readObject(ObjectInputStream stream) throws InvalidObjectException {throw new InvalidObjectException("Cannot readObject, serialization proxy required");}/*** Save the Session object into the supplied Bundle. This method is intended to be called from an* Activity or Fragment's onSaveInstanceState method in order to preserve Sessions across Activity lifecycle events.** @param session the Session to save* @param bundle the Bundle to save the Session to*/public static final void saveSession(Session session, Bundle bundle) {if (bundle != null && session != null && !bundle.containsKey(SESSION_BUNDLE_SAVE_KEY)) {ByteArrayOutputStream outputStream = new ByteArrayOutputStream();try {new ObjectOutputStream(outputStream).writeObject(session);} catch (IOException e) {throw new FacebookException("Unable to save session.", e);}bundle.putByteArray(SESSION_BUNDLE_SAVE_KEY, outputStream.toByteArray());bundle.putBundle(AUTH_BUNDLE_SAVE_KEY, session.authorizationBundle);}}/*** Restores the saved session from a Bundle, if any. Returns the restored Session or* null if it could not be restored. This method is intended to be called from an Activity or Fragment's* onCreate method when a Session has previously been saved into a Bundle via saveState to preserve a Session* across Activity lifecycle events.** @param context the Activity or Service creating the Session, must not be null* @param cachingStrategy the TokenCachingStrategy to use to load and store the token. If this is* null, a default token cachingStrategy that stores data in* SharedPreferences will be used* @param callback the callback to notify for Session state changes, can be null* @param bundle the bundle to restore the Session from* @return the restored Session, or null*/public static final Session restoreSession(Context context, TokenCachingStrategy cachingStrategy, StatusCallback callback, Bundle bundle) {if (bundle == null) {return null;}byte[] data = bundle.getByteArray(SESSION_BUNDLE_SAVE_KEY);if (data != null) {ByteArrayInputStream is = new ByteArrayInputStream(data);try {Session session = (Session) (new ObjectInputStream(is)).readObject();initializeStaticContext(context);if (cachingStrategy != null) {session.tokenCachingStrategy = cachingStrategy;} else {session.tokenCachingStrategy = new SharedPreferencesTokenCachingStrategy(context);}if (callback != null) {session.addCallback(callback);}session.authorizationBundle = bundle.getBundle(AUTH_BUNDLE_SAVE_KEY);return session;} catch (ClassNotFoundException e) {Log.w(TAG, "Unable to restore session", e);} catch (IOException e) {Log.w(TAG, "Unable to restore session.", e);}}return null;}/*** Returns the current active Session, or null if there is none.** @return the current active Session, or null if there is none.*/public static final Session getActiveSession() {synchronized (Session.STATIC_LOCK) {return Session.activeSession;}}/*** <p>* Sets the current active Session.* </p>* <p>* The active Session is used implicitly by predefined Request factory* methods as well as optionally by UI controls in the sdk.* </p>* <p>* It is legal to set this to null, or to a Session that is not yet open.* </p>** @param session A Session to use as the active Session, or null to indicate* that there is no active Session.*/public static final void setActiveSession(Session session) {synchronized (Session.STATIC_LOCK) {if (session != Session.activeSession) {Session oldSession = Session.activeSession;if (oldSession != null) {oldSession.close();}Session.activeSession = session;if (oldSession != null) {postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_UNSET);}if (session != null) {postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_SET);if (session.isOpened()) {postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_OPENED);}}}}}/*** If a cached token is available, creates and opens the session and makes it active without any user interaction,* otherwise this does nothing.** @param context The Context creating this session* @return The new session or null if one could not be created*/public static Session openActiveSessionFromCache(Context context) {return openActiveSession(context, false, null);}/*** If allowLoginUI is true, this will create a new Session, make it active, and* open it. If the default token cache is not available, then this will request* basic permissions. If the default token cache is available and cached tokens* are loaded, this will use the cached token and associated permissions.* <p/>* If allowedLoginUI is false, this will only create the active session and open* it if it requires no user interaction (i.e. the token cache is available and* there are cached tokens).** @param activity The Activity that is opening the new Session.* @param allowLoginUI if false, only sets the active session and opens it if it* does not require user interaction* @param callback The {@link StatusCallback SessionStatusCallback} to* notify regarding Session state changes. May be null.* @return The new Session or null if one could not be created*/public static Session openActiveSession(Activity activity, boolean allowLoginUI,StatusCallback callback) {return openActiveSession(activity, allowLoginUI, new OpenRequest(activity).setCallback(callback));}/*** If allowLoginUI is true, this will create a new Session, make it active, and* open it. If the default token cache is not available, then this will request* the permissions provided (and basic permissions of no permissions are provided).* If the default token cache is available and cached tokens are loaded, this will* use the cached token and associated permissions.* <p/>* If allowedLoginUI is false, this will only create the active session and open* it if it requires no user interaction (i.e. the token cache is available and* there are cached tokens).** @param activity The Activity that is opening the new Session.* @param allowLoginUI if false, only sets the active session and opens it if it* does not require user interaction* @param permissions The permissions to request for this Session* @param callback The {@link StatusCallback SessionStatusCallback} to* notify regarding Session state changes. May be null.* @return The new Session or null if one could not be created*/public static Session openActiveSession(Activity activity, boolean allowLoginUI,List<String> permissions, StatusCallback callback) {return openActiveSession(activity,allowLoginUI,new OpenRequest(activity).setCallback(callback).setPermissions(permissions));}/*** If allowLoginUI is true, this will create a new Session, make it active, and* open it. If the default token cache is not available, then this will request* basic permissions. If the default token cache is available and cached tokens* are loaded, this will use the cached token and associated permissions.* <p/>* If allowedLoginUI is false, this will only create the active session and open* it if it requires no user interaction (i.e. the token cache is available and* there are cached tokens).** @param context The Activity or Service creating this Session* @param fragment The Fragment that is opening the new Session.* @param allowLoginUI if false, only sets the active session and opens it if it* does not require user interaction* @param callback The {@link StatusCallback SessionStatusCallback} to* notify regarding Session state changes.* @return The new Session or null if one could not be created*/public static Session openActiveSession(Context context, Fragment fragment,boolean allowLoginUI, StatusCallback callback) {return openActiveSession(context, allowLoginUI, new OpenRequest(fragment).setCallback(callback));}/*** If allowLoginUI is true, this will create a new Session, make it active, and* open it. If the default token cache is not available, then this will request* the permissions provided (and basic permissions of no permissions are provided).* If the default token cache is available and cached tokens are loaded, this will* use the cached token and associated permissions.* <p/>* If allowedLoginUI is false, this will only create the active session and open* it if it requires no user interaction (i.e. the token cache is available and* there are cached tokens).** @param context The Activity or Service creating this Session* @param fragment The Fragment that is opening the new Session.* @param allowLoginUI if false, only sets the active session and opens it if it* does not require user interaction* @param permissions The permissions to request for this Session* @param callback The {@link StatusCallback SessionStatusCallback} to* notify regarding Session state changes.* @return The new Session or null if one could not be created*/public static Session openActiveSession(Context context, Fragment fragment,boolean allowLoginUI, List<String> permissions, StatusCallback callback) {return openActiveSession(context,allowLoginUI,new OpenRequest(fragment).setCallback(callback).setPermissions(permissions));}/*** Opens a session based on an existing Facebook access token, and also makes this session* the currently active session. This method should be used* only in instances where an application has previously obtained an access token and wishes* to import it into the Session/TokenCachingStrategy-based session-management system. A primary* example would be an application which previously did not use the Facebook SDK for Android* and implemented its own session-management scheme, but wishes to implement an upgrade path* for existing users so they do not need to log in again when upgrading to a version of* the app that uses the SDK. In general, this method will be called only once, when the app* detects that it has been upgraded -- after that, the usual Session lifecycle methods* should be used to manage the session and its associated token.* <p/>* No validation is done that the token, token source, or permissions are actually valid.* It is the caller's responsibility to ensure that these accurately reflect the state of* the token that has been passed in, or calls to the Facebook API may fail.** @param context the Context to use for creation the session* @param accessToken the access token obtained from Facebook* @param callback a callback that will be called when the session status changes; may be null* @return The new Session or null if one could not be created*/public static Session openActiveSessionWithAccessToken(Context context, AccessToken accessToken,StatusCallback callback) {Session session = new Session(context, null, null, false);setActiveSession(session);session.open(accessToken, callback);return session;}private static Session openActiveSession(Context context, boolean allowLoginUI, OpenRequest openRequest) {Session session = new Builder(context).build();if (SessionState.CREATED_TOKEN_LOADED.equals(session.getState()) || allowLoginUI) {setActiveSession(session);session.openForRead(openRequest);return session;}return null;}static Context getStaticContext() {return staticContext;}static void initializeStaticContext(Context currentContext) {if ((currentContext != null) && (staticContext == null)) {Context applicationContext = currentContext.getApplicationContext();staticContext = (applicationContext != null) ? applicationContext : currentContext;}}void authorize(AuthorizationRequest request) {boolean started = false;request.setApplicationId(applicationId);autoPublishAsync();logAuthorizationStart();started = tryLoginActivity(request);pendingAuthorizationRequest.loggingExtras.put(AuthorizationClient.EVENT_EXTRAS_TRY_LOGIN_ACTIVITY,started ? AppEventsConstants.EVENT_PARAM_VALUE_YES : AppEventsConstants.EVENT_PARAM_VALUE_NO);if (!started && request.isLegacy) {pendingAuthorizationRequest.loggingExtras.put(AuthorizationClient.EVENT_EXTRAS_TRY_LEGACY,AppEventsConstants.EVENT_PARAM_VALUE_YES);tryLegacyAuth(request);started = true;}if (!started) {synchronized (this.lock) {final SessionState oldState = this.state;switch (this.state) {case CLOSED:case CLOSED_LOGIN_FAILED:return;default:this.state = SessionState.CLOSED_LOGIN_FAILED;Exception exception = new FacebookException("Log in attempt failed: LoginActivity could not be started, and not legacy request");logAuthorizationComplete(AuthorizationClient.Result.Code.ERROR, null, exception);postStateChange(oldState, this.state, exception);}}}}private void open(OpenRequest openRequest, SessionAuthorizationType authType) {validatePermissions(openRequest, authType);validateLoginBehavior(openRequest);SessionState newState;synchronized (this.lock) {if (pendingAuthorizationRequest != null) {postStateChange(state, state, new UnsupportedOperationException("Session: an attempt was made to open a session that has a pending request."));return;}final SessionState oldState = this.state;switch (this.state) {case CREATED:this.state = newState = SessionState.OPENING;if (openRequest == null) {throw new IllegalArgumentException("openRequest cannot be null when opening a new Session");}pendingAuthorizationRequest = openRequest;break;case CREATED_TOKEN_LOADED:if (openRequest != null && !Utility.isNullOrEmpty(openRequest.getPermissions())) {if (!Utility.isSubset(openRequest.getPermissions(), getPermissions())) {pendingAuthorizationRequest = openRequest;}}if (pendingAuthorizationRequest == null) {this.state = newState = SessionState.OPENED;} else {this.state = newState = SessionState.OPENING;}break;default:throw new UnsupportedOperationException("Session: an attempt was made to open an already opened session.");}if (openRequest != null) {addCallback(openRequest.getCallback());}this.postStateChange(oldState, newState, null);}if (newState == SessionState.OPENING) {authorize(openRequest);}}private void requestNewPermissions(NewPermissionsRequest newPermissionsRequest, SessionAuthorizationType authType) {validatePermissions(newPermissionsRequest, authType);validateLoginBehavior(newPermissionsRequest);if (newPermissionsRequest != null) {synchronized (this.lock) {if (pendingAuthorizationRequest != null) {throw new UnsupportedOperationException("Session: an attempt was made to request new permissions for a session that has a pending request.");}if (state.isOpened()) {pendingAuthorizationRequest = newPermissionsRequest;} else if (state.isClosed()) {throw new UnsupportedOperationException("Session: an attempt was made to request new permissions for a session that has been closed.");} else {throw new UnsupportedOperationException("Session: an attempt was made to request new permissions for a session that is not currently open.");}}newPermissionsRequest.setValidateSameFbidAsToken(getAccessToken());addCallback(newPermissionsRequest.getCallback());authorize(newPermissionsRequest);}}private void validateLoginBehavior(AuthorizationRequest request) {if (request != null && !request.isLegacy) {Intent intent = new Intent();intent.setClass(getStaticContext(), LoginActivity.class);if (!resolveIntent(intent)) {throw new FacebookException(String.format("Cannot use SessionLoginBehavior %s when %s is not declared as an activity in AndroidManifest.xml",request.getLoginBehavior(), LoginActivity.class.getName()));}}}private void validatePermissions(AuthorizationRequest request, SessionAuthorizationType authType) {if (request == null || Utility.isNullOrEmpty(request.getPermissions())) {if (SessionAuthorizationType.PUBLISH.equals(authType)) {throw new FacebookException("Cannot request publish or manage authorization with no permissions.");}return; // nothing to check}for (String permission : request.getPermissions()) {if (isPublishPermission(permission)) {if (SessionAuthorizationType.READ.equals(authType)) {throw new FacebookException(String.format("Cannot pass a publish or manage permission (%s) to a request for read authorization",permission));}} else {if (SessionAuthorizationType.PUBLISH.equals(authType)) {Log.w(TAG,String.format("Should not pass a read permission (%s) to a request for publish or manage authorization",permission));}}}}public static boolean isPublishPermission(String permission) {return permission != null &&(permission.startsWith(PUBLISH_PERMISSION_PREFIX) ||permission.startsWith(MANAGE_PERMISSION_PREFIX) ||OTHER_PUBLISH_PERMISSIONS.contains(permission));}private void handleAuthorizationResult(int resultCode, AuthorizationClient.Result result) {AccessToken newToken = null;Exception exception = null;if (resultCode == Activity.RESULT_OK) {if (result.code == AuthorizationClient.Result.Code.SUCCESS) {newToken = result.token;} else {exception = new FacebookAuthorizationException(result.errorMessage);}} else if (resultCode == Activity.RESULT_CANCELED) {exception = new FacebookOperationCanceledException(result.errorMessage);}logAuthorizationComplete(result.code, result.loggingExtras, exception);authorizationClient = null;finishAuthOrReauth(newToken, exception);}private void logAuthorizationStart() {Bundle bundle = AuthorizationClient.newAuthorizationLoggingBundle(pendingAuthorizationRequest.getAuthId());bundle.putLong(AuthorizationClient.EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());// Log what we already know about the call in start eventtry {JSONObject extras = new JSONObject();extras.put(AuthorizationClient.EVENT_EXTRAS_LOGIN_BEHAVIOR,pendingAuthorizationRequest.loginBehavior.toString());extras.put(AuthorizationClient.EVENT_EXTRAS_REQUEST_CODE, pendingAuthorizationRequest.requestCode);extras.put(AuthorizationClient.EVENT_EXTRAS_IS_LEGACY, pendingAuthorizationRequest.isLegacy);extras.put(AuthorizationClient.EVENT_EXTRAS_PERMISSIONS,TextUtils.join(",", pendingAuthorizationRequest.permissions));extras.put(AuthorizationClient.EVENT_EXTRAS_DEFAULT_AUDIENCE,pendingAuthorizationRequest.defaultAudience.toString());bundle.putString(AuthorizationClient.EVENT_PARAM_EXTRAS, extras.toString());} catch (JSONException e) {}AppEventsLogger logger = getAppEventsLogger();logger.logSdkEvent(AuthorizationClient.EVENT_NAME_LOGIN_START, null, bundle);}private void logAuthorizationComplete(AuthorizationClient.Result.Code result, Map<String, String> resultExtras,Exception exception) {Bundle bundle = null;if (pendingAuthorizationRequest == null) {// We don't expect this to happen, but if it does, log an event for diagnostic purposes.bundle = AuthorizationClient.newAuthorizationLoggingBundle("");bundle.putString(AuthorizationClient.EVENT_PARAM_LOGIN_RESULT,AuthorizationClient.Result.Code.ERROR.getLoggingValue());bundle.putString(AuthorizationClient.EVENT_PARAM_ERROR_MESSAGE,"Unexpected call to logAuthorizationComplete with null pendingAuthorizationRequest.");} else {bundle = AuthorizationClient.newAuthorizationLoggingBundle(pendingAuthorizationRequest.getAuthId());if (result != null) {bundle.putString(AuthorizationClient.EVENT_PARAM_LOGIN_RESULT, result.getLoggingValue());}if (exception != null && exception.getMessage() != null) {bundle.putString(AuthorizationClient.EVENT_PARAM_ERROR_MESSAGE, exception.getMessage());}// Combine extras from the request and from the result.JSONObject jsonObject = null;if (pendingAuthorizationRequest.loggingExtras.isEmpty() == false) {jsonObject = new JSONObject(pendingAuthorizationRequest.loggingExtras);}if (resultExtras != null) {if (jsonObject == null) {jsonObject = new JSONObject();}try {for (Map.Entry<String, String> entry : resultExtras.entrySet()) {jsonObject.put(entry.getKey(), entry.getValue());}} catch (JSONException e) {}}if (jsonObject != null) {bundle.putString(AuthorizationClient.EVENT_PARAM_EXTRAS, jsonObject.toString());}}bundle.putLong(AuthorizationClient.EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());AppEventsLogger logger = getAppEventsLogger();logger.logSdkEvent(AuthorizationClient.EVENT_NAME_LOGIN_COMPLETE, null, bundle);}private boolean tryLoginActivity(AuthorizationRequest request) {Intent intent = getLoginActivityIntent(request);if (!resolveIntent(intent)) {return false;}try {request.getStartActivityDelegate().startActivityForResult(intent, request.getRequestCode());} catch (ActivityNotFoundException e) {return false;}return true;}private boolean resolveIntent(Intent intent) {ResolveInfo resolveInfo = getStaticContext().getPackageManager().resolveActivity(intent, 0);if (resolveInfo == null) {return false;}return true;}private Intent getLoginActivityIntent(AuthorizationRequest request) {Intent intent = new Intent();intent.setClass(getStaticContext(), LoginActivity.class);intent.setAction(request.getLoginBehavior().toString());// Let LoginActivity populate extras appropriatelyAuthorizationClient.AuthorizationRequest authClientRequest = request.getAuthorizationClientRequest();Bundle extras = LoginActivity.populateIntentExtras(authClientRequest);intent.putExtras(extras);return intent;}private void tryLegacyAuth(final AuthorizationRequest request) {authorizationClient = new AuthorizationClient();authorizationClient.setOnCompletedListener(new AuthorizationClient.OnCompletedListener() {@Overridepublic void onCompleted(AuthorizationClient.Result result) {int activityResult;if (result.code == AuthorizationClient.Result.Code.CANCEL) {activityResult = Activity.RESULT_CANCELED;} else {activityResult = Activity.RESULT_OK;}handleAuthorizationResult(activityResult, result);}});authorizationClient.setContext(getStaticContext());authorizationClient.startOrContinueAuth(request.getAuthorizationClientRequest());}void finishAuthOrReauth(AccessToken newToken, Exception exception) {// If the token we came up with is expired/invalid, then auth failed.if ((newToken != null) && newToken.isInvalid()) {newToken = null;exception = new FacebookException("Invalid access token.");}synchronized (this.lock) {switch (this.state) {case OPENING:// This means we are authorizing for the first time in this Session.finishAuthorization(newToken, exception);break;case OPENED:case OPENED_TOKEN_UPDATED:// This means we are reauthorizing.finishReauthorization(newToken, exception);break;case CREATED:case CREATED_TOKEN_LOADED:case CLOSED:case CLOSED_LOGIN_FAILED:Log.d(TAG, "Unexpected call to finishAuthOrReauth in state " + this.state);break;}}}private void finishAuthorization(AccessToken newToken, Exception exception) {final SessionState oldState = state;if (newToken != null) {tokenInfo = newToken;saveTokenToCache(newToken);state = SessionState.OPENED;} else if (exception != null) {state = SessionState.CLOSED_LOGIN_FAILED;}pendingAuthorizationRequest = null;postStateChange(oldState, state, exception);}private void finishReauthorization(final AccessToken newToken, Exception exception) {final SessionState oldState = state;if (newToken != null) {tokenInfo = newToken;saveTokenToCache(newToken);state = SessionState.OPENED_TOKEN_UPDATED;}pendingAuthorizationRequest = null;postStateChange(oldState, state, exception);}private void saveTokenToCache(AccessToken newToken) {if (newToken != null && tokenCachingStrategy != null) {tokenCachingStrategy.save(newToken.toCacheBundle());}}void postStateChange(final SessionState oldState, final SessionState newState, final Exception exception) {// When we request new permissions, we stay in SessionState.OPENED_TOKEN_UPDATED,// but we still want notifications of the state change since permissions are// different now.if ((oldState == newState) &&(oldState != SessionState.OPENED_TOKEN_UPDATED) &&(exception == null)) {return;}if (newState.isClosed()) {this.tokenInfo = AccessToken.createEmptyToken();}// Need to schedule the callbacks inside the same queue to preserve ordering.// Otherwise these callbacks could have been added to the queue before the SessionTracker// gets the ACTIVE_SESSION_SET action.Runnable runCallbacks = new Runnable() {public void run() {synchronized (callbacks) {for (final StatusCallback callback : callbacks) {Runnable closure = new Runnable() {public void run() {// This can be called inside a synchronized block.callback.call(Session.this, newState, exception);}};runWithHandlerOrExecutor(handler, closure);}}}};runWithHandlerOrExecutor(handler, runCallbacks);if (this == Session.activeSession) {if (oldState.isOpened() != newState.isOpened()) {if (newState.isOpened()) {postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_OPENED);} else {postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_CLOSED);}}}}static void postActiveSessionAction(String action) {final Intent intent = new Intent(action);LocalBroadcastManager.getInstance(getStaticContext()).sendBroadcast(intent);}private static void runWithHandlerOrExecutor(Handler handler, Runnable runnable) {if (handler != null) {handler.post(runnable);} else {Settings.getExecutor().execute(runnable);}}void extendAccessTokenIfNeeded() {if (shouldExtendAccessToken()) {extendAccessToken();}}void extendAccessToken() {TokenRefreshRequest newTokenRefreshRequest = null;synchronized (this.lock) {if (currentTokenRefreshRequest == null) {newTokenRefreshRequest = new TokenRefreshRequest();currentTokenRefreshRequest = newTokenRefreshRequest;}}if (newTokenRefreshRequest != null) {newTokenRefreshRequest.bind();}}boolean shouldExtendAccessToken() {if (currentTokenRefreshRequest != null) {return false;}boolean result = false;Date now = new Date();if (state.isOpened() && tokenInfo.getSource().canExtendToken()&& now.getTime() - lastAttemptedTokenExtendDate.getTime() > TOKEN_EXTEND_RETRY_SECONDS * 1000&& now.getTime() - tokenInfo.getLastRefresh().getTime() > TOKEN_EXTEND_THRESHOLD_SECONDS * 1000) {result = true;}return result;}private AppEventsLogger getAppEventsLogger() {synchronized (lock) {if (appEventsLogger == null) {appEventsLogger = AppEventsLogger.newLogger(staticContext, applicationId);}return appEventsLogger;}}AccessToken getTokenInfo() {return tokenInfo;}void setTokenInfo(AccessToken tokenInfo) {this.tokenInfo = tokenInfo;}Date getLastAttemptedTokenExtendDate() {return lastAttemptedTokenExtendDate;}void setLastAttemptedTokenExtendDate(Date lastAttemptedTokenExtendDate) {this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;}void setCurrentTokenRefreshRequest(TokenRefreshRequest request) {this.currentTokenRefreshRequest = request;}class TokenRefreshRequest implements ServiceConnection {final Messenger messageReceiver = new Messenger(new TokenRefreshRequestHandler(Session.this, this));Messenger messageSender = null;public void bind() {Intent intent = NativeProtocol.createTokenRefreshIntent(getStaticContext());if (intent != null&& staticContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {setLastAttemptedTokenExtendDate(new Date());} else {cleanup();}}@Overridepublic void onServiceConnected(ComponentName className, IBinder service) {messageSender = new Messenger(service);refreshToken();}@Overridepublic void onServiceDisconnected(ComponentName arg) {cleanup();try {// We returned an error so there's no point in// keeping the binding open.staticContext.unbindService(TokenRefreshRequest.this);} catch (IllegalArgumentException ex) {// Do nothing, the connection was already unbound}}private void cleanup() {if (currentTokenRefreshRequest == this) {currentTokenRefreshRequest = null;}}private void refreshToken() {Bundle requestData = new Bundle();requestData.putString(AccessToken.ACCESS_TOKEN_KEY, getTokenInfo().getToken());Message request = Message.obtain();request.setData(requestData);request.replyTo = messageReceiver;try {messageSender.send(request);} catch (RemoteException e) {cleanup();}}}// Creating a static Handler class to reduce the possibility of a memory leak.// Handler objects for the same thread all share a common Looper object, which they post messages// to and read from. As messages contain target Handler, as long as there are messages with target// handler in the message queue, the handler cannot be garbage collected. If handler is not static,// the instance of the containing class also cannot be garbage collected even if it is destroyed.static class TokenRefreshRequestHandler extends Handler {private WeakReference<Session> sessionWeakReference;private WeakReference<TokenRefreshRequest> refreshRequestWeakReference;TokenRefreshRequestHandler(Session session, TokenRefreshRequest refreshRequest) {super(Looper.getMainLooper());sessionWeakReference = new WeakReference<Session>(session);refreshRequestWeakReference = new WeakReference<TokenRefreshRequest>(refreshRequest);}@Overridepublic void handleMessage(Message msg) {String token = msg.getData().getString(AccessToken.ACCESS_TOKEN_KEY);Session session = sessionWeakReference.get();if (session != null && token != null) {session.extendTokenCompleted(msg.getData());}TokenRefreshRequest request = refreshRequestWeakReference.get();if (request != null) {// The refreshToken function should be called rarely,// so there is no point in keeping the binding open.staticContext.unbindService(request);request.cleanup();}}}/*** Provides asynchronous notification of Session state changes.** @see Session#open open*/public interface StatusCallback {public void call(Session session, SessionState state, Exception exception);}@Overridepublic int hashCode() {return 0;}@Overridepublic boolean equals(Object otherObj) {if (!(otherObj instanceof Session)) {return false;}Session other = (Session) otherObj;return areEqual(other.applicationId, applicationId) &&areEqual(other.authorizationBundle, authorizationBundle) &&areEqual(other.state, state) &&areEqual(other.getExpirationDate(), getExpirationDate());}private static boolean areEqual(Object a, Object b) {if (a == null) {return b == null;} else {return a.equals(b);}}/*** Builder class used to create a Session.*/public static final class Builder {private final Context context;private String applicationId;private TokenCachingStrategy tokenCachingStrategy;/*** Constructs a new Builder associated with the context.** @param context the Activity or Service starting the Session*/public Builder(Context context) {this.context = context;}/*** Sets the application id for the Session.** @param applicationId the application id* @return the Builder instance*/public Builder setApplicationId(final String applicationId) {this.applicationId = applicationId;return this;}/*** Sets the TokenCachingStrategy for the Session.** @param tokenCachingStrategy the token cache to use* @return the Builder instance*/public Builder setTokenCachingStrategy(final TokenCachingStrategy tokenCachingStrategy) {this.tokenCachingStrategy = tokenCachingStrategy;return this;}/*** Build the Session.** @return a new Session*/public Session build() {return new Session(context, applicationId, tokenCachingStrategy);}}interface StartActivityDelegate {public void startActivityForResult(Intent intent, int requestCode);public Activity getActivityContext();}@SuppressWarnings("deprecation")private void autoPublishAsync() {AutoPublishAsyncTask asyncTask = null;synchronized (this) {if (autoPublishAsyncTask == null && Settings.getShouldAutoPublishInstall()) {// copy the application id to guarantee thread safety against our container.String applicationId = Session.this.applicationId;// skip publish if we don't have an application id.if (applicationId != null) {asyncTask = autoPublishAsyncTask = new AutoPublishAsyncTask(applicationId, staticContext);}}}if (asyncTask != null) {asyncTask.execute();}}/*** Async implementation to allow auto publishing to not block the ui thread.*/private class AutoPublishAsyncTask extends AsyncTask<Void, Void, Void> {private final String mApplicationId;private final Context mApplicationContext;public AutoPublishAsyncTask(String applicationId, Context context) {mApplicationId = applicationId;mApplicationContext = context.getApplicationContext();}@Overrideprotected Void doInBackground(Void... voids) {try {Settings.publishInstallAndWaitForResponse(mApplicationContext, mApplicationId, true);} catch (Exception e) {Utility.logd("Facebook-publish", e);}return null;}@Overrideprotected void onPostExecute(Void result) {// always clear out the publisher to allow other invocations.synchronized (Session.this) {autoPublishAsyncTask = null;}}}/*** Base class for authorization requests {@link OpenRequest} and {@link NewPermissionsRequest}.*/public static class AuthorizationRequest implements Serializable {private static final long serialVersionUID = 1L;private final StartActivityDelegate startActivityDelegate;private SessionLoginBehavior loginBehavior = SessionLoginBehavior.SSO_WITH_FALLBACK;private int requestCode = DEFAULT_AUTHORIZE_ACTIVITY_CODE;private StatusCallback statusCallback;private boolean isLegacy = false;private List<String> permissions = Collections.emptyList();private SessionDefaultAudience defaultAudience = SessionDefaultAudience.FRIENDS;private String applicationId;private String validateSameFbidAsToken;private final String authId = UUID.randomUUID().toString();private final Map<String, String> loggingExtras = new HashMap<String, String>();AuthorizationRequest(final Activity activity) {startActivityDelegate = new StartActivityDelegate() {@Overridepublic void startActivityForResult(Intent intent, int requestCode) {activity.startActivityForResult(intent, requestCode);}@Overridepublic Activity getActivityContext() {return activity;}};}AuthorizationRequest(final Fragment fragment) {startActivityDelegate = new StartActivityDelegate() {@Overridepublic void startActivityForResult(Intent intent, int requestCode) {fragment.startActivityForResult(intent, requestCode);}@Overridepublic Activity getActivityContext() {return fragment.getActivity();}};}/*** Constructor to be used for V1 serialization only, DO NOT CHANGE.*/private AuthorizationRequest(SessionLoginBehavior loginBehavior, int requestCode,List<String> permissions, String defaultAudience, boolean isLegacy, String applicationId,String validateSameFbidAsToken) {startActivityDelegate = new StartActivityDelegate() {@Overridepublic void startActivityForResult(Intent intent, int requestCode) {throw new UnsupportedOperationException("Cannot create an AuthorizationRequest without a valid Activity or Fragment");}@Overridepublic Activity getActivityContext() {throw new UnsupportedOperationException("Cannot create an AuthorizationRequest without a valid Activity or Fragment");}};this.loginBehavior = loginBehavior;this.requestCode = requestCode;this.permissions = permissions;this.defaultAudience = SessionDefaultAudience.valueOf(defaultAudience);this.isLegacy = isLegacy;this.applicationId = applicationId;this.validateSameFbidAsToken = validateSameFbidAsToken;}/*** Used for backwards compatibility with Facebook.java only, DO NOT USE.** @param isLegacy*/public void setIsLegacy(boolean isLegacy) {this.isLegacy = isLegacy;}boolean isLegacy() {return isLegacy;}AuthorizationRequest setCallback(StatusCallback statusCallback) {this.statusCallback = statusCallback;return this;}StatusCallback getCallback() {return statusCallback;}AuthorizationRequest setLoginBehavior(SessionLoginBehavior loginBehavior) {if (loginBehavior != null) {this.loginBehavior = loginBehavior;}return this;}SessionLoginBehavior getLoginBehavior() {return loginBehavior;}AuthorizationRequest setRequestCode(int requestCode) {if (requestCode >= 0) {this.requestCode = requestCode;}return this;}int getRequestCode() {return requestCode;}AuthorizationRequest setPermissions(List<String> permissions) {if (permissions != null) {this.permissions = permissions;}return this;}AuthorizationRequest setPermissions(String... permissions) {return setPermissions(Arrays.asList(permissions));}List<String> getPermissions() {return permissions;}AuthorizationRequest setDefaultAudience(SessionDefaultAudience defaultAudience) {if (defaultAudience != null) {this.defaultAudience = defaultAudience;}return this;}SessionDefaultAudience getDefaultAudience() {return defaultAudience;}StartActivityDelegate getStartActivityDelegate() {return startActivityDelegate;}String getApplicationId() {return applicationId;}void setApplicationId(String applicationId) {this.applicationId = applicationId;}String getValidateSameFbidAsToken() {return validateSameFbidAsToken;}void setValidateSameFbidAsToken(String validateSameFbidAsToken) {this.validateSameFbidAsToken = validateSameFbidAsToken;}String getAuthId() {return authId;}AuthorizationClient.AuthorizationRequest getAuthorizationClientRequest() {AuthorizationClient.StartActivityDelegate delegate = new AuthorizationClient.StartActivityDelegate() {@Overridepublic void startActivityForResult(Intent intent, int requestCode) {startActivityDelegate.startActivityForResult(intent, requestCode);}@Overridepublic Activity getActivityContext() {return startActivityDelegate.getActivityContext();}};return new AuthorizationClient.AuthorizationRequest(loginBehavior, requestCode, isLegacy,permissions, defaultAudience, applicationId, validateSameFbidAsToken, delegate, authId);}// package private so subclasses can use itObject writeReplace() {return new AuthRequestSerializationProxyV1(loginBehavior, requestCode, permissions, defaultAudience.name(), isLegacy, applicationId, validateSameFbidAsToken);}// have a readObject that throws to prevent spoofing; must be private so serializer will call it (will be// called automatically prior to any base class)private void readObject(ObjectInputStream stream) throws InvalidObjectException {throw new InvalidObjectException("Cannot readObject, serialization proxy required");}private static class AuthRequestSerializationProxyV1 implements Serializable {private static final long serialVersionUID = -8748347685113614927L;private final SessionLoginBehavior loginBehavior;private final int requestCode;private boolean isLegacy;private final List<String> permissions;private final String defaultAudience;private final String applicationId;private final String validateSameFbidAsToken;private AuthRequestSerializationProxyV1(SessionLoginBehavior loginBehavior,int requestCode, List<String> permissions, String defaultAudience, boolean isLegacy,String applicationId, String validateSameFbidAsToken) {this.loginBehavior = loginBehavior;this.requestCode = requestCode;this.permissions = permissions;this.defaultAudience = defaultAudience;this.isLegacy = isLegacy;this.applicationId = applicationId;this.validateSameFbidAsToken = validateSameFbidAsToken;}private Object readResolve() {return new AuthorizationRequest(loginBehavior, requestCode, permissions, defaultAudience, isLegacy,applicationId, validateSameFbidAsToken);}}}/*** A request used to open a Session.*/public static final class OpenRequest extends AuthorizationRequest {private static final long serialVersionUID = 1L;/*** Constructs an OpenRequest.** @param activity the Activity to use to open the Session*/public OpenRequest(Activity activity) {super(activity);}/*** Constructs an OpenRequest.** @param fragment the Fragment to use to open the Session*/public OpenRequest(Fragment fragment) {super(fragment);}/*** Sets the StatusCallback for the OpenRequest.** @param statusCallback The {@link StatusCallback SessionStatusCallback} to* notify regarding Session state changes.* @return the OpenRequest object to allow for chaining*/public final OpenRequest setCallback(StatusCallback statusCallback) {super.setCallback(statusCallback);return this;}/*** Sets the login behavior for the OpenRequest.** @param loginBehavior The {@link SessionLoginBehavior SessionLoginBehavior} that* specifies what behaviors should be attempted during* authorization.* @return the OpenRequest object to allow for chaining*/public final OpenRequest setLoginBehavior(SessionLoginBehavior loginBehavior) {super.setLoginBehavior(loginBehavior);return this;}/*** Sets the request code for the OpenRequest.** @param requestCode An integer that identifies this request. This integer will be used* as the request code in {@link Activity#onActivityResult* onActivityResult}. This integer should be >= 0. If a value < 0 is* passed in, then a default value will be used.* @return the OpenRequest object to allow for chaining*/public final OpenRequest setRequestCode(int requestCode) {super.setRequestCode(requestCode);return this;}/*** Sets the permissions for the OpenRequest.** @param permissions A List<String> representing the permissions to request* during the authentication flow. A null or empty List* represents basic permissions.* @return the OpenRequest object to allow for chaining*/public final OpenRequest setPermissions(List<String> permissions) {super.setPermissions(permissions);return this;}/*** Sets the permissions for the OpenRequest.** @param permissions the permissions to request during the authentication flow.* @return the OpenRequest object to allow for chaining*/public final OpenRequest setPermissions(String... permissions) {super.setPermissions(permissions);return this;}/*** Sets the defaultAudience for the OpenRequest.* <p/>* This is only used during Native login using a sufficiently recent facebook app.** @param defaultAudience A SessionDefaultAudience representing the default audience setting to request.* @return the OpenRequest object to allow for chaining*/public final OpenRequest setDefaultAudience(SessionDefaultAudience defaultAudience) {super.setDefaultAudience(defaultAudience);return this;}}/*** A request to be used to request new permissions for a Session.*/public static final class NewPermissionsRequest extends AuthorizationRequest {private static final long serialVersionUID = 1L;/*** Constructs a NewPermissionsRequest.** @param activity the Activity used to issue the request* @param permissions additional permissions to request*/public NewPermissionsRequest(Activity activity, List<String> permissions) {super(activity);setPermissions(permissions);}/*** Constructs a NewPermissionsRequest.** @param fragment the Fragment used to issue the request* @param permissions additional permissions to request*/public NewPermissionsRequest(Fragment fragment, List<String> permissions) {super(fragment);setPermissions(permissions);}/*** Constructs a NewPermissionsRequest.** @param activity the Activity used to issue the request* @param permissions additional permissions to request*/public NewPermissionsRequest(Activity activity, String... permissions) {super(activity);setPermissions(permissions);}/*** Constructs a NewPermissionsRequest.** @param fragment the Fragment used to issue the request* @param permissions additional permissions to request*/public NewPermissionsRequest(Fragment fragment, String... permissions) {super(fragment);setPermissions(permissions);}/*** Sets the StatusCallback for the NewPermissionsRequest. Note that once the request is made, this callback* will be added to the session, and will receive all future state changes on the session.** @param statusCallback The {@link StatusCallback SessionStatusCallback} to* notify regarding Session state changes.* @return the NewPermissionsRequest object to allow for chaining*/public final NewPermissionsRequest setCallback(StatusCallback statusCallback) {super.setCallback(statusCallback);return this;}/*** Sets the login behavior for the NewPermissionsRequest.** @param loginBehavior The {@link SessionLoginBehavior SessionLoginBehavior} that* specifies what behaviors should be attempted during* authorization.* @return the NewPermissionsRequest object to allow for chaining*/public final NewPermissionsRequest setLoginBehavior(SessionLoginBehavior loginBehavior) {super.setLoginBehavior(loginBehavior);return this;}/*** Sets the request code for the NewPermissionsRequest.** @param requestCode An integer that identifies this request. This integer will be used* as the request code in {@link Activity#onActivityResult* onActivityResult}. This integer should be >= 0. If a value < 0 is* passed in, then a default value will be used.* @return the NewPermissionsRequest object to allow for chaining*/public final NewPermissionsRequest setRequestCode(int requestCode) {super.setRequestCode(requestCode);return this;}/*** Sets the defaultAudience for the OpenRequest.** @param defaultAudience A SessionDefaultAudience representing the default audience setting to request.* @return the NewPermissionsRequest object to allow for chaining*/public final NewPermissionsRequest setDefaultAudience(SessionDefaultAudience defaultAudience) {super.setDefaultAudience(defaultAudience);return this;}@OverrideAuthorizationClient.AuthorizationRequest getAuthorizationClientRequest() {AuthorizationClient.AuthorizationRequest request = super.getAuthorizationClientRequest();request.setRerequest(true);return request;}}}