/** * 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.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.util.Log; import com.facebook.internal.SessionAuthorizationType; import com.facebook.internal.Utility; import com.facebook.internal.Validate; import java.io.*; import java.lang.ref.WeakReference; import java.util.*; /** *
* Session is used to authenticate a user and manage the user's session with * Facebook. *
** 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. *
** Instances of Session provide state change notification via a callback * interface, {@link Session.StatusCallback StatusCallback}. *
*/ 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"; /** * Session takes application id as a constructor parameter. If this is null, * Session will attempt to load the application id from * application/meta-data using this String as the key. */ public static final String APPLICATION_ID_PROPERTY = "com.facebook.sdk.ApplicationId"; private static final Object STATIC_LOCK = new Object(); private static Session activeSession; private static volatile Context staticContext; // Token extension constants private static final int TOKEN_EXTEND_THRESHOLD_SECONDS = 24 * 60 * 60; // 1 // day private static final int TOKEN_EXTEND_RETRY_SECONDS = 60 * 60; // 1 hour private 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* Returns the Date at which the current token will expire. *
** Note that Session automatically attempts to extend the lifetime of Tokens * as needed when Facebook requests are made. *
* * @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(); } } /** ** Returns the list of permissions associated with the session. *
** 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}. *
* * @return the list of permissions associated with the session, or null if there is no access token */ public final List* Logs a user in to Facebook. *
** 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. *
** 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. *
** 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. *
* * @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); } /** ** Logs a user in to Facebook. *
** 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. *
** 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. *
** 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. *
* * @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. * * 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 (pendingRequest != null) { throw new UnsupportedOperationException( "Session: an attempt was made to open a session that has a pending request."); } 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(); } /** ** Issues a request to add new read permissions to the Session. *
** If successful, this will update the set of permissions on this session to * match the newPermissions. If this fails, the Session remains unchanged. *
** 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. *
* * @param newPermissionsRequest the new permissions request */ public final void requestNewReadPermissions(NewPermissionsRequest newPermissionsRequest) { requestNewPermissions(newPermissionsRequest, SessionAuthorizationType.READ); } /** ** Issues a request to add new publish or manage permissions to the Session. *
** If successful, this will update the set of permissions on this session to * match the newPermissions. If this fails, the Session remains unchanged. *
** 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. *
* * @param newPermissionsRequest the new permissions request */ public final void requestNewPublishPermissions(NewPermissionsRequest newPermissionsRequest) { requestNewPermissions(newPermissionsRequest, SessionAuthorizationType.PUBLISH); } /** * 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 (pendingRequest == null || (requestCode != pendingRequest.getRequestCode())) { return false; } } AccessToken newToken = null; Exception exception = null; 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."); } finishAuthOrReauth(newToken, exception); return true; } /** * Closes the local in-memory Session object, but does not clear the * persisted token cache. */ @SuppressWarnings("incomplete-switch") 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; } } } /** * 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); 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); } } @Override public 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 open Log.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, pendingRequest); } // have a readObject that throws to prevent spoofing private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Cannot readObject, serialization proxy required"); } /** * Save the Session object into the supplied Bundle. * * @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. * * @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; } } /** ** Sets the current active Session. *
** The active Session is used implicitly by predefined Request factory * methods as well as optionally by UI controls in the sdk. *
** It is legal to set this to null, or to a Session that is not yet open. *
* * @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); } } } } } /** * Create a new Session, and if a token cache is available, open the * Session and make it active without any user interaction. * * @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. * * 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 * basic permissions. If the default token cache is available and cached tokens * are loaded, this will use the cached token and associated permissions. * * 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)); } /** * 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. * * 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(); started = tryLoginActivity(request); if (!started && request.isLegacy) { started = tryLegacyAuth(request); } 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; postStateChange(oldState, this.state, new FacebookException("Log in attempt failed.")); } } } } private void open(OpenRequest openRequest, SessionAuthorizationType authType) { validatePermissions(openRequest, authType); validateLoginBehavior(openRequest); SessionState newState; synchronized (this.lock) { if (pendingRequest != 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"); } pendingRequest = openRequest; break; case CREATED_TOKEN_LOADED: if (openRequest != null && !Utility.isNullOrEmpty(openRequest.getPermissions())) { if (!Utility.isSubset(openRequest.getPermissions(), getPermissions())) { pendingRequest = openRequest; } } if (pendingRequest == 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 (pendingRequest != null) { throw new UnsupportedOperationException( "Session: an attempt was made to request new permissions for a session that has a pending request."); } switch (this.state) { case OPENED: case OPENED_TOKEN_UPDATED: pendingRequest = newPermissionsRequest; break; default: throw new UnsupportedOperationException( "Session: an attempt was made to request new permissions for a session that is not currently open."); } } newPermissionsRequest.setValidateSameFbidAsToken(getAccessToken()); 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)); } } } } 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); } authorizationClient = null; finishAuthOrReauth(newToken, exception); } 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 appropriately AuthorizationClient.AuthorizationRequest authClientRequest = request.getAuthorizationClientRequest(); Bundle extras = LoginActivity.populateIntentExtras(authClientRequest); intent.putExtras(extras); return intent; } private boolean tryLegacyAuth(final AuthorizationRequest request) { authorizationClient = new AuthorizationClient(); authorizationClient.setOnCompletedListener(new AuthorizationClient.OnCompletedListener() { @Override public 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()); return true; } @SuppressWarnings("incomplete-switch") 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; } } } 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; } pendingRequest = 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; } pendingRequest = 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(Collections.