diff --git a/Android/Facebook/README.md b/Android/Facebook/README.md
new file mode 100644
index 00000000..2602fc14
--- /dev/null
+++ b/Android/Facebook/README.md
@@ -0,0 +1,36 @@
+# Facebook for PhoneGap on Android #
+by Jos Shepherd
+
+This is an attempt to make a PhoneGap plugin from the Facebook Android SDK:
+https://github.com/facebook/facebook-android-sdk
+
+It is currently not functional (the login dialog is silently failing to appear)
+
+
+## Licence ##
+
+The MIT License
+
+Copyright (c) 2010 Jos Shepherd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+
+
diff --git a/Android/Facebook/src/com/facebook/android/AsyncFacebookRunner.java b/Android/Facebook/src/com/facebook/android/AsyncFacebookRunner.java
new file mode 100644
index 00000000..d5d07efa
--- /dev/null
+++ b/Android/Facebook/src/com/facebook/android/AsyncFacebookRunner.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2010 Facebook, Inc.
+ *
+ * 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 java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * A sample implementation of asynchronous API requests. This class provides
+ * the ability to execute API methods and have the call return immediately,
+ * without blocking the calling thread. This is necessary when accessing the
+ * API in the UI thread, for instance. The request response is returned to
+ * the caller via a callback interface, which the developer must implement.
+ *
+ * This sample implementation simply spawns a new thread for each request,
+ * and makes the API call immediately. This may work in many applications,
+ * but more sophisticated users may re-implement this behavior using a thread
+ * pool, a network thread, a request queue, or other mechanism. Advanced
+ * functionality could be built, such as rate-limiting of requests, as per
+ * a specific application's needs.
+ *
+ * @see RequestListener
+ * The callback interface.
+ *
+ * @author ssoneff@facebook.com
+ *
+ */
+public class AsyncFacebookRunner {
+
+ Facebook fb;
+
+ public AsyncFacebookRunner(Facebook fb) {
+ this.fb = fb;
+ }
+
+ /**
+ * Invalidate the current user session by removing the access token in
+ * memory, clearing the browser cookies, and calling auth.expireSession
+ * through the API. The application will be notified when logout is
+ * complete via the callback interface.
+ *
+ * Note that this method is asynchronous and the callback will be invoked
+ * in a background thread; operations that affect the UI will need to be
+ * posted to the UI thread or an appropriate handler.
+ *
+ * @param context
+ * The Android context in which the logout should be called: it
+ * should be the same context in which the login occurred in
+ * order to clear any stored cookies
+ * @param listener
+ * Callback interface to notify the application when the request
+ * has completed.
+ */
+ public void logout(final Context context, final RequestListener listener) {
+ new Thread() {
+ @Override public void run() {
+ try {
+ String response = fb.logout(context);
+ if (response.length() == 0 || response.equals("false")){
+ listener.onFacebookError(new FacebookError(
+ "auth.expireSession failed"));
+ return;
+ }
+ listener.onComplete(response);
+ } catch (FileNotFoundException e) {
+ listener.onFileNotFoundException(e);
+ } catch (MalformedURLException e) {
+ listener.onMalformedURLException(e);
+ } catch (IOException e) {
+ listener.onIOException(e);
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Make a request to Facebook's old (pre-graph) API with the given
+ * parameters. One of the parameter keys must be "method" and its value
+ * should be a valid REST server API method.
+ *
+ *
+ * See http://developers.facebook.com/docs/reference/rest/
+ *
+ * Note that this method is asynchronous and the callback will be invoked
+ * in a background thread; operations that affect the UI will need to be
+ * posted to the UI thread or an appropriate handler.
+ *
+ * Example:
+ *
+ * Bundle parameters = new Bundle();
+ * parameters.putString("method", "auth.expireSession", new Listener());
+ * String response = request(parameters);
+ *
+ *
+ * @param parameters
+ * Key-value pairs of parameters to the request. Refer to the
+ * documentation: one of the parameters must be "method".
+ * @param listener
+ * Callback interface to notify the application when the request
+ * has completed.
+ */
+ public void request(Bundle parameters,
+ RequestListener listener) {
+ request(null, parameters, "GET", listener);
+ }
+
+ /**
+ * Make a request to the Facebook Graph API without any parameters.
+ *
+ * See http://developers.facebook.com/docs/api
+ *
+ * Note that this method is asynchronous and the callback will be invoked
+ * in a background thread; operations that affect the UI will need to be
+ * posted to the UI thread or an appropriate handler.
+ *
+ * @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 listener
+ * Callback interface to notify the application when the request
+ * has completed.
+ */
+ public void request(String graphPath,
+ RequestListener listener) {
+ request(graphPath, new Bundle(), "GET", listener);
+ }
+
+ /**
+ * 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 is asynchronous and the callback will be invoked
+ * in a background thread; operations that affect the UI will need to be
+ * posted to the UI thread or an appropriate handler.
+ *
+ * @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
+ * @param listener
+ * Callback interface to notify the application when the request
+ * has completed.
+ */
+ public void request(String graphPath,
+ Bundle parameters,
+ RequestListener listener) {
+ request(graphPath, parameters, "GET", listener);
+ }
+
+ /**
+ * 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 is asynchronous and the callback will be invoked
+ * in a background thread; operations that affect the UI will need to be
+ * posted to the UI thread or an appropriate handler.
+ *
+ * @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
+ * @param httpMethod
+ * http verb, e.g. "POST", "DELETE"
+ * @param listener
+ * Callback interface to notify the application when the request
+ * has completed.
+ */
+ public void request(final String graphPath,
+ final Bundle parameters,
+ final String httpMethod,
+ final RequestListener listener) {
+ new Thread() {
+ @Override public void run() {
+ try {
+ String resp = fb.request(graphPath, parameters, httpMethod);
+ listener.onComplete(resp);
+ } catch (FileNotFoundException e) {
+ listener.onFileNotFoundException(e);
+ } catch (MalformedURLException e) {
+ listener.onMalformedURLException(e);
+ } catch (IOException e) {
+ listener.onIOException(e);
+ }
+ }
+ }.start();
+ }
+
+
+ /**
+ * Callback interface for API requests.
+ *
+ */
+ public static interface RequestListener {
+
+ /**
+ * Called when a request completes with the given response.
+ *
+ * Executed by a background thread: do not update the UI in this method.
+ */
+ public void onComplete(String response);
+
+ /**
+ * Called when a request has a network or request error.
+ *
+ * Executed by a background thread: do not update the UI in this method.
+ */
+ public void onIOException(IOException e);
+
+ /**
+ * Called when a request fails because the requested resource is
+ * invalid or does not exist.
+ *
+ * Executed by a background thread: do not update the UI in this method.
+ */
+ public void onFileNotFoundException(FileNotFoundException e);
+
+ /**
+ * Called if an invalid graph path is provided (which may result in a
+ * malformed URL).
+ *
+ * Executed by a background thread: do not update the UI in this method.
+ */
+ public void onMalformedURLException(MalformedURLException e);
+
+ /**
+ * Called when the server-side Facebook method fails.
+ *
+ * Executed by a background thread: do not update the UI in this method.
+ */
+ public void onFacebookError(FacebookError e);
+
+ }
+
+}
diff --git a/Android/Facebook/src/com/facebook/android/DialogError.java b/Android/Facebook/src/com/facebook/android/DialogError.java
new file mode 100644
index 00000000..ff50a940
--- /dev/null
+++ b/Android/Facebook/src/com/facebook/android/DialogError.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 Facebook, Inc.
+ *
+ * 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;
+
+/**
+ * Encapsulation of Dialog Error.
+ *
+ * @author ssoneff@facebook.com
+ */
+public class DialogError extends Throwable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The ErrorCode received by the WebView: see
+ * http://developer.android.com/reference/android/webkit/WebViewClient.html
+ */
+ private int mErrorCode;
+
+ /** The URL that the dialog was trying to load */
+ private String mFailingUrl;
+
+ public DialogError(String message, int errorCode, String failingUrl) {
+ super(message);
+ mErrorCode = errorCode;
+ mFailingUrl = failingUrl;
+ }
+
+ int getErrorCode() {
+ return mErrorCode;
+ }
+
+ String getFailingUrl() {
+ return mFailingUrl;
+ }
+
+}
diff --git a/Android/Facebook/src/com/facebook/android/Facebook.java b/Android/Facebook/src/com/facebook/android/Facebook.java
new file mode 100644
index 00000000..20ac15fd
--- /dev/null
+++ b/Android/Facebook/src/com/facebook/android/Facebook.java
@@ -0,0 +1,756 @@
+/*
+ * Copyright 2010 Facebook, Inc.
+ *
+ * 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 java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+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.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.CookieSyncManager;
+
+/**
+ * Main Facebook object for interacting with the Facebook developer API.
+ * Provides methods to log in and log out a user, make requests using the REST
+ * and Graph APIs, and start user interface interactions with the API (such as
+ * pop-ups promoting for credentials, permissions, stream posts, etc.)
+ *
+ * @author Steven Soneff (ssoneff@facebook.com), Jim Brusstar
+ * (jimbru@facebook.com), Yariv Sadan (yariv@facebook.com),
+ * Luke Shepard (lshepard@facebook.com)
+ */
+public class Facebook {
+
+ /* Strings used in the OAuth flow */
+ public static final String REDIRECT_URI = "fbconnect://success";
+ public static final String CANCEL_URI = "fbconnect://cancel";
+ public static final String TOKEN = "access_token";
+ public static final String EXPIRES = "expires_in";
+ public static final String SINGLE_SIGN_ON_DISABLED = "service_disabled";
+
+ public static final int FORCE_DIALOG_AUTH = -1;
+
+ private static final String LOGIN = "login";
+
+ // 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 */
+ protected static String OAUTH_ENDPOINT =
+ "https://www.facebook.com/dialog/oauth";
+ protected static String UI_SERVER =
+ "https://www.facebook.com/connect/uiserver.php";
+ protected static String GRAPH_BASE_URL =
+ "https://graph.facebook.com/";
+ protected static String RESTSERVER_URL =
+ "https://api.facebook.com/restserver.php";
+
+ private String mAccessToken = null;
+ private long mAccessExpires = 0;
+ private String mAppId;
+
+ private Activity mAuthActivity;
+ private String[] mAuthPermissions;
+ private int mAuthActivityCode;
+ private DialogListener mAuthDialogListener;
+
+ public Facebook(String applicationId) {
+ if (applicationId == null) {
+ throw new IllegalArgumentException(
+ "You must specify your application ID when instantiating a Facebook "
+ + "object. See README for details.");
+ }
+ mAppId = applicationId;
+ }
+
+ /**
+ * Default authorize method doesn't require any permissions.
+ */
+ public void authorize(Activity activity, final DialogListener listener) {
+ authorize(activity, new String[] {}, DEFAULT_AUTH_ACTIVITY_CODE,
+ listener);
+ }
+
+ /**
+ * Overloaded signature. See authorize() below.
+ */
+ public void authorize(Activity activity, String[] permissions,
+ final DialogListener listener) {
+ authorize(activity, permissions, DEFAULT_AUTH_ACTIVITY_CODE, listener);
+ }
+
+ /**
+ * 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 applicationId
+ * The Facebook application identifier e.g. "350685531728"
+ * @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.
+ */
+ public void authorize(Activity activity, String[] permissions,
+ int activityCode, final DialogListener listener) {
+
+ boolean singleSignOnStarted = false;
+
+ mAuthDialogListener = listener;
+
+ // Prefer single sign-on, where available.
+ if (activityCode >= 0) {
+ singleSignOnStarted = startSingleSignOn(activity, mAppId,
+ permissions, activityCode);
+ }
+ // Otherwise fall back to traditional dialog.
+ if (!singleSignOnStarted) {
+
+ startDialogAuth(activity, mAppId, permissions);
+ }
+ }
+
+ /**
+ * Internal method to handle single sign-on backend for authorize().
+ *
+ * @param activity
+ * The Android Activity that will parent the ProxyAuth Activity.
+ * @param applicationId
+ * The Facebook application identifier.
+ * @param permissions
+ * A list of permissions required for this application. If you do
+ * not require any permissions, pass an empty String array.
+ * @param activityCode
+ * Activity code to uniquely identify the result Intent in the
+ * callback.
+ */
+ private boolean startSingleSignOn(Activity activity, String applicationId,
+ String[] permissions, int activityCode) {
+ boolean didSucceed = true;
+ Intent intent = new Intent();
+
+ intent.setClassName("com.facebook.katana",
+ "com.facebook.katana.ProxyAuth");
+ intent.putExtra("client_id", applicationId);
+ if (permissions.length > 0) {
+ intent.putExtra("scope", TextUtils.join(",", permissions));
+ }
+
+ // Verify that the application whose package name is
+ // com.facebook.katana.ProxyAuth
+ // has the expected FB app signature.
+ if (!validateAppSignatureForIntent(activity, intent)) {
+ return false;
+ }
+
+ mAuthActivity = activity;
+ mAuthPermissions = permissions;
+ mAuthActivityCode = activityCode;
+ try {
+ activity.startActivityForResult(intent, activityCode);
+ } catch (ActivityNotFoundException e) {
+ didSucceed = false;
+ }
+
+ return didSucceed;
+ }
+
+ /**
+ * 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 activity
+ * @param intent
+ * @param validSignature
+ * @return true if the app's signature matches the expected signature.
+ */
+ private boolean validateAppSignatureForIntent(Activity activity,
+ Intent intent) {
+
+ ResolveInfo resolveInfo = activity.getPackageManager().resolveActivity(
+ intent, 0);
+ if (resolveInfo == null) {
+ return false;
+ }
+
+ String packageName = resolveInfo.activityInfo.packageName;
+ PackageInfo packageInfo;
+ try {
+ packageInfo = activity.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;
+ }
+
+ /**
+ * Internal method to handle dialog-based authentication backend for
+ * authorize().
+ *
+ * @param activity
+ * The Android Activity that will parent the auth dialog.
+ * @param applicationId
+ * The Facebook application identifier.
+ * @param permissions
+ * A list of permissions required for this application. If you do
+ * not require any permissions, pass an empty String array.
+ */
+ private void startDialogAuth(Activity activity, String applicationId,
+ String[] permissions) {
+ Bundle params = new Bundle();
+ params.putString("client_id", applicationId);
+ if (permissions.length > 0) {
+ params.putString("scope", TextUtils.join(",", permissions));
+ }
+
+ CookieSyncManager.createInstance(activity);
+ dialog(activity, LOGIN, params, new DialogListener() {
+
+ public void onComplete(Bundle values) {
+ // ensure any cookies set by the dialog are saved
+ CookieSyncManager.getInstance().sync();
+ setAccessToken(values.getString(TOKEN));
+ setAccessExpiresIn(values.getString(EXPIRES));
+ if (isSessionValid()) {
+ Log.d("Facebook-authorize", "Login Success! access_token="
+ + getAccessToken() + " expires="
+ + getAccessExpires());
+ mAuthDialogListener.onComplete(values);
+ } else {
+ mAuthDialogListener.onFacebookError(new FacebookError(
+ "Failed to receive access token."));
+ }
+ }
+
+ public void onError(DialogError error) {
+ Log.d("Facebook-authorize", "Login failed: " + error);
+ mAuthDialogListener.onError(error);
+ }
+
+ public void onFacebookError(FacebookError error) {
+ Log.d("Facebook-authorize", "Login failed: " + error);
+ mAuthDialogListener.onFacebookError(error);
+ }
+
+ public void onCancel() {
+ Log.d("Facebook-authorize", "Login canceled");
+ mAuthDialogListener.onCancel();
+ }
+ });
+ }
+
+ /**
+ * IMPORTANT: 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)
+ */
+ public void authorizeCallback(int requestCode, int resultCode, Intent data) {
+ if (requestCode == mAuthActivityCode) {
+
+ // Successfully redirected.
+ if (resultCode == Activity.RESULT_OK) {
+
+ // Check OAuth 2.0/2.10 error code.
+ String error = data.getStringExtra("error");
+ if (error == null) {
+ error = data.getStringExtra("error_type");
+ }
+
+ // A Facebook error occurred.
+ if (error != null) {
+ if (error.equals(SINGLE_SIGN_ON_DISABLED)
+ || error.equals("AndroidAuthKillSwitchException")) {
+ Log.d("Facebook-authorize", "Hosted auth currently "
+ + "disabled. Retrying dialog auth...");
+ startDialogAuth(mAuthActivity, mAppId,
+ mAuthPermissions);
+ } else if (error.equals("access_denied")
+ || error.equals("OAuthAccessDeniedException")) {
+ Log.d("Facebook-authorize", "Login canceled by user.");
+ mAuthDialogListener.onCancel();
+ } else {
+ Log.d("Facebook-authorize", "Login failed: " + error);
+ mAuthDialogListener.onFacebookError(new FacebookError(
+ error));
+ }
+
+ // No errors.
+ } else {
+ setAccessToken(data.getStringExtra(TOKEN));
+ setAccessExpiresIn(data.getStringExtra(EXPIRES));
+ if (isSessionValid()) {
+ Log.d("Facebook-authorize",
+ "Login Success! access_token="
+ + getAccessToken() + " expires="
+ + getAccessExpires());
+ mAuthDialogListener.onComplete(data.getExtras());
+ } else {
+ mAuthDialogListener.onFacebookError(new FacebookError(
+ "Failed to receive access token."));
+ }
+ }
+
+ // An error occurred before we could be redirected.
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+
+ // An Android error occured.
+ if (data != null) {
+ Log.d("Facebook-authorize",
+ "Login failed: " + data.getStringExtra("error"));
+ mAuthDialogListener.onError(new DialogError(data
+ .getStringExtra("error"), data.getIntExtra(
+ "error_code", -1), data
+ .getStringExtra("failing_url")));
+
+ // User pressed the 'back' button.
+ } else {
+ Log.d("Facebook-authorize", "Login canceled by user.");
+ mAuthDialogListener.onCancel();
+ }
+ }
+ }
+ }
+
+ /**
+ * Invalidate the current user session by removing the access token in
+ * memory, clearing the browser cookie, and calling auth.expireSession
+ * through the API.
+ *
+ * Note that this method blocks waiting for a network response, so do not
+ * call it in a UI thread.
+ *
+ * @param context
+ * The Android context in which the logout should be called: it
+ * should be the same context in which the login occurred in
+ * order to clear any stored cookies
+ * @throws IOException
+ * @throws MalformedURLException
+ * @return JSON string representation of the auth.expireSession response
+ * ("true" if successful)
+ */
+ public String logout(Context context) throws MalformedURLException,
+ IOException {
+ Util.clearCookies(context);
+ Bundle b = new Bundle();
+ b.putString("method", "auth.expireSession");
+ String response = request(b);
+ setAccessToken(null);
+ setAccessExpires(0);
+ return response;
+ }
+
+ /**
+ * Make a request to Facebook's old (pre-graph) API with the given
+ * parameters. One of the parameter keys must be "method" and its value
+ * should be a valid REST server API method.
+ *
+ * See http://developers.facebook.com/docs/reference/rest/
+ *
+ * Note that this method blocks waiting for a network response, so do not
+ * call it in a UI thread.
+ *
+ * Example:
+ * Bundle parameters = new Bundle();
+ * parameters.putString("method", "auth.expireSession");
+ * String response = request(parameters);
+ *
+ *
+ * @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
+ */
+ 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 request(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.
+ *
+ * @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
+ */
+ public String request(String graphPath) throws MalformedURLException,
+ IOException {
+ return request(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.
+ *
+ * @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
+ */
+ public String request(String graphPath, Bundle parameters)
+ throws MalformedURLException, IOException {
+ return request(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.
+ *
+ * @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
+ * @param httpMethod
+ * http verb, e.g. "GET", "POST", "DELETE"
+ * @throws IOException
+ * @throws MalformedURLException
+ * @return JSON string representation of the response
+ */
+ public String request(String graphPath, Bundle parameters, String httpMethod)
+ throws FileNotFoundException, MalformedURLException, IOException {
+ parameters.putString("format", "json");
+ if (isSessionValid()) {
+ parameters.putString(TOKEN, getAccessToken());
+ }
+ String url = graphPath != null ? GRAPH_BASE_URL + graphPath
+ : RESTSERVER_URL;
+ return Util.openUrl(url, httpMethod, parameters);
+ }
+
+ /**
+ * 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).
+ *
+ * @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.
+ */
+ 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).
+ *
+ * @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 parameters
+ * key-value string parameters
+ * @param listener
+ * Callback interface to notify the application when the dialog
+ * has completed.
+ */
+ public void dialog(Context context, String action, Bundle parameters,
+ final DialogListener listener) {
+ String endpoint;
+ if (action.equals(LOGIN)) {
+ endpoint = OAUTH_ENDPOINT;
+ parameters.putString("type", "user_agent");
+ parameters.putString("redirect_uri", REDIRECT_URI);
+ } else {
+ endpoint = UI_SERVER;
+ parameters.putString("method", action);
+ parameters.putString("next", REDIRECT_URI);
+ }
+ parameters.putString("display", "touch");
+ if (isSessionValid()) {
+ parameters.putString(TOKEN, getAccessToken());
+ }
+ String url = endpoint + "?" + Util.encodeUrl(parameters);
+
+ if (context.checkCallingOrSelfPermission(Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) {
+ Util.showAlert(context, "Error",
+ "Application requires permission to access the Internet");
+ } else {
+ System.out.println("FbDialog url: "+url);
+
+ FbDialog test = new FbDialog(context, url, listener);
+ System.out.println("Constructed");
+
+ test.show();
+ }
+ }
+
+ /**
+ * @return boolean - whether this object has an non-expired session token
+ */
+ public boolean isSessionValid() {
+ return (getAccessToken() != null)
+ && ((getAccessExpires() == 0) || (System.currentTimeMillis() < getAccessExpires()));
+ }
+
+ /**
+ * Retrieve the OAuth 2.0 access token for API access: treat with care.
+ * Returns null if no session exists.
+ *
+ * @return String - access token
+ */
+ public String getAccessToken() {
+ return mAccessToken;
+ }
+
+ /**
+ * Retrieve the current session's expiration time (in milliseconds since
+ * Unix epoch), or 0 if the session doesn't expire or doesn't exist.
+ *
+ * @return long - session expiration time
+ */
+ public long getAccessExpires() {
+ return mAccessExpires;
+ }
+
+ /**
+ * Set the OAuth 2.0 access token for API access.
+ *
+ * @param token
+ * - access token
+ */
+ public void setAccessToken(String token) {
+ mAccessToken = token;
+ }
+
+ /**
+ * Set the current session's expiration time (in milliseconds since Unix
+ * epoch), or 0 if the session doesn't expire.
+ *
+ * @param time
+ * - timestamp in milliseconds
+ */
+ public void setAccessExpires(long time) {
+ mAccessExpires = time;
+ }
+
+ /**
+ * Set the current session's duration (in seconds since Unix epoch).
+ *
+ * @param expiresIn
+ * - duration in seconds
+ */
+ public void setAccessExpiresIn(String expiresIn) {
+ if (expiresIn != null && !expiresIn.equals("0")) {
+ setAccessExpires(System.currentTimeMillis()
+ + Integer.parseInt(expiresIn) * 1000);
+ }
+ }
+
+ public String getAppId() {
+ return mAppId;
+ }
+
+ public void setAppId(String appId) {
+ mAppId = appId;
+ }
+
+ /**
+ * Callback interface for dialog requests.
+ *
+ */
+ public static interface DialogListener {
+
+ /**
+ * Called when a dialog completes.
+ *
+ * Executed by the thread that initiated the dialog.
+ *
+ * @param values
+ * Key-value string pairs extracted from the response.
+ */
+ public void onComplete(Bundle values);
+
+ /**
+ * Called when a Facebook responds to a dialog with an error.
+ *
+ * Executed by the thread that initiated the dialog.
+ *
+ */
+ public void onFacebookError(FacebookError e);
+
+ /**
+ * Called when a dialog has an error.
+ *
+ * Executed by the thread that initiated the dialog.
+ *
+ */
+ public void onError(DialogError e);
+
+ /**
+ * Called when a dialog is canceled by the user.
+ *
+ * Executed by the thread that initiated the dialog.
+ *
+ */
+ public void onCancel();
+
+ }
+
+ public static final String FB_APP_SIGNATURE =
+ "30820268308201d102044a9c4610300d06092a864886f70d0101040500307a310"
+ + "b3009060355040613025553310b30090603550408130243413112301006035504"
+ + "07130950616c6f20416c746f31183016060355040a130f46616365626f6f6b204"
+ + "d6f62696c653111300f060355040b130846616365626f6f6b311d301b06035504"
+ + "03131446616365626f6f6b20436f72706f726174696f6e3020170d30393038333"
+ + "13231353231365a180f32303530303932353231353231365a307a310b30090603"
+ + "55040613025553310b30090603550408130243413112301006035504071309506"
+ + "16c6f20416c746f31183016060355040a130f46616365626f6f6b204d6f62696c"
+ + "653111300f060355040b130846616365626f6f6b311d301b06035504031314466"
+ + "16365626f6f6b20436f72706f726174696f6e30819f300d06092a864886f70d01"
+ + "0101050003818d0030818902818100c207d51df8eb8c97d93ba0c8c1002c928fa"
+ + "b00dc1b42fca5e66e99cc3023ed2d214d822bc59e8e35ddcf5f44c7ae8ade50d7"
+ + "e0c434f500e6c131f4a2834f987fc46406115de2018ebbb0d5a3c261bd97581cc"
+ + "fef76afc7135a6d59e8855ecd7eacc8f8737e794c60a761c536b72b11fac8e603"
+ + "f5da1a2d54aa103b8a13c0dbc10203010001300d06092a864886f70d010104050"
+ + "0038181005ee9be8bcbb250648d3b741290a82a1c9dc2e76a0af2f2228f1d9f9c"
+ + "4007529c446a70175c5a900d5141812866db46be6559e2141616483998211f4a6"
+ + "73149fb2232a10d247663b26a9031e15f84bc1c74d141ff98a02d76f85b2c8ab2"
+ + "571b6469b232d8e768a7f7ca04f7abe4a775615916c07940656b58717457b42bd"
+ + "928a2";
+
+}
diff --git a/Android/Facebook/src/com/facebook/android/FacebookError.java b/Android/Facebook/src/com/facebook/android/FacebookError.java
new file mode 100644
index 00000000..c98bed94
--- /dev/null
+++ b/Android/Facebook/src/com/facebook/android/FacebookError.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010 Facebook, Inc.
+ *
+ * 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;
+
+/**
+ * Encapsulation of a Facebook Error: a Facebook request that could not be
+ * fulfilled.
+ *
+ * @author ssoneff@facebook.com
+ */
+public class FacebookError extends Throwable {
+
+ private static final long serialVersionUID = 1L;
+
+ private int mErrorCode = 0;
+ private String mErrorType;
+
+ public FacebookError(String message) {
+ super(message);
+ }
+
+ public FacebookError(String message, String type, int code) {
+ super(message);
+ mErrorType = type;
+ mErrorCode = code;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ public String getErrorType() {
+ return mErrorType;
+ }
+}
diff --git a/Android/Facebook/src/com/facebook/android/FbDialog.java b/Android/Facebook/src/com/facebook/android/FbDialog.java
new file mode 100644
index 00000000..908b10f5
--- /dev/null
+++ b/Android/Facebook/src/com/facebook/android/FbDialog.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2010 Facebook, Inc.
+ *
+ * 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.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Display;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.facebook.android.Facebook.DialogListener;
+
+public class FbDialog extends Dialog {
+
+ static final int FB_BLUE = 0xFF6D84B4;
+ static final float[] DIMENSIONS_LANDSCAPE = {460, 260};
+ static final float[] DIMENSIONS_PORTRAIT = {280, 420};
+ static final FrameLayout.LayoutParams FILL =
+ new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT);
+ static final int MARGIN = 4;
+ static final int PADDING = 2;
+ static final String DISPLAY_STRING = "touch";
+ static final String FB_ICON = "icon.png";
+
+ private String mUrl;
+ private DialogListener mListener;
+ private ProgressDialog mSpinner;
+ private WebView mWebView;
+ private LinearLayout mContent;
+ private TextView mTitle;
+
+ public FbDialog(Context context, String url, DialogListener listener) {
+ super(context);
+ Log.d("FbDialog","construct");
+
+ mUrl = url;
+ mListener = listener;
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d("FbDialog","onCreate");
+
+ mSpinner = new ProgressDialog(getContext());
+ mSpinner.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ mSpinner.setMessage("Loading...");
+
+ mContent = new LinearLayout(getContext());
+ mContent.setOrientation(LinearLayout.VERTICAL);
+ setUpTitle();
+ setUpWebView();
+ Display display = getWindow().getWindowManager().getDefaultDisplay();
+ final float scale = getContext().getResources().getDisplayMetrics().density;
+ float[] dimensions = display.getWidth() < display.getHeight() ?
+ DIMENSIONS_PORTRAIT : DIMENSIONS_LANDSCAPE;
+ addContentView(mContent, new FrameLayout.LayoutParams(
+ (int) (dimensions[0] * scale + 0.5f),
+ (int) (dimensions[1] * scale + 0.5f)));
+ }
+
+ private void setUpTitle() {
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+/* Drawable icon = getContext().getResources().getDrawable(
+ R.drawable.facebook_icon); */
+ mTitle = new TextView(getContext());
+ mTitle.setText("Facebook");
+ mTitle.setTextColor(Color.WHITE);
+ mTitle.setTypeface(Typeface.DEFAULT_BOLD);
+ mTitle.setBackgroundColor(FB_BLUE);
+ mTitle.setPadding(MARGIN + PADDING, MARGIN, MARGIN, MARGIN);
+ mTitle.setCompoundDrawablePadding(MARGIN + PADDING);
+/* mTitle.setCompoundDrawablesWithIntrinsicBounds(
+ icon, null, null, null); */
+ mContent.addView(mTitle);
+ }
+
+ private void setUpWebView() {
+
+ mWebView = new WebView(getContext());
+ mWebView.setVerticalScrollBarEnabled(false);
+ mWebView.setHorizontalScrollBarEnabled(false);
+ mWebView.setWebViewClient(new FbDialog.FbWebViewClient());
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.loadUrl(mUrl);
+ mWebView.setLayoutParams(FILL);
+ mContent.addView(mWebView);
+ }
+
+ private class FbWebViewClient extends WebViewClient {
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ Log.d("Facebook-WebView", "Redirect URL: " + url);
+ if (url.startsWith(Facebook.REDIRECT_URI)) {
+ Bundle values = Util.parseUrl(url);
+
+ String error = values.getString("error");
+ if (error == null) {
+ error = values.getString("error_type");
+ }
+
+ if (error == null) {
+ mListener.onComplete(values);
+ } else if (error.equals("access_denied") ||
+ error.equals("OAuthAccessDeniedException")) {
+ mListener.onCancel();
+ } else {
+ mListener.onFacebookError(new FacebookError(error));
+ }
+
+ FbDialog.this.dismiss();
+ return true;
+ } else if (url.startsWith(Facebook.CANCEL_URI)) {
+ mListener.onCancel();
+ FbDialog.this.dismiss();
+ return true;
+ } else if (url.contains(DISPLAY_STRING)) {
+ return false;
+ }
+ // launch non-dialog URLs in a full browser
+ getContext().startActivity(
+ new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+ return true;
+ }
+
+ @Override
+ public void onReceivedError(WebView view, int errorCode,
+ String description, String failingUrl) {
+ super.onReceivedError(view, errorCode, description, failingUrl);
+ mListener.onError(
+ new DialogError(description, errorCode, failingUrl));
+ FbDialog.this.dismiss();
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ Log.d("Facebook-WebView", "Webview loading URL: " + url);
+ super.onPageStarted(view, url, favicon);
+ mSpinner.show();
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ String title = mWebView.getTitle();
+ if (title != null && title.length() > 0) {
+ mTitle.setText(title);
+ }
+ mSpinner.dismiss();
+ }
+
+ }
+}
diff --git a/Android/Facebook/src/com/facebook/android/Util.java b/Android/Facebook/src/com/facebook/android/Util.java
new file mode 100644
index 00000000..bb90a1d6
--- /dev/null
+++ b/Android/Facebook/src/com/facebook/android/Util.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2010 Facebook, Inc.
+ *
+ * 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 java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
+
+
+/**
+ * Utility class supporting the Facebook Object.
+ *
+ * @author ssoneff@facebook.com
+ *
+ */
+public final class Util {
+
+ /**
+ * Generate the multi-part post body providing the parameters and boundary
+ * string
+ *
+ * @param parameters the parameters need to be posted
+ * @param boundary the random string as boundary
+ * @return a string of the post body
+ */
+ public static String encodePostBody(Bundle parameters, String boundary) {
+ if (parameters == null) return "";
+ StringBuilder sb = new StringBuilder();
+
+ for (String key : parameters.keySet()) {
+ if (parameters.getByteArray(key) != null) {
+ continue;
+ }
+
+ sb.append("Content-Disposition: form-data; name=\"" + key +
+ "\"\r\n\r\n" + parameters.getString(key));
+ sb.append("\r\n" + "--" + boundary + "\r\n");
+ }
+
+ return sb.toString();
+ }
+
+ public static String encodeUrl(Bundle parameters) {
+ if (parameters == null) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (String key : parameters.keySet()) {
+ if (first) first = false; else sb.append("&");
+ sb.append(URLEncoder.encode(key) + "=" +
+ URLEncoder.encode(parameters.getString(key)));
+ }
+ return sb.toString();
+ }
+
+ public static Bundle decodeUrl(String s) {
+ Bundle params = new Bundle();
+ if (s != null) {
+ String array[] = s.split("&");
+ for (String parameter : array) {
+ String v[] = parameter.split("=");
+ params.putString(URLDecoder.decode(v[0]),
+ URLDecoder.decode(v[1]));
+ }
+ }
+ return params;
+ }
+
+ /**
+ * Parse a URL query and fragment parameters into a key-value bundle.
+ *
+ * @param url the URL to parse
+ * @return a dictionary bundle of keys and values
+ */
+ public static Bundle parseUrl(String url) {
+ // hack to prevent MalformedURLException
+ url = url.replace("fbconnect", "http");
+ try {
+ URL u = new URL(url);
+ Bundle b = decodeUrl(u.getQuery());
+ b.putAll(decodeUrl(u.getRef()));
+ return b;
+ } catch (MalformedURLException e) {
+ return new Bundle();
+ }
+ }
+
+
+ /**
+ * Connect to an HTTP URL and return the response as a string.
+ *
+ * Note that the HTTP method override is used on non-GET requests. (i.e.
+ * requests are made as "POST" with method specified in the body).
+ *
+ * @param url - the resource to open: must be a welformed URL
+ * @param method - the HTTP method to use ("GET", "POST", etc.)
+ * @param params - the query parameter for the URL (e.g. access_token=foo)
+ * @return the URL contents as a String
+ * @throws MalformedURLException - if the URL format is invalid
+ * @throws IOException - if a network problem occurs
+ */
+ public static String openUrl(String url, String method, Bundle params)
+ throws MalformedURLException, IOException {
+ // random string as boundary for multi-part http post
+ String strBoundary = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f";
+ String endLine = "\r\n";
+
+ OutputStream os;
+
+ if (method.equals("GET")) {
+ url = url + "?" + encodeUrl(params);
+ }
+ Log.d("Facebook-Util", method + " URL: " + url);
+ HttpURLConnection conn =
+ (HttpURLConnection) new URL(url).openConnection();
+ conn.setRequestProperty("User-Agent", System.getProperties().
+ getProperty("http.agent") + " FacebookAndroidSDK");
+ if (!method.equals("GET")) {
+ Bundle dataparams = new Bundle();
+ for (String key : params.keySet()) {
+ if (params.getByteArray(key) != null) {
+ dataparams.putByteArray(key, params.getByteArray(key));
+ }
+ }
+
+ // use method override
+ if (!params.containsKey("method")) {
+ params.putString("method", method);
+ }
+
+ if (params.containsKey("access_token")) {
+ String decoded_token = URLDecoder.decode(params.getString("access_token"));
+ params.putString("access_token", decoded_token);
+ }
+
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+strBoundary);
+ conn.setDoOutput(true);
+ conn.setDoInput(true);
+ conn.setRequestProperty("Connection", "Keep-Alive");
+ conn.connect();
+ os = new BufferedOutputStream(conn.getOutputStream());
+
+ os.write(("--" + strBoundary +endLine).getBytes());
+ os.write((encodePostBody(params, strBoundary)).getBytes());
+ os.write((endLine + "--" + strBoundary + endLine).getBytes());
+
+ if (!dataparams.isEmpty()) {
+
+ for (String key: dataparams.keySet()){
+ os.write(("Content-Disposition: form-data; filename=\"" + key + "\"" + endLine).getBytes());
+ os.write(("Content-Type: content/unknown" + endLine + endLine).getBytes());
+ os.write(dataparams.getByteArray(key));
+ os.write((endLine + "--" + strBoundary + endLine).getBytes());
+
+ }
+ }
+ os.flush();
+ }
+
+ String response = "";
+ try {
+ response = read(conn.getInputStream());
+ } catch (FileNotFoundException e) {
+ // Error Stream contains JSON that we can parse to a FB error
+ response = read(conn.getErrorStream());
+ }
+ return response;
+ }
+
+ private static String read(InputStream in) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ BufferedReader r = new BufferedReader(new InputStreamReader(in), 1000);
+ for (String line = r.readLine(); line != null; line = r.readLine()) {
+ sb.append(line);
+ }
+ in.close();
+ return sb.toString();
+ }
+
+ public static void clearCookies(Context context) {
+ // Edge case: an illegal state exception is thrown if an instance of
+ // CookieSyncManager has not be created. CookieSyncManager is normally
+ // created by a WebKit view, but this might happen if you start the
+ // app, restore saved state, and click logout before running a UI
+ // dialog in a WebView -- in which case the app crashes
+ @SuppressWarnings("unused")
+ CookieSyncManager cookieSyncMngr =
+ CookieSyncManager.createInstance(context);
+ CookieManager cookieManager = CookieManager.getInstance();
+ cookieManager.removeAllCookie();
+ }
+
+ /**
+ * Parse a server response into a JSON Object. This is a basic
+ * implementation using org.json.JSONObject representation. More
+ * sophisticated applications may wish to do their own parsing.
+ *
+ * The parsed JSON is checked for a variety of error fields and
+ * a FacebookException is thrown if an error condition is set,
+ * populated with the error message and error type or code if
+ * available.
+ *
+ * @param response - string representation of the response
+ * @return the response as a JSON Object
+ * @throws JSONException - if the response is not valid JSON
+ * @throws FacebookError - if an error condition is set
+ */
+ public static JSONObject parseJson(String response)
+ throws JSONException, FacebookError {
+ // Edge case: when sending a POST request to /[post_id]/likes
+ // the return value is 'true' or 'false'. Unfortunately
+ // these values cause the JSONObject constructor to throw
+ // an exception.
+ if (response.equals("false")) {
+ throw new FacebookError("request failed");
+ }
+ if (response.equals("true")) {
+ response = "{value : true}";
+ }
+ JSONObject json = new JSONObject(response);
+
+ // errors set by the server are not consistent
+ // they depend on the method and endpoint
+ if (json.has("error")) {
+ JSONObject error = json.getJSONObject("error");
+ throw new FacebookError(
+ error.getString("message"), error.getString("type"), 0);
+ }
+ if (json.has("error_code") && json.has("error_msg")) {
+ throw new FacebookError(json.getString("error_msg"), "",
+ Integer.parseInt(json.getString("error_code")));
+ }
+ if (json.has("error_code")) {
+ throw new FacebookError("request failed", "",
+ Integer.parseInt(json.getString("error_code")));
+ }
+ if (json.has("error_msg")) {
+ throw new FacebookError(json.getString("error_msg"));
+ }
+ if (json.has("error_reason")) {
+ throw new FacebookError(json.getString("error_reason"));
+ }
+ return json;
+ }
+
+ /**
+ * Display a simple alert dialog with the given text and title.
+ *
+ * @param context
+ * Android context in which the dialog should be displayed
+ * @param title
+ * Alert dialog title
+ * @param text
+ * Alert dialog message
+ */
+ public static void showAlert(Context context, String title, String text) {
+ Builder alertBuilder = new Builder(context);
+ alertBuilder.setTitle(title);
+ alertBuilder.setMessage(text);
+ alertBuilder.create().show();
+ }
+
+}
diff --git a/Android/Facebook/src/com/phonegap/plugins/facebook/FacebookAuth.java b/Android/Facebook/src/com/phonegap/plugins/facebook/FacebookAuth.java
new file mode 100755
index 00000000..50b847ad
--- /dev/null
+++ b/Android/Facebook/src/com/phonegap/plugins/facebook/FacebookAuth.java
@@ -0,0 +1,118 @@
+/*
+ * PhoneGap is available under *either* the terms of the modified BSD license *or* the
+ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
+ *
+ * Copyright (c) 2005-2010, Nitobi Software Inc.
+ * Copyright (c) 2010, IBM Corporation
+ */
+package com.phonegap.plugins.facebook;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import android.content.Intent;
+import android.content.Context;
+import android.app.Activity;
+
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.phonegap.api.Plugin;
+import com.phonegap.api.PluginResult;
+
+import com.facebook.android.Facebook;
+import com.facebook.android.AsyncFacebookRunner;
+import com.facebook.android.Facebook.DialogListener;
+import com.facebook.android.DialogError;
+import com.facebook.android.FacebookError;
+
+public class FacebookAuth extends Plugin {
+
+ public static final String APP_ID = "175729095772478";
+ private Facebook mFb;
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @param callbackId The callback id used when calling back into JavaScript.
+ * @return A PluginResult object with a status and message.
+ */
+ public PluginResult execute(String action, JSONArray args, String callbackId) {
+ PluginResult.Status status = PluginResult.Status.OK;
+ String result = "";
+ System.out.println("execute: "+ action);
+
+ try {
+ if (action.equals("authorize")) {
+
+ result = this.authorize(args.getString(0));
+ if (result.length() > 0) {
+ status = PluginResult.Status.ERROR;
+ }
+ System.out.println("result: "+ result);
+
+ }
+
+ return new PluginResult(status, result);
+ } catch (JSONException e) {
+ System.out.println("exception: "+ action);
+
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+ }
+ }
+
+ /**
+ * Identifies if action to be executed returns a value and should be run synchronously.
+ *
+ * @param action The action to execute
+ * @return T=returns value
+ */
+ public boolean isSynch(String action) {
+ return false;
+ }
+
+ /**
+ * Called by AccelBroker when listener is to be shut down.
+ * Stop listener.
+ */
+ public void onDestroy() {
+ }
+
+ //--------------------------------------------------------------------------
+ // LOCAL METHODS
+ //--------------------------------------------------------------------------
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url The url to load.
+ * @param usePhoneGap Load url in PhoneGap webview
+ * @return "" if ok, or error message.
+ */
+ public String authorize(String url) {
+
+ this.mFb = new Facebook(APP_ID);
+ this.mFb.authorize((Activity) this.ctx, new AuthorizeListener());
+ return "string";
+ }
+
+ class AuthorizeListener implements DialogListener {
+ public void onComplete(Bundle values) {
+ // Handle a successful login
+ }
+ public void onFacebookError(FacebookError e) {
+ e.printStackTrace();
+ }
+
+ public void onError(DialogError e) {
+ e.printStackTrace();
+ }
+
+ public void onCancel() {
+ }
+ }
+
+
+}
diff --git a/Android/Facebook/www/facebook.js b/Android/Facebook/www/facebook.js
new file mode 100755
index 00000000..426ec4ea
--- /dev/null
+++ b/Android/Facebook/www/facebook.js
@@ -0,0 +1,34 @@
+/*
+ * PhoneGap is available under *either* the terms of the modified BSD license *or* the
+ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
+ *
+ * Copyright (c) 2005-2010, Nitobi Software Inc.
+ * Copyright (c) 2010, IBM Corporation
+ */
+
+
+(function(){
+ /**
+ * Constructor
+ */
+ function Facebook() {}
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url The url to load
+ * @param usePhoneGap Load url in PhoneGap webview [optional]
+ */
+ Facebook.prototype.authorize = function(url) {
+ PhoneGap.exec(null, null, "FacebookAuth", "authorize", [url]);
+ };
+
+ /**
+ * Load ChildBrowser
+ */
+ PhoneGap.addConstructor(function() {
+ PhoneGap.addPlugin("facebook", new Facebook());
+ PluginManager.addService("FacebookAuth", "com.phonegap.plugins.facebook.FacebookAuth");
+ });
+
+})();
\ No newline at end of file