/** * 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.android; import android.Manifest; import android.app.Activity; import android.content.*; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.Signature; import android.net.Uri; import android.os.*; import com.facebook.*; import com.facebook.Session.StatusCallback; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * THIS CLASS SHOULD BE CONSIDERED DEPRECATED. *
* All public members of this class are intentionally deprecated. * New code should instead use * {@link Session} to manage session state, * {@link Request} to make API requests, and * {@link com.facebook.widget.WebDialog} to make dialog requests. * * Adding @Deprecated to this class causes warnings in other deprecated classes * that reference this one. That is the only reason this entire class is not * deprecated. * * @devDocDeprecated */ public class Facebook { // Strings used in the authorization flow @Deprecated public static final String REDIRECT_URI = "fbconnect://success"; @Deprecated public static final String CANCEL_URI = "fbconnect://cancel"; @Deprecated public static final String TOKEN = "access_token"; @Deprecated public static final String EXPIRES = "expires_in"; @Deprecated public static final String SINGLE_SIGN_ON_DISABLED = "service_disabled"; @Deprecated public static final Uri ATTRIBUTION_ID_CONTENT_URI = Uri.parse("content://com.facebook.katana.provider.AttributionIdProvider"); @Deprecated public static final String ATTRIBUTION_ID_COLUMN_NAME = "aid"; @Deprecated public static final int FORCE_DIALOG_AUTH = -1; private static final String LOGIN = "oauth"; // Used as default activityCode by authorize(). See authorize() below. private static final int DEFAULT_AUTH_ACTIVITY_CODE = 32665; // Facebook server endpoints: may be modified in a subclass for testing @Deprecated protected static String DIALOG_BASE_URL = "https://m.facebook.com/dialog/"; @Deprecated protected static String GRAPH_BASE_URL = "https://graph.facebook.com/"; @Deprecated protected static String RESTSERVER_URL = "https://api.facebook.com/restserver.php"; private final Object lock = new Object(); private String accessToken = null; private long accessExpiresMillisecondsAfterEpoch = 0; private long lastAccessUpdateMillisecondsAfterEpoch = 0; private String mAppId; private Activity pendingAuthorizationActivity; private String[] pendingAuthorizationPermissions; private Session pendingOpeningSession; private volatile Session session; // must synchronize this.sync to write private boolean sessionInvalidated; // must synchronize this.sync to access private SetterTokenCachingStrategy tokenCache; private volatile Session userSetSession; // If the last time we extended the access token was more than 24 hours ago // we try to refresh the access token again. final private long REFRESH_TOKEN_BARRIER = 24L * 60L * 60L * 1000L; /** * Constructor for Facebook object. * * @param appId * Your Facebook application ID. Found at * www.facebook.com/developers/apps.php. */ @Deprecated public Facebook(String appId) { if (appId == null) { throw new IllegalArgumentException("You must specify your application ID when instantiating " + "a Facebook object. See README for details."); } mAppId = appId; } /** * Default authorize method. Grants only basic permissions. * * See authorize() below for @params. * * This method is deprecated. See {@link Facebook} and {@link Session} for more info. */ @Deprecated public void authorize(Activity activity, final DialogListener listener) { authorize(activity, new String[]{}, DEFAULT_AUTH_ACTIVITY_CODE, SessionLoginBehavior.SSO_WITH_FALLBACK, listener); } /** * Authorize method that grants custom permissions. * * See authorize() below for @params. * * This method is deprecated. See {@link Facebook} and {@link Session} for more info. */ @Deprecated public void authorize(Activity activity, String[] permissions, final DialogListener listener) { authorize(activity, permissions, DEFAULT_AUTH_ACTIVITY_CODE, SessionLoginBehavior.SSO_WITH_FALLBACK, listener); } /** * Full authorize method. * * Starts either an Activity or a dialog which prompts the user to log in to * Facebook and grant the requested permissions to the given application. * * This method will, when possible, use Facebook's single sign-on for * Android to obtain an access token. This involves proxying a call through * the Facebook for Android stand-alone application, which will handle the * authentication flow, and return an OAuth access token for making API * calls. * * Because this process will not be available for all users, if single * sign-on is not possible, this method will automatically fall back to the * OAuth 2.0 User-Agent flow. In this flow, the user credentials are handled * by Facebook in an embedded WebView, not by the client application. As * such, the dialog makes a network request and renders HTML content rather * than a native UI. The access token is retrieved from a redirect to a * special URL that the WebView handles. * * Note that User credentials could be handled natively using the OAuth 2.0 * Username and Password Flow, but this is not supported by this SDK. * * See http://developers.facebook.com/docs/authentication/ and * http://wiki.oauth.net/OAuth-2 for more details. * * Note that this method is asynchronous and the callback will be invoked in * the original calling thread (not in a background thread). * * Also note that requests may be made to the API without calling authorize * first, in which case only public information is returned. * * IMPORTANT: Note that single sign-on authentication will not function * correctly if you do not include a call to the authorizeCallback() method * in your onActivityResult() function! Please see below for more * information. single sign-on may be disabled by passing FORCE_DIALOG_AUTH * as the activityCode parameter in your call to authorize(). * * This method is deprecated. See {@link Facebook} and {@link Session} for more info. * * @param activity * The Android activity in which we want to display the * authorization dialog. * @param permissions * A list of permissions required for this application: e.g. * "read_stream", "publish_stream", "offline_access", etc. see * http://developers.facebook.com/docs/authentication/permissions * This parameter should not be null -- if you do not require any * permissions, then pass in an empty String array. * @param activityCode * Single sign-on requires an activity result to be called back * to the client application -- if you are waiting on other * activities to return data, pass a custom activity code here to * avoid collisions. If you would like to force the use of legacy * dialog-based authorization, pass FORCE_DIALOG_AUTH for this * parameter. Otherwise just omit this parameter and Facebook * will use a suitable default. See * http://developer.android.com/reference/android/ * app/Activity.html for more information. * @param listener * Callback interface for notifying the calling application when * the authentication dialog has completed, failed, or been * canceled. */ @Deprecated public void authorize(Activity activity, String[] permissions, int activityCode, final DialogListener listener) { SessionLoginBehavior behavior = (activityCode >= 0) ? SessionLoginBehavior.SSO_WITH_FALLBACK : SessionLoginBehavior.SUPPRESS_SSO; authorize(activity, permissions, activityCode, behavior, listener); } /** * Full authorize method. * * Starts either an Activity or a dialog which prompts the user to log in to * Facebook and grant the requested permissions to the given application. * * This method will, when possible, use Facebook's single sign-on for * Android to obtain an access token. This involves proxying a call through * the Facebook for Android stand-alone application, which will handle the * authentication flow, and return an OAuth access token for making API * calls. * * Because this process will not be available for all users, if single * sign-on is not possible, this method will automatically fall back to the * OAuth 2.0 User-Agent flow. In this flow, the user credentials are handled * by Facebook in an embedded WebView, not by the client application. As * such, the dialog makes a network request and renders HTML content rather * than a native UI. The access token is retrieved from a redirect to a * special URL that the WebView handles. * * Note that User credentials could be handled natively using the OAuth 2.0 * Username and Password Flow, but this is not supported by this SDK. * * See http://developers.facebook.com/docs/authentication/ and * http://wiki.oauth.net/OAuth-2 for more details. * * Note that this method is asynchronous and the callback will be invoked in * the original calling thread (not in a background thread). * * Also note that requests may be made to the API without calling authorize * first, in which case only public information is returned. * * IMPORTANT: Note that single sign-on authentication will not function * correctly if you do not include a call to the authorizeCallback() method * in your onActivityResult() function! Please see below for more * information. single sign-on may be disabled by passing FORCE_DIALOG_AUTH * as the activityCode parameter in your call to authorize(). * * @param activity * The Android activity in which we want to display the * authorization dialog. * @param permissions * A list of permissions required for this application: e.g. * "read_stream", "publish_stream", "offline_access", etc. see * http://developers.facebook.com/docs/authentication/permissions * This parameter should not be null -- if you do not require any * permissions, then pass in an empty String array. * @param activityCode * Single sign-on requires an activity result to be called back * to the client application -- if you are waiting on other * activities to return data, pass a custom activity code here to * avoid collisions. If you would like to force the use of legacy * dialog-based authorization, pass FORCE_DIALOG_AUTH for this * parameter. Otherwise just omit this parameter and Facebook * will use a suitable default. See * http://developer.android.com/reference/android/ * app/Activity.html for more information. * @param behavior * The {@link SessionLoginBehavior SessionLoginBehavior} that * specifies what behaviors should be attempted during * authorization. * @param listener * Callback interface for notifying the calling application when * the authentication dialog has completed, failed, or been * canceled. */ private void authorize(Activity activity, String[] permissions, int activityCode, SessionLoginBehavior behavior, final DialogListener listener) { checkUserSession("authorize"); pendingOpeningSession = new Session.Builder(activity). setApplicationId(mAppId). setTokenCachingStrategy(getTokenCache()). build(); pendingAuthorizationActivity = activity; pendingAuthorizationPermissions = (permissions != null) ? permissions : new String[0]; StatusCallback callback = new StatusCallback() { @Override public void call(Session callbackSession, SessionState state, Exception exception) { // Invoke user-callback. onSessionCallback(callbackSession, state, exception, listener); } }; Session.OpenRequest openRequest = new Session.OpenRequest(activity). setCallback(callback). setLoginBehavior(behavior). setRequestCode(activityCode). setPermissions(Arrays.asList(permissions)); openSession(pendingOpeningSession, openRequest, pendingAuthorizationPermissions.length > 0); } private void openSession(Session session, Session.OpenRequest openRequest, boolean isPublish) { openRequest.setIsLegacy(true); if (isPublish) { session.openForPublish(openRequest); } else { session.openForRead(openRequest); } } @SuppressWarnings("deprecation") private void onSessionCallback(Session callbackSession, SessionState state, Exception exception, DialogListener listener) { Bundle extras = callbackSession.getAuthorizationBundle(); if (state == SessionState.OPENED) { Session sessionToClose = null; synchronized (Facebook.this.lock) { if (callbackSession != Facebook.this.session) { sessionToClose = Facebook.this.session; Facebook.this.session = callbackSession; Facebook.this.sessionInvalidated = false; } } if (sessionToClose != null) { sessionToClose.close(); } listener.onComplete(extras); } else if (exception != null) { if (exception instanceof FacebookOperationCanceledException) { listener.onCancel(); } else if ((exception instanceof FacebookAuthorizationException) && (extras != null) && extras.containsKey(Session.WEB_VIEW_ERROR_CODE_KEY) && extras.containsKey(Session.WEB_VIEW_FAILING_URL_KEY)) { DialogError error = new DialogError(exception.getMessage(), extras.getInt(Session.WEB_VIEW_ERROR_CODE_KEY), extras.getString(Session.WEB_VIEW_FAILING_URL_KEY)); listener.onError(error); } else { FacebookError error = new FacebookError(exception.getMessage()); listener.onFacebookError(error); } } } /** * Helper to validate a service intent by resolving and checking the * provider's package signature. * * @param context * @param intent * @return true if the service intent resolution happens successfully and * the signatures match. */ private boolean validateServiceIntent(Context context, Intent intent) { ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, 0); if (resolveInfo == null) { return false; } return validateAppSignatureForPackage(context, resolveInfo.serviceInfo.packageName); } /** * Query the signature for the application that would be invoked by the * given intent and verify that it matches the FB application's signature. * * @param context * @param packageName * @return true if the app's signature matches the expected signature. */ private boolean validateAppSignatureForPackage(Context context, String packageName) { PackageInfo packageInfo; try { packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); } catch (NameNotFoundException e) { return false; } for (Signature signature : packageInfo.signatures) { if (signature.toCharsString().equals(FB_APP_SIGNATURE)) { return true; } } return false; } /** * IMPORTANT: If you are using the deprecated authorize() method, * this method must be invoked at the top of the calling * activity's onActivityResult() function or Facebook authentication will * not function properly! * * If your calling activity does not currently implement onActivityResult(), * you must implement it and include a call to this method if you intend to * use the authorize() method in this SDK. * * For more information, see * http://developer.android.com/reference/android/app/ * Activity.html#onActivityResult(int, int, android.content.Intent) * * This method is deprecated. See {@link Facebook} and {@link Session} for more info. */ @Deprecated public void authorizeCallback(int requestCode, int resultCode, Intent data) { checkUserSession("authorizeCallback"); Session pending = this.pendingOpeningSession; if (pending != null) { if (pending.onActivityResult(this.pendingAuthorizationActivity, requestCode, resultCode, data)) { this.pendingOpeningSession = null; this.pendingAuthorizationActivity = null; this.pendingAuthorizationPermissions = null; } } } /** * Refresh OAuth access token method. Binds to Facebook for Android * stand-alone application application to refresh the access token. This * method tries to connect to the Facebook App which will handle the * authentication flow, and return a new OAuth access token. This method * will automatically replace the old token with a new one. Note that this * method is asynchronous and the callback will be invoked in the original * calling thread (not in a background thread). * * This method is deprecated. See {@link Facebook} and {@link Session} for more info. * * @param context * The Android Context that will be used to bind to the Facebook * RefreshToken Service * @param serviceListener * Callback interface for notifying the calling application when * the refresh request has completed or failed (can be null). In * case of a success a new token can be found inside the result * Bundle under Facebook.ACCESS_TOKEN key. * @return true if the binding to the RefreshToken Service was created */ @Deprecated public boolean extendAccessToken(Context context, ServiceListener serviceListener) { checkUserSession("extendAccessToken"); Intent intent = new Intent(); intent.setClassName("com.facebook.katana", "com.facebook.katana.platform.TokenRefreshService"); // Verify that the application whose package name is // com.facebook.katana // has the expected FB app signature. if (!validateServiceIntent(context, intent)) { return false; } return context.bindService(intent, new TokenRefreshServiceConnection(context, serviceListener), Context.BIND_AUTO_CREATE); } /** * Calls extendAccessToken if shouldExtendAccessToken returns true. * * This method is deprecated. See {@link Facebook} and {@link Session} for more info. * * @return the same value as extendAccessToken if the the token requires * refreshing, true otherwise */ @Deprecated public boolean extendAccessTokenIfNeeded(Context context, ServiceListener serviceListener) { checkUserSession("extendAccessTokenIfNeeded"); if (shouldExtendAccessToken()) { return extendAccessToken(context, serviceListener); } return true; } /** * Check if the access token requires refreshing. * * This method is deprecated. See {@link Facebook} and {@link Session} for more info. * * @return true if the last time a new token was obtained was over 24 hours * ago. */ @Deprecated public boolean shouldExtendAccessToken() { checkUserSession("shouldExtendAccessToken"); return isSessionValid() && (System.currentTimeMillis() - lastAccessUpdateMillisecondsAfterEpoch >= REFRESH_TOKEN_BARRIER); } /** * Handles connection to the token refresh service (this service is a part * of Facebook App). */ private class TokenRefreshServiceConnection implements ServiceConnection { final Messenger messageReceiver = new Messenger( new TokenRefreshConnectionHandler(Facebook.this, this)); final ServiceListener serviceListener; final Context applicationsContext; Messenger messageSender = null; public TokenRefreshServiceConnection(Context applicationsContext, ServiceListener serviceListener) { this.applicationsContext = applicationsContext; this.serviceListener = serviceListener; } @Override public void onServiceConnected(ComponentName className, IBinder service) { messageSender = new Messenger(service); refreshToken(); } @Override public void onServiceDisconnected(ComponentName arg) { serviceListener.onError(new Error("Service disconnected")); // We returned an error so there's no point in // keeping the binding open. applicationsContext.unbindService(TokenRefreshServiceConnection.this); } private void refreshToken() { Bundle requestData = new Bundle(); requestData.putString(TOKEN, accessToken); Message request = Message.obtain(); request.setData(requestData); request.replyTo = messageReceiver; try { messageSender.send(request); } catch (RemoteException e) { serviceListener.onError(new Error("Service connection error")); } } } // 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. private static class TokenRefreshConnectionHandler extends Handler { WeakReference
* Bundle parameters = new Bundle();
* parameters.putString("method", "auth.expireSession");
* String response = request(parameters);
*
*
* This method is deprecated. See {@link Facebook} and {@link Request} for more info.
*
* @param parameters
* Key-value pairs of parameters to the request. Refer to the
* documentation: one of the parameters must be "method".
* @throws IOException
* if a network error occurs
* @throws MalformedURLException
* if accessing an invalid endpoint
* @throws IllegalArgumentException
* if one of the parameters is not "method"
* @return JSON string representation of the response
*/
@Deprecated
public String request(Bundle parameters) throws MalformedURLException, IOException {
if (!parameters.containsKey("method")) {
throw new IllegalArgumentException("API method must be specified. "
+ "(parameters must contain key \"method\" and value). See"
+ " http://developers.facebook.com/docs/reference/rest/");
}
return requestImpl(null, parameters, "GET");
}
/**
* Make a request to the Facebook Graph API without any parameters.
*
* See http://developers.facebook.com/docs/api
*
* Note that this method blocks waiting for a network response, so do not
* call it in a UI thread.
*
* This method is deprecated. See {@link Facebook} and {@link Request} for more info.
*
* @param graphPath
* Path to resource in the Facebook graph, e.g., to fetch data
* about the currently logged authenticated user, provide "me",
* which will fetch http://graph.facebook.com/me
* @throws IOException
* @throws MalformedURLException
* @return JSON string representation of the response
*/
@Deprecated
public String request(String graphPath) throws MalformedURLException, IOException {
return requestImpl(graphPath, new Bundle(), "GET");
}
/**
* Make a request to the Facebook Graph API with the given string parameters
* using an HTTP GET (default method).
*
* See http://developers.facebook.com/docs/api
*
* Note that this method blocks waiting for a network response, so do not
* call it in a UI thread.
*
* This method is deprecated. See {@link Facebook} and {@link Request} for more info.
*
* @param graphPath
* Path to resource in the Facebook graph, e.g., to fetch data
* about the currently logged authenticated user, provide "me",
* which will fetch http://graph.facebook.com/me
* @param parameters
* key-value string parameters, e.g. the path "search" with
* parameters "q" : "facebook" would produce a query for the
* following graph resource:
* https://graph.facebook.com/search?q=facebook
* @throws IOException
* @throws MalformedURLException
* @return JSON string representation of the response
*/
@Deprecated
public String request(String graphPath, Bundle parameters) throws MalformedURLException, IOException {
return requestImpl(graphPath, parameters, "GET");
}
/**
* Synchronously make a request to the Facebook Graph API with the given
* HTTP method and string parameters. Note that binary data parameters (e.g.
* pictures) are not yet supported by this helper function.
*
* See http://developers.facebook.com/docs/api
*
* Note that this method blocks waiting for a network response, so do not
* call it in a UI thread.
*
* This method is deprecated. See {@link Facebook} and {@link Request} for more info.
*
* @param graphPath
* Path to resource in the Facebook graph, e.g., to fetch data
* about the currently logged authenticated user, provide "me",
* which will fetch http://graph.facebook.com/me
* @param params
* Key-value string parameters, e.g. the path "search" with
* parameters {"q" : "facebook"} would produce a query for the
* following graph resource:
* https://graph.facebook.com/search?q=facebook
* @param httpMethod
* http verb, e.g. "GET", "POST", "DELETE"
* @throws IOException
* @throws MalformedURLException
* @return JSON string representation of the response
*/
@Deprecated
public String request(String graphPath, Bundle params, String httpMethod) throws FileNotFoundException,
MalformedURLException, IOException {
return requestImpl(graphPath, params, httpMethod);
}
// Internal call to avoid deprecated warnings.
@SuppressWarnings("deprecation")
String requestImpl(String graphPath, Bundle params, String httpMethod) throws FileNotFoundException,
MalformedURLException, IOException {
params.putString("format", "json");
if (isSessionValid()) {
params.putString(TOKEN, getAccessToken());
}
String url = (graphPath != null) ? GRAPH_BASE_URL + graphPath : RESTSERVER_URL;
return Util.openUrl(url, httpMethod, params);
}
/**
* Generate a UI dialog for the request action in the given Android context.
*
* Note that this method is asynchronous and the callback will be invoked in
* the original calling thread (not in a background thread).
*
* This method is deprecated. See {@link com.facebook.widget.WebDialog}.
*
* @param context
* The Android context in which we will generate this dialog.
* @param action
* String representation of the desired method: e.g. "login",
* "stream.publish", ...
* @param listener
* Callback interface to notify the application when the dialog
* has completed.
*/
@Deprecated
public void dialog(Context context, String action, DialogListener listener) {
dialog(context, action, new Bundle(), listener);
}
/**
* Generate a UI dialog for the request action in the given Android context
* with the provided parameters.
*
* Note that this method is asynchronous and the callback will be invoked in
* the original calling thread (not in a background thread).
*
* This method is deprecated. See {@link com.facebook.widget.WebDialog}.
*
* @param context
* The Android context in which we will generate this dialog.
* @param action
* String representation of the desired method: e.g. "feed" ...
* @param parameters
* String key-value pairs to be passed as URL parameters.
* @param listener
* Callback interface to notify the application when the dialog
* has completed.
*/
@Deprecated
public void dialog(Context context, String action, Bundle parameters, final DialogListener listener) {
parameters.putString("display", "touch");
parameters.putString("redirect_uri", REDIRECT_URI);
if (action.equals(LOGIN)) {
parameters.putString("type", "user_agent");
parameters.putString("client_id", mAppId);
} else {
parameters.putString("app_id", mAppId);
// We do not want to add an access token when displaying the auth dialog.
if (isSessionValid()) {
parameters.putString(TOKEN, getAccessToken());
}
}
if (context.checkCallingOrSelfPermission(Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) {
Util.showAlert(context, "Error", "Application requires permission to access the Internet");
} else {
new FbDialog(context, action, parameters, listener).show();
}
}
/**
* Returns whether the current access token is valid
*
* @return boolean - whether this object has an non-expired session token
*/
@Deprecated
public boolean isSessionValid() {
return (getAccessToken() != null)
&& ((getAccessExpires() == 0) || (System.currentTimeMillis() < getAccessExpires()));
}
/**
* Allows the user to set a Session for the Facebook class to use.
* If a Session is set here, then one should not use the authorize, logout,
* or extendAccessToken methods which alter the Session object since that may
* result in undefined behavior. Using those methods after setting the
* session here will result in exceptions being thrown.
*
* @param session the Session object to use, cannot be null
*/
@Deprecated
public void setSession(Session session) {
if (session == null) {
throw new IllegalArgumentException("session cannot be null");
}
synchronized (this.lock) {
this.userSetSession = session;
}
}
private void checkUserSession(String methodName) {
if (userSetSession != null) {
throw new UnsupportedOperationException(
String.format("Cannot call %s after setSession has been called.", methodName));
}
}
/**
* Get the underlying Session object to use with 3.0 api.
*
* @return Session - underlying session
*/
@Deprecated
public final Session getSession() {
while (true) {
String cachedToken = null;
Session oldSession = null;
synchronized (this.lock) {
if (userSetSession != null) {
return userSetSession;
}
if ((session != null) || !sessionInvalidated) {
return session;
}
cachedToken = accessToken;
oldSession = session;
}
if (cachedToken == null) {
return null;
}
// At this point we do not have a valid session, but mAccessToken is
// non-null.
// So we can try building a session based on that.
List