(loggingBehaviors));
+ }
+ }
+
+ /**
+ * Certain logging behaviors are available for debugging beyond those that should be
+ * enabled in production.
+ *
+ * Enables a particular extended logging in the SDK.
+ *
+ * @param behavior
+ * The LoggingBehavior to enable
+ */
+ public static void addLoggingBehavior(LoggingBehavior behavior) {
+ synchronized (loggingBehaviors) {
+ loggingBehaviors.add(behavior);
+ updateGraphDebugBehavior();
+ }
+ }
+
+ /**
+ * Certain logging behaviors are available for debugging beyond those that should be
+ * enabled in production.
+ *
+ * Disables a particular extended logging behavior in the SDK.
+ *
+ * @param behavior
+ * The LoggingBehavior to disable
+ */
+ public static void removeLoggingBehavior(LoggingBehavior behavior) {
+ synchronized (loggingBehaviors) {
+ loggingBehaviors.remove(behavior);
+ }
+ }
+
+ /**
+ * Certain logging behaviors are available for debugging beyond those that should be
+ * enabled in production.
+ *
+ * Disables all extended logging behaviors.
+ */
+ public static void clearLoggingBehaviors() {
+ synchronized (loggingBehaviors) {
+ loggingBehaviors.clear();
+ }
+ }
+
+ /**
+ * Certain logging behaviors are available for debugging beyond those that should be
+ * enabled in production.
+ *
+ * Checks if a particular extended logging behavior is enabled.
+ *
+ * @param behavior
+ * The LoggingBehavior to check
+ * @return whether behavior is enabled
+ */
+ public static boolean isLoggingBehaviorEnabled(LoggingBehavior behavior) {
+ synchronized (loggingBehaviors) {
+ return FacebookSdk.isDebugEnabled() && loggingBehaviors.contains(behavior);
+ }
+ }
+
+ /**
+ * Indicates if we are in debug mode.
+ */
+ public static boolean isDebugEnabled() {
+ return isDebugEnabled;
+ }
+
+ /**
+ * Used to enable or disable logging, and other debug features. Defaults to BuildConfig.DEBUG.
+ * @param enabled Debug features (like logging) are enabled if true, disabled if false.
+ */
+ public static void setIsDebugEnabled(boolean enabled) {
+ isDebugEnabled = enabled;
+ }
+
+ /**
+ * Indicates if the SDK should fallback and read the legacy token. This is turned off by default
+ * for performance.
+ * @return if the legacy token upgrade is supported.
+ */
+ public static boolean isLegacyTokenUpgradeSupported() {
+ return isLegacyTokenUpgradeSupported;
+ }
+
+ private static void updateGraphDebugBehavior() {
+ if (loggingBehaviors.contains(LoggingBehavior.GRAPH_API_DEBUG_INFO)
+ && !loggingBehaviors.contains(LoggingBehavior.GRAPH_API_DEBUG_WARNING)) {
+ loggingBehaviors.add(LoggingBehavior.GRAPH_API_DEBUG_WARNING);
+ }
+ }
+
+ /**
+ * Setter for legacy token upgrade.
+ * @param supported True if upgrade should be supported.
+ */
+ public static void setLegacyTokenUpgradeSupported(boolean supported) {
+ isLegacyTokenUpgradeSupported = supported;
+ }
+
+ /**
+ * Returns the Executor used by the SDK for non-AsyncTask background work.
+ *
+ * By default this uses AsyncTask Executor via reflection if the API level is high enough.
+ * Otherwise this creates a new Executor with defaults similar to those used in AsyncTask.
+ *
+ * @return an Executor used by the SDK. This will never be null.
+ */
+ public static Executor getExecutor() {
+ synchronized (LOCK) {
+ if (FacebookSdk.executor == null) {
+ Executor executor = getAsyncTaskExecutor();
+ if (executor == null) {
+ executor = new ThreadPoolExecutor(
+ DEFAULT_CORE_POOL_SIZE, DEFAULT_MAXIMUM_POOL_SIZE, DEFAULT_KEEP_ALIVE,
+ TimeUnit.SECONDS, DEFAULT_WORK_QUEUE, DEFAULT_THREAD_FACTORY);
+ }
+ FacebookSdk.executor = executor;
+ }
+ }
+ return FacebookSdk.executor;
+ }
+
+ /**
+ * Sets the Executor used by the SDK for non-AsyncTask background work.
+ *
+ * @param executor
+ * the Executor to use; must not be null.
+ */
+ public static void setExecutor(Executor executor) {
+ Validate.notNull(executor, "executor");
+ synchronized (LOCK) {
+ FacebookSdk.executor = executor;
+ }
+ }
+
+ /**
+ * Gets the base Facebook domain to use when making Web requests; in production code this will
+ * always be "facebook.com".
+ *
+ * @return the Facebook domain
+ */
+ public static String getFacebookDomain() {
+ return facebookDomain;
+ }
+
+ /**
+ * Sets the base Facebook domain to use when making Web requests. This defaults to
+ * "facebook.com", but may be overridden to, e.g., "beta.facebook.com" to direct requests at a
+ * different domain. This method should never be called from production code.
+ *
+ * @param facebookDomain the base domain to use instead of "facebook.com"
+ */
+ public static void setFacebookDomain(String facebookDomain) {
+ if (!BuildConfig.DEBUG) {
+ Log.w(TAG, "WARNING: Calling setFacebookDomain from non-DEBUG code.");
+ }
+
+ FacebookSdk.facebookDomain = facebookDomain;
+ }
+
+ /**
+ * The getter for the context of the current application.
+ * @return The context of the current application.
+ */
+ public static Context getApplicationContext() {
+ Validate.sdkInitialized();
+ return applicationContext;
+ }
+
+ private static Executor getAsyncTaskExecutor() {
+ Field executorField = null;
+ try {
+ executorField = AsyncTask.class.getField("THREAD_POOL_EXECUTOR");
+ } catch (NoSuchFieldException e) {
+ return null;
+ }
+
+ Object executorObject = null;
+ try {
+ executorObject = executorField.get(null);
+ } catch (IllegalAccessException e) {
+ return null;
+ }
+
+ if (executorObject == null) {
+ return null;
+ }
+
+ if (!(executorObject instanceof Executor)) {
+ return null;
+ }
+
+ return (Executor) executorObject;
+ }
+
+ /**
+ * This method is public in order to be used by app events, please don't use directly.
+ * @param context The application context.
+ * @param applicationId The application id.
+ */
+ public static void publishInstallAsync(final Context context, final String applicationId) {
+ // grab the application context ahead of time, since we will return to the caller
+ // immediately.
+ final Context applicationContext = context.getApplicationContext();
+ FacebookSdk.getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ FacebookSdk.publishInstallAndWaitForResponse(applicationContext, applicationId);
+ }
+ });
+ }
+
+ static GraphResponse publishInstallAndWaitForResponse(
+ final Context context,
+ final String applicationId) {
+ try {
+ if (context == null || applicationId == null) {
+ throw new IllegalArgumentException("Both context and applicationId must be non-null");
+ }
+ AttributionIdentifiers identifiers = AttributionIdentifiers.getAttributionIdentifiers(context);
+ SharedPreferences preferences = context.getSharedPreferences(ATTRIBUTION_PREFERENCES, Context.MODE_PRIVATE);
+ String pingKey = applicationId+"ping";
+ String jsonKey = applicationId+"json";
+ long lastPing = preferences.getLong(pingKey, 0);
+ String lastResponseJSON = preferences.getString(jsonKey, null);
+
+ JSONObject publishParams;
+ try {
+ publishParams = AppEventsLoggerUtility.getJSONObjectForGraphAPICall(
+ AppEventsLoggerUtility.GraphAPIActivityType.MOBILE_INSTALL_EVENT,
+ identifiers,
+ AppEventsLogger.getAnonymousAppDeviceGUID(context),
+ getLimitEventAndDataUsage(context),
+ context);
+ } catch (JSONException e) {
+ throw new FacebookException("An error occurred while publishing install.", e);
+ }
+
+ String publishUrl = String.format(PUBLISH_ACTIVITY_PATH, applicationId);
+ GraphRequest publishRequest = GraphRequest.newPostRequest(null, publishUrl, publishParams, null);
+
+ if (lastPing != 0) {
+ JSONObject graphObject = null;
+ try {
+ if (lastResponseJSON != null) {
+ graphObject = new JSONObject(lastResponseJSON);
+ }
+ }
+ catch (JSONException je) {
+ // return the default graph object if there is any problem reading the data.
+ }
+ if (graphObject == null) {
+ return GraphResponse.createResponsesFromString(
+ "true",
+ null,
+ new GraphRequestBatch(publishRequest)
+ ).get(0);
+ } else {
+ return new GraphResponse(null, null, null, graphObject);
+ }
+
+ } else {
+
+ GraphResponse publishResponse = publishRequest.executeAndWait();
+
+ // denote success since no error threw from the post.
+ SharedPreferences.Editor editor = preferences.edit();
+ lastPing = System.currentTimeMillis();
+ editor.putLong(pingKey, lastPing);
+
+ // if we got an object response back, cache the string of the JSON.
+ if (publishResponse.getJSONObject() != null) {
+ editor.putString(jsonKey, publishResponse.getJSONObject().toString());
+ }
+ editor.apply();
+
+ return publishResponse;
+ }
+ } catch (Exception e) {
+ // if there was an error, fall through to the failure case.
+ Utility.logd("Facebook-publish", e);
+ return new GraphResponse(null, null, new FacebookRequestError(null, e));
+ }
+ }
+
+ /**
+ * Returns the current version of the Facebook SDK for Android as a string.
+ *
+ * @return the current version of the SDK
+ */
+ public static String getSdkVersion() {
+ Validate.sdkInitialized();
+ return FacebookSdkVersion.BUILD;
+ }
+
+ /**
+ * Returns whether data such as those generated through AppEventsLogger and sent to Facebook
+ * should be restricted from being used for purposes other than analytics and conversions, such
+ * as targeting ads to this user. Defaults to false. This value is stored on the device and
+ * persists across app launches.
+ *
+ * @param context Used to read the value.
+ */
+ public static boolean getLimitEventAndDataUsage(Context context) {
+ Validate.sdkInitialized();
+ SharedPreferences preferences = context.getSharedPreferences(
+ AppEventsLogger.APP_EVENT_PREFERENCES, Context.MODE_PRIVATE);
+ return preferences.getBoolean("limitEventUsage", false);
+ }
+
+ /**
+ * Sets whether data such as those generated through AppEventsLogger and sent to Facebook should
+ * be restricted from being used for purposes other than analytics and conversions, such as
+ * targeting ads to this user. Defaults to false. This value is stored on the device and
+ * persists across app launches. Changes to this setting will apply to app events currently
+ * queued to be flushed.
+ *
+ * @param context Used to persist this value across app runs.
+ */
+ public static void setLimitEventAndDataUsage(Context context, boolean limitEventUsage) {
+ context.getSharedPreferences(AppEventsLogger.APP_EVENT_PREFERENCES, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean("limitEventUsage", limitEventUsage)
+ .apply();
+ }
+
+ /**
+ * Gets the threshold used to report progress on requests.
+ */
+ public static long getOnProgressThreshold() {
+ Validate.sdkInitialized();
+ return onProgressThreshold.get();
+ }
+
+ /**
+ * Sets the threshold used to report progress on requests. Note that the value will be read when
+ * the request is started and cannot be changed during a request (or batch) execution.
+ *
+ * @param threshold The number of bytes progressed to force a callback.
+ */
+ public static void setOnProgressThreshold(long threshold) {
+ onProgressThreshold.set(threshold);
+ }
+
+ // Package private for testing only
+ static void loadDefaultsFromMetadata(Context context) {
+ if (context == null) {
+ return;
+ }
+
+ ApplicationInfo ai = null;
+ try {
+ ai = context.getPackageManager().getApplicationInfo(
+ context.getPackageName(), PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+
+ if (ai == null || ai.metaData == null) {
+ return;
+ }
+
+ if (applicationId == null) {
+ Object appId = ai.metaData.get(APPLICATION_ID_PROPERTY);
+ if (appId instanceof String) {
+ applicationId = (String) appId;
+ } else if (appId instanceof Integer) {
+ applicationId = appId.toString();
+ }
+ }
+
+ if (applicationName == null) {
+ applicationName = ai.metaData.getString(APPLICATION_NAME_PROPERTY);
+ }
+
+ if (appClientToken == null) {
+ appClientToken = ai.metaData.getString(CLIENT_TOKEN_PROPERTY);
+ }
+ }
+
+ /**
+ * Internal call please don't use directly.
+ * @param context The application context.
+ * @return The application signature.
+ */
+ public static String getApplicationSignature(Context context) {
+ Validate.sdkInitialized();
+ if (context == null) {
+ return null;
+ }
+ PackageManager packageManager = context.getPackageManager();
+ if (packageManager == null) {
+ return null;
+ }
+
+ String packageName = context.getPackageName();
+ PackageInfo pInfo;
+ try {
+ pInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+
+ Signature[] signatures = pInfo.signatures;
+ if (signatures == null || signatures.length == 0) {
+ return null;
+ }
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+
+ md.update(pInfo.signatures[0].toByteArray());
+ return Base64.encodeToString(md.digest(), Base64.URL_SAFE | Base64.NO_PADDING);
+ }
+
+ /**
+ * Gets the Facebook application ID for the current app. This should only be called after the
+ * SDK has been initialized by calling FacebookSdk.sdkInitialize().
+ *
+ * @return the application ID
+ */
+ public static String getApplicationId() {
+ Validate.sdkInitialized();
+ return applicationId;
+ }
+
+ /**
+ * Sets the Facebook application ID for the current app.
+ * @param applicationId the application ID
+ */
+ public static void setApplicationId(String applicationId) {
+ FacebookSdk.applicationId = applicationId;
+ }
+
+ /**
+ * Gets the Facebook application name of the current app. This should only be called after the
+ * SDK has been initialized by calling FacebookSdk.sdkInitialize().
+ *
+ * @return the application name
+ */
+ public static String getApplicationName() {
+ Validate.sdkInitialized();
+ return applicationName;
+ }
+
+ /**
+ * Sets the Facebook application name for the current app.
+ * @param applicationName the application name
+ */
+ public static void setApplicationName(String applicationName) {
+ FacebookSdk.applicationName = applicationName;
+ }
+
+ /**
+ * Gets the client token for the current app. This will be null unless explicitly set or unless
+ * loadDefaultsFromMetadata has been called.
+ * @return the client token
+ */
+ public static String getClientToken() {
+ Validate.sdkInitialized();
+ return appClientToken;
+ }
+
+ /**
+ * Sets the Facebook client token for the current app.
+ * @param clientToken the client token
+ */
+ public static void setClientToken(String clientToken) {
+ appClientToken = clientToken;
+ }
+
+ /**
+ * Gets the cache directory to use for caching responses, etc. The default will be the value
+ * returned by Context.getCacheDir() when the SDK was initialized, but it can be overridden.
+ *
+ * @return the cache directory
+ */
+ public static File getCacheDir() {
+ Validate.sdkInitialized();
+ return cacheDir;
+ }
+
+ /**
+ * Sets the cache directory to use for caching responses, etc.
+ * @param cacheDir the cache directory
+ */
+ public static void setCacheDir(File cacheDir) {
+ FacebookSdk.cacheDir = cacheDir;
+ }
+
+ /**
+ * Getter for the callback request code offset. The request codes starting at this offset and
+ * the next 100 values are used by the Facebook SDK.
+ *
+ * @return The callback request code offset.
+ */
+ public static int getCallbackRequestCodeOffset() {
+ Validate.sdkInitialized();
+ return callbackRequestCodeOffset;
+ }
+
+ /**
+ * Returns true if the request code is within the range used by Facebook SDK requests. This does
+ * not include request codes that you explicitly set on the dialogs, buttons or LoginManager.
+ * The range of request codes that the SDK uses starts at the callbackRequestCodeOffset and
+ * continues for the next 100 values.
+ *
+ * @param requestCode the request code to check.
+ * @return true if the request code is within the range used by the Facebook SDK.
+ */
+ public static boolean isFacebookRequestCode(int requestCode) {
+ return requestCode >= callbackRequestCodeOffset
+ && requestCode < callbackRequestCodeOffset + MAX_REQUEST_CODE_RANGE;
+ }
+}
diff --git a/platforms/android/FacebookLib/src/com/facebook/FacebookSdkNotInitializedException.java b/platforms/android/FacebookLib/src/com/facebook/FacebookSdkNotInitializedException.java
new file mode 100755
index 000000000..14e0c9176
--- /dev/null
+++ b/platforms/android/FacebookLib/src/com/facebook/FacebookSdkNotInitializedException.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright 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.
+ */
+
+package com.facebook;
+
+/**
+ * An Exception indicating that the Facebook SDK has not been correctly initialized.
+ */
+public class FacebookSdkNotInitializedException extends FacebookException {
+ static final long serialVersionUID = 1;
+
+ /**
+ * Constructs a FacebookSdkNotInitializedException with no additional information.
+ */
+ public FacebookSdkNotInitializedException() {
+ super();
+ }
+
+ /**
+ * Constructs a FacebookSdkNotInitializedException with a message.
+ *
+ * @param message A String to be returned from getMessage.
+ */
+ public FacebookSdkNotInitializedException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a FacebookSdkNotInitializedException with a message and inner error.
+ *
+ * @param message A String to be returned from getMessage.
+ * @param throwable A Throwable to be returned from getCause.
+ */
+ public FacebookSdkNotInitializedException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+
+ /**
+ * Constructs a FacebookSdkNotInitializedException with an inner error.
+ *
+ * @param throwable A Throwable to be returned from getCause.
+ */
+ public FacebookSdkNotInitializedException(Throwable throwable) {
+ super(throwable);
+ }
+}
diff --git a/platforms/android/FacebookLib/src/com/facebook/FacebookSdkVersion.java b/platforms/android/FacebookLib/src/com/facebook/FacebookSdkVersion.java
old mode 100644
new mode 100755
index 82398e7a7..27a041e62
--- a/platforms/android/FacebookLib/src/com/facebook/FacebookSdkVersion.java
+++ b/platforms/android/FacebookLib/src/com/facebook/FacebookSdkVersion.java
@@ -1,21 +1,25 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
final class FacebookSdkVersion {
- public static final String BUILD = "3.21.1";
+ public static final String BUILD = "4.1.2";
}
diff --git a/platforms/android/FacebookLib/src/com/facebook/FacebookServiceException.java b/platforms/android/FacebookLib/src/com/facebook/FacebookServiceException.java
old mode 100644
new mode 100755
index 0ff4f84f8..709b15fe2
--- a/platforms/android/FacebookLib/src/com/facebook/FacebookServiceException.java
+++ b/platforms/android/FacebookLib/src/com/facebook/FacebookServiceException.java
@@ -1,17 +1,21 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
diff --git a/platforms/android/FacebookLib/src/com/facebook/GetTokenClient.java b/platforms/android/FacebookLib/src/com/facebook/GetTokenClient.java
deleted file mode 100644
index c0f1a7015..000000000
--- a/platforms/android/FacebookLib/src/com/facebook/GetTokenClient.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * 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.content.Context;
-import android.os.Bundle;
-import com.facebook.internal.NativeProtocol;
-import com.facebook.internal.PlatformServiceClient;
-
-final class GetTokenClient extends PlatformServiceClient {
-
- GetTokenClient(Context context, String applicationId) {
- super(context, NativeProtocol.MESSAGE_GET_ACCESS_TOKEN_REQUEST, NativeProtocol.MESSAGE_GET_ACCESS_TOKEN_REPLY,
- NativeProtocol.PROTOCOL_VERSION_20121101, applicationId);
- }
-
- @Override
- protected void populateRequestBundle(Bundle data) {
- }
-}
-
diff --git a/platforms/android/FacebookLib/src/com/facebook/GraphRequest.java b/platforms/android/FacebookLib/src/com/facebook/GraphRequest.java
new file mode 100755
index 000000000..302018b3c
--- /dev/null
+++ b/platforms/android/FacebookLib/src/com/facebook/GraphRequest.java
@@ -0,0 +1,2158 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright 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.
+ */
+
+package com.facebook;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.location.Location;
+import android.net.Uri;
+import android.os.*;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.facebook.internal.*;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ *
+ * A single request to be sent to the Facebook Platform through the Graph API. The Request class
+ * provides functionality relating to serializing and deserializing requests and responses, making
+ * calls in batches (with a single round-trip to the service) and making calls asynchronously.
+ *
+ *
+ * The particular service endpoint that a request targets is determined by a graph path (see the
+ * {@link #setGraphPath(String) setGraphPath} method).
+ *
+ *
+ * A Request can be executed either anonymously or representing an authenticated user. In the former
+ * case, no AccessToken needs to be specified, while in the latter, an AccessToken must be provided.
+ * If requests are executed in a batch, a Facebook application ID must be associated with the batch,
+ * either by setting the application ID in the AndroidManifest.xml or via FacebookSdk or by calling
+ * the {@link #setDefaultBatchApplicationId(String) setDefaultBatchApplicationId} method.
+ *
+ *
+ * After completion of a request, the AccessToken, if not null and taken from AccessTokenManager,
+ * will be checked to determine if its Facebook access token needs to be extended; if so, a request
+ * to extend it will be issued in the background.
+ *
+ */
+public class GraphRequest {
+ /**
+ * The maximum number of requests that can be submitted in a single batch. This limit is
+ * enforced on the service side by the Facebook platform, not by the Request class.
+ */
+ public static final int MAXIMUM_BATCH_SIZE = 50;
+
+ public static final String TAG = GraphRequest.class.getSimpleName();
+
+ private static final String VIDEOS_SUFFIX = "/videos";
+ private static final String ME = "me";
+ private static final String MY_FRIENDS = "me/friends";
+ private static final String SEARCH = "search";
+ private static final String USER_AGENT_BASE = "FBAndroidSDK";
+ private static final String USER_AGENT_HEADER = "User-Agent";
+ private static final String CONTENT_TYPE_HEADER = "Content-Type";
+ private static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language";
+ private static final String CONTENT_ENCODING_HEADER = "Content-Encoding";
+
+ // Parameter names/values
+ private static final String FORMAT_PARAM = "format";
+ private static final String FORMAT_JSON = "json";
+ private static final String SDK_PARAM = "sdk";
+ private static final String SDK_ANDROID = "android";
+ private static final String ACCESS_TOKEN_PARAM = "access_token";
+ private static final String BATCH_ENTRY_NAME_PARAM = "name";
+ private static final String BATCH_ENTRY_OMIT_RESPONSE_ON_SUCCESS_PARAM =
+ "omit_response_on_success";
+ private static final String BATCH_ENTRY_DEPENDS_ON_PARAM = "depends_on";
+ private static final String BATCH_APP_ID_PARAM = "batch_app_id";
+ private static final String BATCH_RELATIVE_URL_PARAM = "relative_url";
+ private static final String BATCH_BODY_PARAM = "body";
+ private static final String BATCH_METHOD_PARAM = "method";
+ private static final String BATCH_PARAM = "batch";
+ private static final String ATTACHMENT_FILENAME_PREFIX = "file";
+ private static final String ATTACHED_FILES_PARAM = "attached_files";
+ private static final String ISO_8601_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssZ";
+ private static final String DEBUG_PARAM = "debug";
+ private static final String DEBUG_SEVERITY_INFO = "info";
+ private static final String DEBUG_SEVERITY_WARNING = "warning";
+ private static final String DEBUG_KEY = "__debug__";
+ private static final String DEBUG_MESSAGES_KEY = "messages";
+ private static final String DEBUG_MESSAGE_KEY = "message";
+ private static final String DEBUG_MESSAGE_TYPE_KEY = "type";
+ private static final String DEBUG_MESSAGE_LINK_KEY = "link";
+
+ private static final String MIME_BOUNDARY = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f";
+
+ private static String defaultBatchApplicationId;
+
+ // Group 1 in the pattern is the path without the version info
+ private static Pattern versionPattern = Pattern.compile("^/?v\\d+\\.\\d+/(.*)");
+
+ private AccessToken accessToken;
+ private HttpMethod httpMethod;
+ private String graphPath;
+ private JSONObject graphObject;
+ private String batchEntryName;
+ private String batchEntryDependsOn;
+ private boolean batchEntryOmitResultOnSuccess = true;
+ private Bundle parameters;
+ private Callback callback;
+ private String overriddenURL;
+ private Object tag;
+ private String version;
+ private boolean skipClientToken = false;
+
+ /**
+ * Constructs a request without an access token, graph path, or any other parameters.
+ */
+ public GraphRequest() {
+ this(null, null, null, null, null);
+ }
+
+ /**
+ * Constructs a request with an access token to retrieve a particular graph path.
+ * An access token need not be provided, in which case the request is sent without an access
+ * token and thus is not executed in the context of any particular user. Only certain graph
+ * requests can be expected to succeed in this case.
+ *
+ * @param accessToken the access token to use, or null
+ * @param graphPath the graph path to retrieve
+ */
+ public GraphRequest(AccessToken accessToken, String graphPath) {
+ this(accessToken, graphPath, null, null, null);
+ }
+
+ /**
+ * Constructs a request with a specific AccessToken, graph path, parameters, and HTTP method. An
+ * access token need not be provided, in which case the request is sent without an access token
+ * and thus is not executed in the context of any particular user. Only certain graph requests
+ * can be expected to succeed in this case.
+ *
+ * Depending on the httpMethod parameter, the object at the graph path may be retrieved,
+ * created, or deleted.
+ *
+ * @param accessToken the access token to use, or null
+ * @param graphPath the graph path to retrieve, create, or delete
+ * @param parameters additional parameters to pass along with the Graph API request; parameters
+ * must be Strings, Numbers, Bitmaps, Dates, or Byte arrays.
+ * @param httpMethod the {@link HttpMethod} to use for the request, or null for default
+ * (HttpMethod.GET)
+ */
+ public GraphRequest(
+ AccessToken accessToken,
+ String graphPath,
+ Bundle parameters,
+ HttpMethod httpMethod) {
+ this(accessToken, graphPath, parameters, httpMethod, null);
+ }
+
+ /**
+ * Constructs a request with a specific access token, graph path, parameters, and HTTP method.
+ * An access token need not be provided, in which case the request is sent without an access
+ * token and thus is not executed in the context of any particular user. Only certain graph\
+ * requests can be expected to succeed in this case.
+ *
+ * Depending on the httpMethod parameter, the object at the graph path may be retrieved,
+ * created, or deleted.
+ *
+ * @param accessToken the access token to use, or null
+ * @param graphPath the graph path to retrieve, create, or delete
+ * @param parameters additional parameters to pass along with the Graph API request; parameters
+ * must be Strings, Numbers, Bitmaps, Dates, or Byte arrays.
+ * @param httpMethod the {@link HttpMethod} to use for the request, or null for default
+ * (HttpMethod.GET)
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions
+ */
+ public GraphRequest(
+ AccessToken accessToken,
+ String graphPath,
+ Bundle parameters,
+ HttpMethod httpMethod,
+ Callback callback) {
+ this(accessToken, graphPath, parameters, httpMethod, callback, null);
+ }
+
+ /**
+ * Constructs a request with a specific access token, graph path, parameters, and HTTP method.
+ * An access token need not be provided, in which case the request is sent without an access
+ * token and thus is not executed in the context of any particular user. Only certain graph
+ * requests can be expected to succeed in this case.
+ *
+ * Depending on the httpMethod parameter, the object at the graph path may be retrieved,
+ * created, or deleted.
+ *
+ * @param accessToken the access token to use, or null
+ * @param graphPath the graph path to retrieve, create, or delete
+ * @param parameters additional parameters to pass along with the Graph API request; parameters
+ * must be Strings, Numbers, Bitmaps, Dates, or Byte arrays.
+ * @param httpMethod the {@link HttpMethod} to use for the request, or null for default
+ * (HttpMethod.GET)
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions
+ * @param version the version of the Graph API
+ */
+ public GraphRequest(
+ AccessToken accessToken,
+ String graphPath,
+ Bundle parameters,
+ HttpMethod httpMethod,
+ Callback callback,
+ String version) {
+ this.accessToken = accessToken;
+ this.graphPath = graphPath;
+ this.version = version;
+
+ setCallback(callback);
+ setHttpMethod(httpMethod);
+
+ if (parameters != null) {
+ this.parameters = new Bundle(parameters);
+ } else {
+ this.parameters = new Bundle();
+ }
+
+ if (this.version == null) {
+ this.version = ServerProtocol.getAPIVersion();
+ }
+ }
+
+ GraphRequest(AccessToken accessToken, URL overriddenURL) {
+ this.accessToken = accessToken;
+ this.overriddenURL = overriddenURL.toString();
+
+ setHttpMethod(HttpMethod.GET);
+
+ this.parameters = new Bundle();
+ }
+
+ /**
+ * Creates a new Request configured to delete a resource through the Graph API.
+ *
+ * @param accessToken the access token to use, or null
+ * @param id the id of the object to delete
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions
+ * @return a Request that is ready to execute
+ */
+ public static GraphRequest newDeleteObjectRequest(
+ AccessToken accessToken,
+ String id,
+ Callback callback) {
+ return new GraphRequest(accessToken, id, null, HttpMethod.DELETE, callback);
+ }
+
+ /**
+ * Creates a new Request configured to retrieve a user's own profile.
+ *
+ * @param accessToken the access token to use, or null
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions
+ * @return a Request that is ready to execute
+ */
+ public static GraphRequest newMeRequest(
+ AccessToken accessToken,
+ final GraphJSONObjectCallback callback) {
+ Callback wrapper = new Callback() {
+ @Override
+ public void onCompleted(GraphResponse response) {
+ if (callback != null) {
+ callback.onCompleted(response.getJSONObject(), response);
+ }
+ }
+ };
+ return new GraphRequest(accessToken, ME, null, null, wrapper);
+ }
+
+ /**
+ * Creates a new Request configured to post a GraphObject to a particular graph path, to either
+ * create or update the object at that path.
+ *
+ * @param accessToken the access token to use, or null
+ * @param graphPath the graph path to retrieve, create, or delete
+ * @param graphObject the graph object to create or update
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions
+ * @return a Request that is ready to execute
+ */
+ public static GraphRequest newPostRequest(
+ AccessToken accessToken,
+ String graphPath,
+ JSONObject graphObject,
+ Callback callback) {
+ GraphRequest request = new GraphRequest(
+ accessToken,
+ graphPath,
+ null,
+ HttpMethod.POST,
+ callback);
+ request.setGraphObject(graphObject);
+ return request;
+ }
+
+ /**
+ * Creates a new Request configured to retrieve a user's friend list.
+ *
+ * @param accessToken the access token to use, or null
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions
+ * @return a Request that is ready to execute
+ */
+ public static GraphRequest newMyFriendsRequest(
+ AccessToken accessToken,
+ final GraphJSONArrayCallback callback) {
+ Callback wrapper = new Callback() {
+ @Override
+ public void onCompleted(GraphResponse response) {
+ if (callback != null) {
+ JSONObject result = response.getJSONObject();
+ JSONArray data = result != null ? result.optJSONArray("data") : null;
+ callback.onCompleted(data, response);
+ }
+ }
+ };
+ return new GraphRequest(accessToken, MY_FRIENDS, null, null, wrapper);
+ }
+
+ /**
+ * Creates a new Request configured to retrieve a particular graph path.
+ *
+ * @param accessToken the access token to use, or null
+ * @param graphPath the graph path to retrieve
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions
+ * @return a Request that is ready to execute
+ */
+ public static GraphRequest newGraphPathRequest(
+ AccessToken accessToken,
+ String graphPath,
+ Callback callback) {
+ return new GraphRequest(accessToken, graphPath, null, null, callback);
+ }
+
+ /**
+ * Creates a new Request that is configured to perform a search for places near a specified
+ * location via the Graph API. At least one of location or searchText must be specified.
+ *
+ * @param accessToken the access token to use, or null
+ * @param location the location around which to search; only the latitude and longitude
+ * components of the location are meaningful
+ * @param radiusInMeters the radius around the location to search, specified in meters; this is
+ * ignored if no location is specified
+ * @param resultsLimit the maximum number of results to return
+ * @param searchText optional text to search for as part of the name or type of an object
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions
+ * @return a Request that is ready to execute
+ * @throws FacebookException If neither location nor searchText is specified
+ */
+ public static GraphRequest newPlacesSearchRequest(
+ AccessToken accessToken,
+ Location location,
+ int radiusInMeters,
+ int resultsLimit,
+ String searchText,
+ final GraphJSONArrayCallback callback) {
+ if (location == null && Utility.isNullOrEmpty(searchText)) {
+ throw new FacebookException("Either location or searchText must be specified.");
+ }
+
+ Bundle parameters = new Bundle(5);
+ parameters.putString("type", "place");
+ parameters.putInt("limit", resultsLimit);
+ if (location != null) {
+ parameters.putString("center",
+ String.format(
+ Locale.US,
+ "%f,%f",
+ location.getLatitude(),
+ location.getLongitude()));
+ parameters.putInt("distance", radiusInMeters);
+ }
+ if (!Utility.isNullOrEmpty(searchText)) {
+ parameters.putString("q", searchText);
+ }
+
+ Callback wrapper = new Callback() {
+ @Override
+ public void onCompleted(GraphResponse response) {
+ if (callback != null) {
+ JSONObject result = response.getJSONObject();
+ JSONArray data = result != null ? result.optJSONArray("data") : null;
+ callback.onCompleted(data, response);
+ }
+ }
+ };
+
+ return new GraphRequest(accessToken, SEARCH, parameters, HttpMethod.GET, wrapper);
+ }
+
+
+ /**
+ * Creates a new Request configured to retrieve an App User ID for the app's Facebook user.
+ * Callers will send this ID back to their own servers, collect up a set to create a Facebook
+ * Custom Audience with, and then use the resultant Custom Audience to target ads.
+ *
+ * The GraphObject in the response will include a "custom_audience_third_party_id" property,
+ * with the value being the ID retrieved. This ID is an encrypted encoding of the Facebook
+ * user's ID and the invoking Facebook app ID. Multiple calls with the same user will return
+ * different IDs, thus these IDs cannot be used to correlate behavior across devices or
+ * applications, and are only meaningful when sent back to Facebook for creating Custom
+ * Audiences.
+ *
+ * The ID retrieved represents the Facebook user identified in the following way: if the
+ * specified access token (or active access token if `null`) is valid, the ID will represent the
+ * user associated with the active access token; otherwise the ID will represent the user logged
+ * into the native Facebook app on the device. A `null` ID will be provided into the callback if
+ * a) there is no native Facebook app, b) no one is logged into it, or c) the app has previously
+ * called {@link FacebookSdk#setLimitEventAndDataUsage(android.content.Context, boolean)} ;}
+ * with `true` for this user. You must call this method from a background thread for it to
+ * work properly.
+ *
+ * @param accessToken the access token to issue the Request on, or null If there is no
+ * logged-in Facebook user, null is the expected choice.
+ * @param context the Application context from which the app ID will be pulled, and from
+ * which the 'attribution ID' for the Facebook user is determined. If
+ * there has been no app ID set, an exception will be thrown.
+ * @param applicationId explicitly specified Facebook App ID. If null, the application ID from
+ * the access token will be used, if any; if not, the application ID from
+ * metadata will be used.
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions. The GraphObject in the Response will
+ * contain a "custom_audience_third_party_id" property that represents the
+ * user as described above.
+ * @return a Request that is ready to execute
+ */
+ public static GraphRequest newCustomAudienceThirdPartyIdRequest(AccessToken accessToken,
+ Context context,
+ String applicationId,
+ Callback callback) {
+
+ if (applicationId == null && accessToken != null) {
+ applicationId = accessToken.getApplicationId();
+ }
+
+ if (applicationId == null) {
+ applicationId = Utility.getMetadataApplicationId(context);
+ }
+
+ if (applicationId == null) {
+ throw new FacebookException("Facebook App ID cannot be determined");
+ }
+
+ String endpoint = applicationId + "/custom_audience_third_party_id";
+ AttributionIdentifiers attributionIdentifiers =
+ AttributionIdentifiers.getAttributionIdentifiers(context);
+ Bundle parameters = new Bundle();
+
+ if (accessToken == null) {
+ // Only use the attributionID if we don't have an access token. If we do, then the user
+ // token will be used to identify the user, and is more reliable than the attributionID.
+ String udid = attributionIdentifiers.getAttributionId() != null
+ ? attributionIdentifiers.getAttributionId()
+ : attributionIdentifiers.getAndroidAdvertiserId();
+ if (attributionIdentifiers.getAttributionId() != null) {
+ parameters.putString("udid", udid);
+ }
+ }
+
+ // Server will choose to not provide the App User ID in the event that event usage has been
+ // limited for this user for this app.
+ if (FacebookSdk.getLimitEventAndDataUsage(context)
+ || attributionIdentifiers.isTrackingLimited()) {
+ parameters.putString("limit_event_usage", "1");
+ }
+
+ return new GraphRequest(accessToken, endpoint, parameters, HttpMethod.GET, callback);
+ }
+
+ /**
+ * Creates a new Request configured to retrieve an App User ID for the app's Facebook user.
+ * Callers will send this ID back to their own servers, collect up a set to create a Facebook
+ * Custom Audience with, and then use the resultant Custom Audience to target ads.
+ *
+ * The GraphObject in the response will include a "custom_audience_third_party_id" property,
+ * with the value being the ID retrieved. This ID is an encrypted encoding of the Facebook
+ * user's ID and the invoking Facebook app ID. Multiple calls with the same user will return
+ * different IDs, thus these IDs cannot be used to correlate behavior across devices or
+ * applications, and are only meaningful when sent back to Facebook for creating Custom
+ * Audiences.
+ *
+ * The ID retrieved represents the Facebook user identified in the following way: if the
+ * specified access token (or active access token if `null`) is valid, the ID will represent the
+ * user associated with the active access token; otherwise the ID will represent the user logged
+ * into the native Facebook app on the device. A `null` ID will be provided into the callback if
+ * a) there is no native Facebook app, b) no one is logged into it, or c) the app has previously
+ * called {@link FacebookSdk#setLimitEventAndDataUsage(android.content.Context, boolean)} with
+ * `true` for this user. You must call this method from a background thread for it to work
+ * properly.
+ *
+ * @param accessToken the access token to issue the Request on, or null If there is no logged-in
+ * Facebook user, null is the expected choice.
+ * @param context the Application context from which the app ID will be pulled, and from
+ * which the 'attribution ID' for the Facebook user is determined. If there
+ * has been no app ID set, an exception will be thrown.
+ * @param callback a callback that will be called when the request is completed to handle
+ * success or error conditions. The GraphObject in the Response will contain
+ * a "custom_audience_third_party_id" property that represents the user as
+ * described above.
+ * @return a Request that is ready to execute
+ */
+ public static GraphRequest newCustomAudienceThirdPartyIdRequest(
+ AccessToken accessToken,
+ Context context,
+ Callback callback) {
+ return newCustomAudienceThirdPartyIdRequest(accessToken, context, null, callback);
+ }
+
+ /**
+ * Returns the GraphObject, if any, associated with this request.
+ *
+ * @return the GraphObject associated with this request, or null if there is none
+ */
+ public final JSONObject getGraphObject() {
+ return this.graphObject;
+ }
+
+ /**
+ * Sets the GraphObject associated with this request. This is meaningful only for POST
+ * requests.
+ *
+ * @param graphObject the GraphObject to upload along with this request
+ */
+ public final void setGraphObject(JSONObject graphObject) {
+ this.graphObject = graphObject;
+ }
+
+ /**
+ * Returns the graph path of this request, if any.
+ *
+ * @return the graph path of this request, or null if there is none
+ */
+ public final String getGraphPath() {
+ return this.graphPath;
+ }
+
+ /**
+ * Sets the graph path of this request.
+ *
+ * @param graphPath the graph path for this request
+ */
+ public final void setGraphPath(String graphPath) {
+ this.graphPath = graphPath;
+ }
+
+ /**
+ * Returns the {@link HttpMethod} to use for this request.
+ *
+ * @return the HttpMethod
+ */
+ public final HttpMethod getHttpMethod() {
+ return this.httpMethod;
+ }
+
+ /**
+ * Sets the {@link HttpMethod} to use for this request.
+ *
+ * @param httpMethod the HttpMethod, or null for the default (HttpMethod.GET).
+ */
+ public final void setHttpMethod(HttpMethod httpMethod) {
+ if (overriddenURL != null && httpMethod != HttpMethod.GET) {
+ throw new FacebookException("Can't change HTTP method on request with overridden URL.");
+ }
+ this.httpMethod = (httpMethod != null) ? httpMethod : HttpMethod.GET;
+ }
+
+ /**
+ * Returns the version of the API that this request will use. By default this is the current
+ * API at the time the SDK is released.
+ *
+ * @return the version that this request will use
+ */
+ public final String getVersion() {
+ return this.version;
+ }
+
+ /**
+ * Set the version to use for this request. By default the version will be the current API at
+ * the time the SDK is released. Only use this if you need to explicitly override.
+ *
+ * @param version The version to use. Should look like "v2.0"
+ */
+ public final void setVersion(String version) {
+ this.version = version;
+ }
+
+ /**
+ * This is an internal function that is not meant to be used by developers.
+ */
+ public final void setSkipClientToken(boolean skipClientToken) {
+ this.skipClientToken = skipClientToken;
+ }
+
+ /**
+ * Returns the parameters for this request.
+ *
+ * @return the parameters
+ */
+ public final Bundle getParameters() {
+ return this.parameters;
+ }
+
+ /**
+ * Sets the parameters for this request.
+ *
+ * @param parameters the parameters
+ */
+ public final void setParameters(Bundle parameters) {
+ this.parameters = parameters;
+ }
+
+ /**
+ * Returns the access token associated with this request.
+ *
+ * @return the access token associated with this request, or null if none has been specified
+ */
+ public final AccessToken getAccessToken() {
+ return this.accessToken;
+ }
+
+ /**
+ * Sets the access token to use for this request.
+ *
+ * @param accessToken the access token to use for this request
+ */
+ public final void setAccessToken(AccessToken accessToken) {
+ this.accessToken = accessToken;
+ }
+
+ /**
+ * Returns the name of this requests entry in a batched request.
+ *
+ * @return the name of this requests batch entry, or null if none has been specified
+ */
+ public final String getBatchEntryName() {
+ return this.batchEntryName;
+ }
+
+ /**
+ * Sets the name of this request's entry in a batched request. This value is only used if this
+ * request is submitted as part of a batched request. It can be used to specified dependencies
+ * between requests.
+ * See Batch Requests in
+ * the Graph API documentation for more details.
+ *
+ * @param batchEntryName the name of this requests entry in a batched request, which must be
+ * unique within a particular batch of requests
+ */
+ public final void setBatchEntryName(String batchEntryName) {
+ this.batchEntryName = batchEntryName;
+ }
+
+ /**
+ * Returns the name of the request that this request entry explicitly depends on in a batched
+ * request.
+ *
+ * @return the name of this requests dependency, or null if none has been specified
+ */
+ public final String getBatchEntryDependsOn() {
+ return this.batchEntryDependsOn;
+ }
+
+ /**
+ * Sets the name of the request entry that this request explicitly depends on in a batched
+ * request. This value is only used if this request is submitted as part of a batched request.
+ * It can be used to specified dependencies between requests. See Batch Requests in the
+ * Graph API documentation for more details.
+ *
+ * @param batchEntryDependsOn the name of the request entry that this entry depends on in a
+ * batched request
+ */
+ public final void setBatchEntryDependsOn(String batchEntryDependsOn) {
+ this.batchEntryDependsOn = batchEntryDependsOn;
+ }
+
+
+ /**
+ * Returns whether or not this batch entry will return a response if it is successful. Only
+ * applies if another request entry in the batch specifies this entry as a dependency.
+ *
+ * @return the name of this requests dependency, or null if none has been specified
+ */
+ public final boolean getBatchEntryOmitResultOnSuccess() {
+ return this.batchEntryOmitResultOnSuccess;
+ }
+
+ /**
+ * Sets whether or not this batch entry will return a response if it is successful. Only applies
+ * if another request entry in the batch specifies this entry as a dependency. See Batch Requests in the
+ * Graph API documentation for more details.
+ *
+ * @param batchEntryOmitResultOnSuccess the name of the request entry that this entry depends on
+ * in a batched request
+ */
+ public final void setBatchEntryOmitResultOnSuccess(boolean batchEntryOmitResultOnSuccess) {
+ this.batchEntryOmitResultOnSuccess = batchEntryOmitResultOnSuccess;
+ }
+
+ /**
+ * Gets the default Facebook application ID that will be used to submit batched requests.
+ * Batched requests require an application ID, so either at least one request in a batch must
+ * provide an access token or the application ID must be specified explicitly.
+ *
+ * @return the Facebook application ID to use for batched requests if none can be determined
+ */
+ public static final String getDefaultBatchApplicationId() {
+ return GraphRequest.defaultBatchApplicationId;
+ }
+
+ /**
+ * Sets the default application ID that will be used to submit batched requests if none of those
+ * requests specifies an access token. Batched requests require an application ID, so either at
+ * least one request in a batch must specify an access token or the application ID must be
+ * specified explicitly.
+ *
+ * @param applicationId the Facebook application ID to use for batched requests if none can
+ * be determined
+ */
+ public static final void setDefaultBatchApplicationId(String applicationId) {
+ defaultBatchApplicationId = applicationId;
+ }
+
+ /**
+ * Returns the callback which will be called when the request finishes.
+ *
+ * @return the callback
+ */
+ public final Callback getCallback() {
+ return callback;
+ }
+
+ /**
+ * Sets the callback which will be called when the request finishes.
+ *
+ * @param callback the callback
+ */
+ public final void setCallback(final Callback callback) {
+ // Wrap callback to parse debug response if Graph Debug Mode is Enabled.
+ if (FacebookSdk.isLoggingBehaviorEnabled(LoggingBehavior.GRAPH_API_DEBUG_INFO)
+ || FacebookSdk.isLoggingBehaviorEnabled(LoggingBehavior.GRAPH_API_DEBUG_WARNING)) {
+ Callback wrapper = new Callback() {
+ @Override
+ public void onCompleted(GraphResponse response) {
+ JSONObject responseObject = response.getJSONObject();
+ JSONObject debug =
+ responseObject != null ? responseObject.optJSONObject(DEBUG_KEY) : null;
+ JSONArray debugMessages =
+ debug != null ? debug.optJSONArray(DEBUG_MESSAGES_KEY) : null;
+ if (debugMessages != null) {
+ for (int i = 0; i < debugMessages.length(); ++i) {
+ JSONObject debugMessageObject = debugMessages.optJSONObject(i);
+ String debugMessage = debugMessageObject != null
+ ? debugMessageObject.optString(DEBUG_MESSAGE_KEY)
+ : null;
+ String debugMessageType = debugMessageObject != null
+ ? debugMessageObject.optString(DEBUG_MESSAGE_TYPE_KEY)
+ : null;
+ String debugMessageLink = debugMessageObject != null
+ ? debugMessageObject.optString(DEBUG_MESSAGE_LINK_KEY)
+ : null;
+ if (debugMessage != null && debugMessageType != null) {
+ LoggingBehavior behavior = LoggingBehavior.GRAPH_API_DEBUG_INFO;
+ if (debugMessageType.equals("warning")) {
+ behavior = LoggingBehavior.GRAPH_API_DEBUG_WARNING;
+ }
+ if (!Utility.isNullOrEmpty(debugMessageLink)) {
+ debugMessage += " Link: " + debugMessageLink;
+ }
+ Logger.log(behavior, TAG, debugMessage);
+ }
+ }
+ }
+ if (callback != null) {
+ callback.onCompleted(response);
+ }
+ }
+ };
+ this.callback = wrapper;
+ } else {
+ this.callback = callback;
+ }
+
+ }
+
+ /**
+ * Sets the tag on the request; this is an application-defined object that can be used to
+ * distinguish between different requests. Its value has no effect on the execution of the
+ * request.
+ *
+ * @param tag an object to serve as a tag, or null
+ */
+ public final void setTag(Object tag) {
+ this.tag = tag;
+ }
+
+ /**
+ * Gets the tag on the request; this is an application-defined object that can be used to
+ * distinguish between different requests. Its value has no effect on the execution of the
+ * request.
+ *
+ * @return an object that serves as a tag, or null
+ */
+ public final Object getTag() {
+ return tag;
+ }
+
+ /**
+ * Executes this request on the current thread and blocks while waiting for the response.
+ *
+ * This should only be called if you have transitioned off the UI thread.
+ *
+ * @return the Response object representing the results of the request
+ * @throws FacebookException If there was an error in the protocol used to communicate
+ * with the service
+ * @throws IllegalArgumentException
+ */
+ public final GraphResponse executeAndWait() {
+ return GraphRequest.executeAndWait(this);
+ }
+
+ /**
+ * Executes the request asynchronously. This function will return immediately,
+ * and the request will be processed on a separate thread. In order to process result of a
+ * request, or determine whether a request succeeded or failed, a callback must be specified
+ * (see the {@link #setCallback(Callback) setCallback} method).
+ *
+ * This should only be called from the UI thread.
+ *
+ * @return a RequestAsyncTask that is executing the request
+ * @throws IllegalArgumentException
+ */
+ public final GraphRequestAsyncTask executeAsync() {
+ return GraphRequest.executeBatchAsync(this);
+ }
+
+ /**
+ * Serializes one or more requests but does not execute them. The resulting HttpURLConnection
+ * can be executed explicitly by the caller.
+ *
+ * @param requests one or more Requests to serialize
+ * @return an HttpURLConnection which is ready to execute
+ * @throws FacebookException If any of the requests in the batch are badly constructed or
+ * if there are problems contacting the service
+ * @throws IllegalArgumentException if the passed in array is zero-length
+ * @throws NullPointerException if the passed in array or any of its contents are null
+ */
+ public static HttpURLConnection toHttpConnection(GraphRequest... requests) {
+ return toHttpConnection(Arrays.asList(requests));
+ }
+
+ /**
+ * Serializes one or more requests but does not execute them. The resulting HttpURLConnection
+ * can be executed explicitly by the caller.
+ *
+ * @param requests one or more Requests to serialize
+ * @return an HttpURLConnection which is ready to execute
+ * @throws FacebookException If any of the requests in the batch are badly constructed or
+ * if there are problems contacting the service
+ * @throws IllegalArgumentException if the passed in collection is empty
+ * @throws NullPointerException if the passed in collection or any of its contents are null
+ */
+ public static HttpURLConnection toHttpConnection(Collection requests) {
+ Validate.notEmptyAndContainsNoNulls(requests, "requests");
+
+ return toHttpConnection(new GraphRequestBatch(requests));
+ }
+
+ /**
+ * Serializes one or more requests but does not execute them. The resulting HttpURLConnection
+ * can be executed explicitly by the caller.
+ *
+ * @param requests a RequestBatch to serialize
+ * @return an HttpURLConnection which is ready to execute
+ * @throws FacebookException If any of the requests in the batch are badly constructed or
+ * if there are problems contacting the service
+ * @throws IllegalArgumentException
+ */
+ public static HttpURLConnection toHttpConnection(GraphRequestBatch requests) {
+
+ URL url;
+ try {
+ if (requests.size() == 1) {
+ // Single request case.
+ GraphRequest request = requests.get(0);
+ // In the non-batch case, the URL we use really is the same one returned by
+ // getUrlForSingleRequest.
+ url = new URL(request.getUrlForSingleRequest());
+ } else {
+ // Batch case -- URL is just the graph API base, individual request URLs are
+ // serialized as relative_url parameters within each batch entry.
+ url = new URL(ServerProtocol.getGraphUrlBase());
+ }
+ } catch (MalformedURLException e) {
+ throw new FacebookException("could not construct URL for request", e);
+ }
+
+ HttpURLConnection connection;
+ try {
+ connection = createConnection(url);
+
+ serializeToUrlConnection(requests, connection);
+ } catch (IOException e) {
+ throw new FacebookException("could not construct request body", e);
+ } catch (JSONException e) {
+ throw new FacebookException("could not construct request body", e);
+ }
+
+ return connection;
+ }
+
+ /**
+ * Executes a single request on the current thread and blocks while waiting for the response.
+ *
+ * This should only be used if you have transitioned off the UI thread.
+ *
+ * @param request the Request to execute
+ * @return the Response object representing the results of the request
+ * @throws FacebookException If there was an error in the protocol used to communicate with the
+ * service
+ */
+ public static GraphResponse executeAndWait(GraphRequest request) {
+ List responses = executeBatchAndWait(request);
+
+ if (responses == null || responses.size() != 1) {
+ throw new FacebookException("invalid state: expected a single response");
+ }
+
+ return responses.get(0);
+ }
+
+ /**
+ * Executes requests on the current thread as a single batch and blocks while waiting for the
+ * response.
+ *
+ * This should only be used if you have transitioned off the UI thread.
+ *
+ * @param requests the Requests to execute
+ * @return a list of Response objects representing the results of the requests; responses are
+ * returned in the same order as the requests were specified.
+ * @throws NullPointerException In case of a null request
+ * @throws FacebookException If there was an error in the protocol used to communicate with
+ * the service
+ */
+ public static List executeBatchAndWait(GraphRequest... requests) {
+ Validate.notNull(requests, "requests");
+
+ return executeBatchAndWait(Arrays.asList(requests));
+ }
+
+ /**
+ * Executes requests as a single batch on the current thread and blocks while waiting for the
+ * responses.
+ *
+ * This should only be used if you have transitioned off the UI thread.
+ *
+ * @param requests the Requests to execute
+ * @return a list of Response objects representing the results of the requests; responses are
+ * returned in the same order as the requests were specified.
+ * @throws FacebookException If there was an error in the protocol used to communicate with the
+ * service
+ */
+ public static List executeBatchAndWait(Collection requests) {
+ return executeBatchAndWait(new GraphRequestBatch(requests));
+ }
+
+ /**
+ * Executes requests on the current thread as a single batch and blocks while waiting for the
+ * responses.
+ *
+ * This should only be used if you have transitioned off the UI thread.
+ *
+ * @param requests the batch of Requests to execute
+ * @return a list of Response objects representing the results of the requests; responses are
+ * returned in the same order as the requests were specified.
+ * @throws FacebookException If there was an error in the protocol used to communicate
+ * with the service
+ * @throws IllegalArgumentException if the passed in RequestBatch is empty
+ * @throws NullPointerException if the passed in RequestBatch or any of its contents are
+ * null
+ */
+ public static List executeBatchAndWait(GraphRequestBatch requests) {
+ Validate.notEmptyAndContainsNoNulls(requests, "requests");
+
+ HttpURLConnection connection = null;
+ try {
+ connection = toHttpConnection(requests);
+ } catch (Exception ex) {
+ List responses = GraphResponse.constructErrorResponses(
+ requests.getRequests(),
+ null,
+ new FacebookException(ex));
+ runCallbacks(requests, responses);
+ return responses;
+ }
+
+ List responses = executeConnectionAndWait(connection, requests);
+ return responses;
+ }
+
+ /**
+ * Executes requests as a single batch asynchronously. This function will return immediately,
+ * and the requests will be processed on a separate thread. In order to process results of a
+ * request, or determine whether a request succeeded or failed, a callback must be specified
+ * (see the {@link #setCallback(Callback) setCallback} method).
+ *
+ * This should only be called from the UI thread.
+ *
+ * @param requests the Requests to execute
+ * @return a RequestAsyncTask that is executing the request
+ * @throws NullPointerException If a null request is passed in
+ */
+ public static GraphRequestAsyncTask executeBatchAsync(GraphRequest... requests) {
+ Validate.notNull(requests, "requests");
+
+ return executeBatchAsync(Arrays.asList(requests));
+ }
+
+ /**
+ * Executes requests as a single batch asynchronously. This function will return immediately,
+ * and the requests will be processed on a separate thread. In order to process results of a
+ * request, or determine whether a request succeeded or failed, a callback must be specified
+ * (see the {@link #setCallback(Callback) setCallback} method).
+ *
+ * This should only be called from the UI thread.
+ *
+ * @param requests the Requests to execute
+ * @return a RequestAsyncTask that is executing the request
+ * @throws IllegalArgumentException if the passed in collection is empty
+ * @throws NullPointerException if the passed in collection or any of its contents are null
+ */
+ public static GraphRequestAsyncTask executeBatchAsync(Collection requests) {
+ return executeBatchAsync(new GraphRequestBatch(requests));
+ }
+
+ /**
+ * Executes requests as a single batch asynchronously. This function will return immediately,
+ * and the requests will be processed on a separate thread. In order to process results of a
+ * request, or determine whether a request succeeded or failed, a callback must be specified
+ * (see the {@link #setCallback(Callback) setCallback} method).
+ *
+ * This should only be called from the UI thread.
+ *
+ * @param requests the RequestBatch to execute
+ * @return a RequestAsyncTask that is executing the request
+ * @throws IllegalArgumentException if the passed in RequestBatch is empty
+ * @throws NullPointerException if the passed in RequestBatch or any of its contents are
+ * null
+ */
+ public static GraphRequestAsyncTask executeBatchAsync(GraphRequestBatch requests) {
+ Validate.notEmptyAndContainsNoNulls(requests, "requests");
+
+ GraphRequestAsyncTask asyncTask = new GraphRequestAsyncTask(requests);
+ asyncTask.executeOnSettingsExecutor();
+ return asyncTask;
+ }
+
+ /**
+ * Executes requests that have already been serialized into an HttpURLConnection. No validation
+ * is done that the contents of the connection actually reflect the serialized requests, so it
+ * is the caller's responsibility to ensure that it will correctly generate the desired
+ * responses.
+ *
+ * This should only be called if you have transitioned off the UI thread.
+ *
+ * @param connection the HttpURLConnection that the requests were serialized into
+ * @param requests the requests represented by the HttpURLConnection
+ * @return a list of Responses corresponding to the requests
+ * @throws FacebookException If there was an error in the protocol used to communicate with the
+ * service
+ */
+ public static List executeConnectionAndWait(
+ HttpURLConnection connection,
+ Collection requests) {
+ return executeConnectionAndWait(connection, new GraphRequestBatch(requests));
+ }
+
+ /**
+ * Executes requests that have already been serialized into an HttpURLConnection. No validation
+ * is done that the contents of the connection actually reflect the serialized requests, so it
+ * is the caller's responsibility to ensure that it will correctly generate the desired
+ * responses.
+ *
+ * This should only be called if you have transitioned off the UI thread.
+ *
+ * @param connection the HttpURLConnection that the requests were serialized into
+ * @param requests the RequestBatch represented by the HttpURLConnection
+ * @return a list of Responses corresponding to the requests
+ * @throws FacebookException If there was an error in the protocol used to communicate with the
+ * service
+ */
+ public static List executeConnectionAndWait(
+ HttpURLConnection connection,
+ GraphRequestBatch requests) {
+ List responses = GraphResponse.fromHttpConnection(connection, requests);
+
+ Utility.disconnectQuietly(connection);
+
+ int numRequests = requests.size();
+ if (numRequests != responses.size()) {
+ throw new FacebookException(
+ String.format(Locale.US,
+ "Received %d responses while expecting %d",
+ responses.size(),
+ numRequests));
+ }
+
+ runCallbacks(requests, responses);
+
+ // Try extending the current access token in case it's needed.
+ AccessTokenManager.getInstance().extendAccessTokenIfNeeded();
+
+ return responses;
+ }
+
+ /**
+ * Asynchronously executes requests that have already been serialized into an HttpURLConnection.
+ * No validation is done that the contents of the connection actually reflect the serialized
+ * requests, so it is the caller's responsibility to ensure that it will correctly generate the
+ * desired responses. This function will return immediately, and the requests will be processed
+ * on a separate thread. In order to process results of a request, or determine whether a
+ * request succeeded or failed, a callback must be specified (see the {@link
+ * #setCallback(Callback) setCallback} method).
+ *
+ * This should only be called from the UI thread.
+ *
+ * @param connection the HttpURLConnection that the requests were serialized into
+ * @param requests the requests represented by the HttpURLConnection
+ * @return a RequestAsyncTask that is executing the request
+ */
+ public static GraphRequestAsyncTask executeConnectionAsync(
+ HttpURLConnection connection,
+ GraphRequestBatch requests) {
+ return executeConnectionAsync(null, connection, requests);
+ }
+
+ /**
+ * Asynchronously executes requests that have already been serialized into an HttpURLConnection.
+ * No validation is done that the contents of the connection actually reflect the serialized
+ * requests, so it is the caller's responsibility to ensure that it will correctly generate the
+ * desired responses. This function will return immediately, and the requests will be processed
+ * on a separate thread. In order to process results of a request, or determine whether a
+ * request succeeded or failed, a callback must be specified (see the {@link
+ * #setCallback(Callback) setCallback} method)
+ *
+ * This should only be called from the UI thread.
+ *
+ * @param callbackHandler a Handler that will be used to post calls to the callback for each
+ * request; if null, a Handler will be instantiated on the calling
+ * thread
+ * @param connection the HttpURLConnection that the requests were serialized into
+ * @param requests the requests represented by the HttpURLConnection
+ * @return a RequestAsyncTask that is executing the request
+ */
+ public static GraphRequestAsyncTask executeConnectionAsync(
+ Handler callbackHandler,
+ HttpURLConnection connection,
+ GraphRequestBatch requests) {
+ Validate.notNull(connection, "connection");
+
+ GraphRequestAsyncTask asyncTask = new GraphRequestAsyncTask(connection, requests);
+ requests.setCallbackHandler(callbackHandler);
+ asyncTask.executeOnSettingsExecutor();
+ return asyncTask;
+ }
+
+ /**
+ * Returns a string representation of this Request, useful for debugging.
+ *
+ * @return the debugging information
+ */
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{Request: ")
+ .append(" accessToken: ")
+ .append(accessToken == null ? "null" : accessToken)
+ .append(", graphPath: ")
+ .append(graphPath)
+ .append(", graphObject: ")
+ .append(graphObject)
+ .append(", httpMethod: ")
+ .append(httpMethod)
+ .append(", parameters: ")
+ .append(parameters)
+ .append("}")
+ .toString();
+ }
+
+ static void runCallbacks(final GraphRequestBatch requests, List responses) {
+ int numRequests = requests.size();
+
+ // Compile the list of callbacks to call and then run them either on this thread or via the
+ // Handler we received
+ final ArrayList> callbacks = new ArrayList>();
+ for (int i = 0; i < numRequests; ++i) {
+ GraphRequest request = requests.get(i);
+ if (request.callback != null) {
+ callbacks.add(
+ new Pair(request.callback, responses.get(i)));
+ }
+ }
+
+ if (callbacks.size() > 0) {
+ Runnable runnable = new Runnable() {
+ public void run() {
+ for (Pair pair : callbacks) {
+ pair.first.onCompleted(pair.second);
+ }
+
+ List batchCallbacks = requests.getCallbacks();
+ for (GraphRequestBatch.Callback batchCallback : batchCallbacks) {
+ batchCallback.onBatchCompleted(requests);
+ }
+ }
+ };
+
+ Handler callbackHandler = requests.getCallbackHandler();
+ if (callbackHandler == null) {
+ // Run on this thread.
+ runnable.run();
+ } else {
+ // Post to the handler.
+ callbackHandler.post(runnable);
+ }
+ }
+ }
+
+ private static HttpURLConnection createConnection(URL url) throws IOException {
+ HttpURLConnection connection;
+ connection = (HttpURLConnection) url.openConnection();
+
+ connection.setRequestProperty(USER_AGENT_HEADER, getUserAgent());
+ connection.setRequestProperty(ACCEPT_LANGUAGE_HEADER, Locale.getDefault().toString());
+
+ connection.setChunkedStreamingMode(0);
+ return connection;
+ }
+
+
+ private void addCommonParameters() {
+ if (this.accessToken != null) {
+ if (!this.parameters.containsKey(ACCESS_TOKEN_PARAM)) {
+ String token = accessToken.getToken();
+ Logger.registerAccessToken(token);
+ this.parameters.putString(ACCESS_TOKEN_PARAM, token);
+ }
+ } else if (!skipClientToken && !this.parameters.containsKey(ACCESS_TOKEN_PARAM)) {
+ String appID = FacebookSdk.getApplicationId();
+ String clientToken = FacebookSdk.getClientToken();
+ if (!Utility.isNullOrEmpty(appID) && !Utility.isNullOrEmpty(clientToken)) {
+ String accessToken = appID + "|" + clientToken;
+ this.parameters.putString(ACCESS_TOKEN_PARAM, accessToken);
+ } else {
+ Log.d(TAG, "Warning: Request without access token missing application ID or" +
+ " client token.");
+ }
+ }
+ this.parameters.putString(SDK_PARAM, SDK_ANDROID);
+ this.parameters.putString(FORMAT_PARAM, FORMAT_JSON);
+
+ if (FacebookSdk.isLoggingBehaviorEnabled(LoggingBehavior.GRAPH_API_DEBUG_INFO)) {
+ this.parameters.putString(DEBUG_PARAM, DEBUG_SEVERITY_INFO);
+ } else if (FacebookSdk.isLoggingBehaviorEnabled(LoggingBehavior.GRAPH_API_DEBUG_WARNING)) {
+ this.parameters.putString(DEBUG_PARAM, DEBUG_SEVERITY_WARNING);
+ }
+ }
+
+ private String appendParametersToBaseUrl(String baseUrl) {
+ Uri.Builder uriBuilder = new Uri.Builder().encodedPath(baseUrl);
+
+ Set keys = this.parameters.keySet();
+ for (String key : keys) {
+ Object value = this.parameters.get(key);
+
+ if (value == null) {
+ value = "";
+ }
+
+ if (isSupportedParameterType(value)) {
+ value = parameterToString(value);
+ } else {
+ if (httpMethod == HttpMethod.GET) {
+ throw new IllegalArgumentException(
+ String.format(
+ Locale.US,
+ "Unsupported parameter type for GET request: %s",
+ value.getClass().getSimpleName()));
+ }
+ continue;
+ }
+
+ uriBuilder.appendQueryParameter(key, value.toString());
+ }
+
+ return uriBuilder.toString();
+ }
+
+ final String getUrlForBatchedRequest() {
+ if (overriddenURL != null) {
+ throw new FacebookException("Can't override URL for a batch request");
+ }
+
+ String baseUrl = getGraphPathWithVersion();
+ addCommonParameters();
+ return appendParametersToBaseUrl(baseUrl);
+ }
+
+ final String getUrlForSingleRequest() {
+ if (overriddenURL != null) {
+ return overriddenURL.toString();
+ }
+
+ String graphBaseUrlBase;
+ if (this.getHttpMethod() == HttpMethod.POST
+ && graphPath != null
+ && graphPath.endsWith(VIDEOS_SUFFIX)) {
+ graphBaseUrlBase = ServerProtocol.getGraphVideoUrlBase();
+ } else {
+ graphBaseUrlBase = ServerProtocol.getGraphUrlBase();
+ }
+ String baseUrl = String.format("%s/%s", graphBaseUrlBase, getGraphPathWithVersion());
+
+ addCommonParameters();
+ return appendParametersToBaseUrl(baseUrl);
+ }
+
+ private String getGraphPathWithVersion() {
+ Matcher matcher = versionPattern.matcher(this.graphPath);
+ if (matcher.matches()) {
+ return this.graphPath;
+ }
+ return String.format("%s/%s", this.version, this.graphPath);
+ }
+
+ private static class Attachment {
+ private final GraphRequest request;
+ private final Object value;
+
+ public Attachment(GraphRequest request, Object value) {
+ this.request = request;
+ this.value = value;
+ }
+
+ public GraphRequest getRequest() {
+ return request;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+ }
+
+ private void serializeToBatch(
+ JSONArray batch,
+ Map attachments
+ ) throws JSONException, IOException {
+ JSONObject batchEntry = new JSONObject();
+
+ if (this.batchEntryName != null) {
+ batchEntry.put(BATCH_ENTRY_NAME_PARAM, this.batchEntryName);
+ batchEntry.put(
+ BATCH_ENTRY_OMIT_RESPONSE_ON_SUCCESS_PARAM,
+ this.batchEntryOmitResultOnSuccess);
+ }
+ if (this.batchEntryDependsOn != null) {
+ batchEntry.put(BATCH_ENTRY_DEPENDS_ON_PARAM, this.batchEntryDependsOn);
+ }
+
+ String relativeURL = getUrlForBatchedRequest();
+ batchEntry.put(BATCH_RELATIVE_URL_PARAM, relativeURL);
+ batchEntry.put(BATCH_METHOD_PARAM, httpMethod);
+ if (this.accessToken != null) {
+ String token = this.accessToken.getToken();
+ Logger.registerAccessToken(token);
+ }
+
+ // Find all of our attachments. Remember their names and put them in the attachment map.
+ ArrayList attachmentNames = new ArrayList();
+ Set keys = this.parameters.keySet();
+ for (String key : keys) {
+ Object value = this.parameters.get(key);
+ if (isSupportedAttachmentType(value)) {
+ // Make the name unique across this entire batch.
+ String name = String.format(
+ Locale.ROOT,
+ "%s%d",
+ ATTACHMENT_FILENAME_PREFIX,
+ attachments.size());
+ attachmentNames.add(name);
+ attachments.put(name, new Attachment(this, value));
+ }
+ }
+
+ if (!attachmentNames.isEmpty()) {
+ String attachmentNamesString = TextUtils.join(",", attachmentNames);
+ batchEntry.put(ATTACHED_FILES_PARAM, attachmentNamesString);
+ }
+
+ if (this.graphObject != null) {
+ // Serialize the graph object into the "body" parameter.
+ final ArrayList keysAndValues = new ArrayList();
+ processGraphObject(this.graphObject, relativeURL, new KeyValueSerializer() {
+ @Override
+ public void writeString(String key, String value) throws IOException {
+ keysAndValues.add(String.format(
+ Locale.US,
+ "%s=%s",
+ key,
+ URLEncoder.encode(value, "UTF-8")));
+ }
+ });
+ String bodyValue = TextUtils.join("&", keysAndValues);
+ batchEntry.put(BATCH_BODY_PARAM, bodyValue);
+ }
+
+ batch.put(batchEntry);
+ }
+
+ private static boolean hasOnProgressCallbacks(GraphRequestBatch requests) {
+ for (GraphRequestBatch.Callback callback : requests.getCallbacks()) {
+ if (callback instanceof GraphRequestBatch.OnProgressCallback) {
+ return true;
+ }
+ }
+
+ for (GraphRequest request : requests) {
+ if (request.getCallback() instanceof OnProgressCallback) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static void setConnectionContentType(
+ HttpURLConnection connection,
+ boolean shouldUseGzip) {
+ if (shouldUseGzip) {
+ connection.setRequestProperty(CONTENT_TYPE_HEADER, "application/x-www-form-urlencoded");
+ connection.setRequestProperty(CONTENT_ENCODING_HEADER, "gzip");
+ } else {
+ connection.setRequestProperty(CONTENT_TYPE_HEADER, getMimeContentType());
+ }
+ }
+
+ private static boolean isGzipCompressible(GraphRequestBatch requests) {
+ for (GraphRequest request : requests) {
+ for (String key : request.parameters.keySet()) {
+ Object value = request.parameters.get(key);
+ if (isSupportedAttachmentType(value)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ final static void serializeToUrlConnection(
+ GraphRequestBatch requests,
+ HttpURLConnection connection
+ ) throws IOException, JSONException {
+ Logger logger = new Logger(LoggingBehavior.REQUESTS, "Request");
+
+ int numRequests = requests.size();
+ boolean shouldUseGzip = isGzipCompressible(requests);
+
+ HttpMethod connectionHttpMethod =
+ (numRequests == 1) ? requests.get(0).httpMethod : HttpMethod.POST;
+ connection.setRequestMethod(connectionHttpMethod.name());
+ setConnectionContentType(connection, shouldUseGzip);
+
+ URL url = connection.getURL();
+ logger.append("Request:\n");
+ logger.appendKeyValue("Id", requests.getId());
+ logger.appendKeyValue("URL", url);
+ logger.appendKeyValue("Method", connection.getRequestMethod());
+ logger.appendKeyValue("User-Agent", connection.getRequestProperty("User-Agent"));
+ logger.appendKeyValue("Content-Type", connection.getRequestProperty("Content-Type"));
+
+ connection.setConnectTimeout(requests.getTimeout());
+ connection.setReadTimeout(requests.getTimeout());
+
+ // If we have a single non-POST request, don't try to serialize anything or
+ // HttpURLConnection will turn it into a POST.
+ boolean isPost = (connectionHttpMethod == HttpMethod.POST);
+ if (!isPost) {
+ logger.log();
+ return;
+ }
+
+ connection.setDoOutput(true);
+
+ OutputStream outputStream = null;
+ try {
+ outputStream = new BufferedOutputStream(connection.getOutputStream());
+ if (shouldUseGzip) {
+ outputStream = new GZIPOutputStream(outputStream);
+ }
+
+ if (hasOnProgressCallbacks(requests)) {
+ ProgressNoopOutputStream countingStream = null;
+ countingStream = new ProgressNoopOutputStream(requests.getCallbackHandler());
+ processRequest(requests, null, numRequests, url, countingStream, shouldUseGzip);
+
+ int max = countingStream.getMaxProgress();
+ Map progressMap = countingStream.getProgressMap();
+
+ outputStream = new ProgressOutputStream(outputStream, requests, progressMap, max);
+ }
+
+ processRequest(requests, logger, numRequests, url, outputStream, shouldUseGzip);
+ } finally {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+
+ logger.log();
+ }
+
+ private static void processRequest(GraphRequestBatch requests, Logger logger, int numRequests,
+ URL url, OutputStream outputStream, boolean shouldUseGzip)
+ throws IOException, JSONException {
+ Serializer serializer = new Serializer(outputStream, logger, shouldUseGzip);
+
+ if (numRequests == 1) {
+ GraphRequest request = requests.get(0);
+
+ Map attachments = new HashMap();
+ for (String key : request.parameters.keySet()) {
+ Object value = request.parameters.get(key);
+ if (isSupportedAttachmentType(value)) {
+ attachments.put(key, new Attachment(request, value));
+ }
+ }
+
+ if (logger != null) {
+ logger.append(" Parameters:\n");
+ }
+ serializeParameters(request.parameters, serializer, request);
+
+ if (logger != null) {
+ logger.append(" Attachments:\n");
+ }
+ serializeAttachments(attachments, serializer);
+
+ if (request.graphObject != null) {
+ processGraphObject(request.graphObject, url.getPath(), serializer);
+ }
+ } else {
+ String batchAppID = getBatchAppId(requests);
+ if (Utility.isNullOrEmpty(batchAppID)) {
+ throw new FacebookException(
+ "App ID was not specified at the request or Settings.");
+ }
+
+ serializer.writeString(BATCH_APP_ID_PARAM, batchAppID);
+
+ // We write out all the requests as JSON, remembering which file attachments they have,
+ // then write out the attachments.
+ Map attachments = new HashMap();
+ serializeRequestsAsJSON(serializer, requests, attachments);
+
+ if (logger != null) {
+ logger.append(" Attachments:\n");
+ }
+ serializeAttachments(attachments, serializer);
+ }
+ }
+
+ private static boolean isMeRequest(String path) {
+ Matcher matcher = versionPattern.matcher(path);
+ if (matcher.matches()) {
+ // Group 1 contains the path aside from version
+ path = matcher.group(1);
+ }
+ if (path.startsWith("me/") || path.startsWith("/me/")) {
+ return true;
+ }
+ return false;
+ }
+
+ private static void processGraphObject(
+ JSONObject graphObject,
+ String path,
+ KeyValueSerializer serializer
+ ) throws IOException {
+ // In general, graph objects are passed by reference (ID/URL). But if this is an OG Action,
+ // we need to pass the entire values of the contents of the 'image' property, as they
+ // contain important metadata beyond just a URL. We don't have a 100% foolproof way of
+ // knowing if we are posting an OG Action, given that batched requests can have parameter
+ // substitution, but passing the OG Action type as a substituted parameter is unlikely.
+ // It looks like an OG Action if it's posted to me/namespace:action[?other=stuff].
+ boolean isOGAction = false;
+ if (isMeRequest(path)) {
+ int colonLocation = path.indexOf(":");
+ int questionMarkLocation = path.indexOf("?");
+ isOGAction = colonLocation > 3
+ && (questionMarkLocation == -1 || colonLocation < questionMarkLocation);
+ }
+
+ Iterator keyIterator = graphObject.keys();
+ while (keyIterator.hasNext()) {
+ String key = keyIterator.next();
+ Object value = graphObject.opt(key);
+ boolean passByValue = isOGAction && key.equalsIgnoreCase("image");
+ processGraphObjectProperty(key, value, serializer, passByValue);
+ }
+ }
+
+ private static void processGraphObjectProperty(
+ String key,
+ Object value,
+ KeyValueSerializer serializer,
+ boolean passByValue
+ ) throws IOException {
+ Class> valueClass = value.getClass();
+
+ if (JSONObject.class.isAssignableFrom(valueClass)) {
+ JSONObject jsonObject = (JSONObject) value;
+ if (passByValue) {
+ // We need to pass all properties of this object in key[propertyName] format.
+ @SuppressWarnings("unchecked")
+ Iterator keys = jsonObject.keys();
+ while (keys.hasNext()) {
+ String propertyName = keys.next();
+ String subKey = String.format("%s[%s]", key, propertyName);
+ processGraphObjectProperty(
+ subKey,
+ jsonObject.opt(propertyName),
+ serializer,
+ passByValue);
+ }
+ } else {
+ // Normal case is passing objects by reference, so just pass the ID or URL, if any,
+ // as the value for "key"
+ if (jsonObject.has("id")) {
+ processGraphObjectProperty(
+ key,
+ jsonObject.optString("id"),
+ serializer,
+ passByValue);
+ } else if (jsonObject.has("url")) {
+ processGraphObjectProperty(
+ key,
+ jsonObject.optString("url"),
+ serializer,
+ passByValue);
+ } else if (jsonObject.has(NativeProtocol.OPEN_GRAPH_CREATE_OBJECT_KEY)) {
+ processGraphObjectProperty(key, jsonObject.toString(), serializer, passByValue);
+ }
+ }
+ } else if (JSONArray.class.isAssignableFrom(valueClass)) {
+ JSONArray jsonArray = (JSONArray) value;
+ int length = jsonArray.length();
+ for (int i = 0; i < length; ++i) {
+ String subKey = String.format(Locale.ROOT, "%s[%d]", key, i);
+ processGraphObjectProperty(subKey, jsonArray.opt(i), serializer, passByValue);
+ }
+ } else if (String.class.isAssignableFrom(valueClass) ||
+ Number.class.isAssignableFrom(valueClass) ||
+ Boolean.class.isAssignableFrom(valueClass)) {
+ serializer.writeString(key, value.toString());
+ } else if (Date.class.isAssignableFrom(valueClass)) {
+ Date date = (Date) value;
+ // The "Events Timezone" platform migration affects what date/time formats Facebook
+ // accepts and returns. Apps created after 8/1/12 (or apps that have explicitly enabled
+ // the migration) should send/receive dates in ISO-8601 format. Pre-migration apps can
+ // send as Unix timestamps. Since the future is ISO-8601, that is what we support here.
+ // Apps that need pre-migration behavior can explicitly send these as integer timestamps
+ // rather than Dates.
+ final SimpleDateFormat iso8601DateFormat = new SimpleDateFormat(
+ ISO_8601_FORMAT_STRING,
+ Locale.US);
+ serializer.writeString(key, iso8601DateFormat.format(date));
+ }
+ }
+
+ private static void serializeParameters(
+ Bundle bundle,
+ Serializer serializer,
+ GraphRequest request
+ ) throws IOException {
+ Set keys = bundle.keySet();
+
+ for (String key : keys) {
+ Object value = bundle.get(key);
+ if (isSupportedParameterType(value)) {
+ serializer.writeObject(key, value, request);
+ }
+ }
+ }
+
+ private static void serializeAttachments(
+ Map attachments,
+ Serializer serializer
+ ) throws IOException {
+ Set keys = attachments.keySet();
+
+ for (String key : keys) {
+ Attachment attachment = attachments.get(key);
+ if (isSupportedAttachmentType(attachment.getValue())) {
+ serializer.writeObject(key, attachment.getValue(), attachment.getRequest());
+ }
+ }
+ }
+
+ private static void serializeRequestsAsJSON(
+ Serializer serializer,
+ Collection requests,
+ Map attachments
+ ) throws JSONException, IOException {
+ JSONArray batch = new JSONArray();
+ for (GraphRequest request : requests) {
+ request.serializeToBatch(batch, attachments);
+ }
+
+ serializer.writeRequestsAsJson(BATCH_PARAM, batch, requests);
+ }
+
+ private static String getMimeContentType() {
+ return String.format("multipart/form-data; boundary=%s", MIME_BOUNDARY);
+ }
+
+ private static volatile String userAgent;
+
+ private static String getUserAgent() {
+ if (userAgent == null) {
+ userAgent = String.format("%s.%s", USER_AGENT_BASE, FacebookSdkVersion.BUILD);
+ }
+
+ return userAgent;
+ }
+
+ private static String getBatchAppId(GraphRequestBatch batch) {
+ if (!Utility.isNullOrEmpty(batch.getBatchApplicationId())) {
+ return batch.getBatchApplicationId();
+ }
+
+ for (GraphRequest request : batch) {
+ AccessToken accessToken = request.accessToken;
+ if (accessToken != null) {
+ String applicationId = accessToken.getApplicationId();
+ if (applicationId != null) {
+ return applicationId;
+ }
+ }
+ }
+ if (!Utility.isNullOrEmpty(GraphRequest.defaultBatchApplicationId)) {
+ return GraphRequest.defaultBatchApplicationId;
+ }
+ return FacebookSdk.getApplicationId();
+ }
+
+ private static boolean isSupportedAttachmentType(Object value) {
+ return value instanceof Bitmap ||
+ value instanceof byte[] ||
+ value instanceof Uri ||
+ value instanceof ParcelFileDescriptor ||
+ value instanceof ParcelableResourceWithMimeType;
+ }
+
+ private static boolean isSupportedParameterType(Object value) {
+ return value instanceof String || value instanceof Boolean || value instanceof Number ||
+ value instanceof Date;
+ }
+
+ private static String parameterToString(Object value) {
+ if (value instanceof String) {
+ return (String) value;
+ } else if (value instanceof Boolean || value instanceof Number) {
+ return value.toString();
+ } else if (value instanceof Date) {
+ final SimpleDateFormat iso8601DateFormat = new SimpleDateFormat(
+ ISO_8601_FORMAT_STRING, Locale.US);
+ return iso8601DateFormat.format(value);
+ }
+ throw new IllegalArgumentException("Unsupported parameter type.");
+ }
+
+ private interface KeyValueSerializer {
+ void writeString(String key, String value) throws IOException;
+ }
+
+ private static class Serializer implements KeyValueSerializer {
+ private final OutputStream outputStream;
+ private final Logger logger;
+ private boolean firstWrite = true;
+ private boolean useUrlEncode = false;
+
+ public Serializer(OutputStream outputStream, Logger logger, boolean useUrlEncode) {
+ this.outputStream = outputStream;
+ this.logger = logger;
+ this.useUrlEncode = useUrlEncode;
+ }
+
+ public void writeObject(String key, Object value, GraphRequest request) throws IOException {
+ if (outputStream instanceof RequestOutputStream) {
+ ((RequestOutputStream) outputStream).setCurrentRequest(request);
+ }
+
+ if (isSupportedParameterType(value)) {
+ writeString(key, parameterToString(value));
+ } else if (value instanceof Bitmap) {
+ writeBitmap(key, (Bitmap) value);
+ } else if (value instanceof byte[]) {
+ writeBytes(key, (byte[]) value);
+ } else if (value instanceof Uri) {
+ writeContentUri(key, (Uri) value, null);
+ } else if (value instanceof ParcelFileDescriptor) {
+ writeFile(key, (ParcelFileDescriptor) value, null);
+ } else if (value instanceof ParcelableResourceWithMimeType) {
+ ParcelableResourceWithMimeType resourceWithMimeType =
+ (ParcelableResourceWithMimeType) value;
+ Parcelable resource = resourceWithMimeType.getResource();
+ String mimeType = resourceWithMimeType.getMimeType();
+ if (resource instanceof ParcelFileDescriptor) {
+ writeFile(key, (ParcelFileDescriptor) resource, mimeType);
+ } else if (resource instanceof Uri) {
+ writeContentUri(key, (Uri) resource, mimeType);
+ } else {
+ throw getInvalidTypeError();
+ }
+ } else {
+ throw getInvalidTypeError();
+ }
+ }
+
+ private RuntimeException getInvalidTypeError() {
+ return new IllegalArgumentException("value is not a supported type.");
+ }
+
+ public void writeRequestsAsJson(
+ String key,
+ JSONArray requestJsonArray,
+ Collection requests
+ ) throws IOException, JSONException {
+ if (!(outputStream instanceof RequestOutputStream)) {
+ writeString(key, requestJsonArray.toString());
+ return;
+ }
+
+ RequestOutputStream requestOutputStream = (RequestOutputStream) outputStream;
+ writeContentDisposition(key, null, null);
+ write("[");
+ int i = 0;
+ for (GraphRequest request : requests) {
+ JSONObject requestJson = requestJsonArray.getJSONObject(i);
+ requestOutputStream.setCurrentRequest(request);
+ if (i > 0) {
+ write(",%s", requestJson.toString());
+ } else {
+ write("%s", requestJson.toString());
+ }
+ i++;
+ }
+ write("]");
+ if (logger != null) {
+ logger.appendKeyValue(" " + key, requestJsonArray.toString());
+ }
+ }
+
+ public void writeString(String key, String value) throws IOException {
+ writeContentDisposition(key, null, null);
+ writeLine("%s", value);
+ writeRecordBoundary();
+ if (logger != null) {
+ logger.appendKeyValue(" " + key, value);
+ }
+ }
+
+ public void writeBitmap(String key, Bitmap bitmap) throws IOException {
+ writeContentDisposition(key, key, "image/png");
+ // Note: quality parameter is ignored for PNG
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
+ writeLine("");
+ writeRecordBoundary();
+ if (logger != null) {
+ logger.appendKeyValue(" " + key, "");
+ }
+ }
+
+ public void writeBytes(String key, byte[] bytes) throws IOException {
+ writeContentDisposition(key, key, "content/unknown");
+ this.outputStream.write(bytes);
+ writeLine("");
+ writeRecordBoundary();
+ if (logger != null) {
+ logger.appendKeyValue(
+ " " + key,
+ String.format(Locale.ROOT, "", bytes.length));
+ }
+ }
+
+ public void writeContentUri(String key, Uri contentUri, String mimeType)
+ throws IOException {
+ if (mimeType == null) {
+ mimeType = "content/unknown";
+ }
+ writeContentDisposition(key, key, mimeType);
+
+ InputStream inputStream = FacebookSdk
+ .getApplicationContext()
+ .getContentResolver()
+ .openInputStream(contentUri);
+
+ int totalBytes = 0;
+ if (outputStream instanceof ProgressNoopOutputStream) {
+ // If we are only counting bytes then skip reading the file
+ long contentSize = Utility.getContentSize(contentUri);
+
+ ((ProgressNoopOutputStream) outputStream).addProgress(contentSize);
+ } else {
+ totalBytes += Utility.copyAndCloseInputStream(inputStream, outputStream);
+ }
+
+ writeLine("");
+ writeRecordBoundary();
+ if (logger != null) {
+ logger.appendKeyValue(
+ " " + key,
+ String.format(Locale.ROOT, "", totalBytes));
+ }
+ }
+
+ public void writeFile(
+ String key,
+ ParcelFileDescriptor descriptor,
+ String mimeType
+ ) throws IOException {
+ if (mimeType == null) {
+ mimeType = "content/unknown";
+ }
+ writeContentDisposition(key, key, mimeType);
+
+ int totalBytes = 0;
+
+ if (outputStream instanceof ProgressNoopOutputStream) {
+ // If we are only counting bytes then skip reading the file
+ ((ProgressNoopOutputStream) outputStream).addProgress(descriptor.getStatSize());
+ } else {
+ ParcelFileDescriptor.AutoCloseInputStream inputStream =
+ new ParcelFileDescriptor.AutoCloseInputStream(descriptor);
+ totalBytes += Utility.copyAndCloseInputStream(inputStream, outputStream);
+ }
+ writeLine("");
+ writeRecordBoundary();
+ if (logger != null) {
+ logger.appendKeyValue(
+ " " + key,
+ String.format(Locale.ROOT, "", totalBytes));
+ }
+ }
+
+ public void writeRecordBoundary() throws IOException {
+ if (!useUrlEncode) {
+ writeLine("--%s", MIME_BOUNDARY);
+ } else {
+ this.outputStream.write("&".getBytes());
+ }
+ }
+
+ public void writeContentDisposition(
+ String name,
+ String filename,
+ String contentType
+ ) throws IOException {
+ if (!useUrlEncode) {
+ write("Content-Disposition: form-data; name=\"%s\"", name);
+ if (filename != null) {
+ write("; filename=\"%s\"", filename);
+ }
+ writeLine(""); // newline after Content-Disposition
+ if (contentType != null) {
+ writeLine("%s: %s", CONTENT_TYPE_HEADER, contentType);
+ }
+ writeLine(""); // blank line before content
+ } else {
+ this.outputStream.write(String.format("%s=", name).getBytes());
+ }
+ }
+
+ public void write(String format, Object... args) throws IOException {
+ if (!useUrlEncode) {
+ if (firstWrite) {
+ // Prepend all of our output with a boundary string.
+ this.outputStream.write("--".getBytes());
+ this.outputStream.write(MIME_BOUNDARY.getBytes());
+ this.outputStream.write("\r\n".getBytes());
+ firstWrite = false;
+ }
+ this.outputStream.write(String.format(format, args).getBytes());
+ } else {
+ this.outputStream.write(
+ URLEncoder.encode(
+ String.format(Locale.US, format, args), "UTF-8").getBytes());
+ }
+ }
+
+ public void writeLine(String format, Object... args) throws IOException {
+ write(format, args);
+ if (!useUrlEncode) {
+ write("\r\n");
+ }
+ }
+
+ }
+
+ /**
+ * Specifies the interface that consumers of the Request class can implement in order to be
+ * notified when a particular request completes, either successfully or with an error.
+ */
+ public interface Callback {
+ /**
+ * The method that will be called when a request completes.
+ *
+ * @param response the Response of this request, which may include error information if the
+ * request was unsuccessful
+ */
+ void onCompleted(GraphResponse response);
+ }
+
+ /**
+ * Specifies the interface that consumers of the Request class can implement in order to be
+ * notified when a progress is made on a particular request. The frequency of the callbacks can
+ * be controlled using {@link FacebookSdk#setOnProgressThreshold(long)}
+ */
+ public interface OnProgressCallback extends Callback {
+ /**
+ * The method that will be called when progress is made.
+ *
+ * @param current the current value of the progress of the request.
+ * @param max the maximum value (target) value that the progress will have.
+ */
+ void onProgress(long current, long max);
+ }
+
+ /**
+ * Callback for requests that result in an array of JSONObjects.
+ */
+ public interface GraphJSONArrayCallback {
+ /**
+ * The method that will be called when the request completes.
+ *
+ * @param objects the list of GraphObjects representing the returned objects, or null
+ * @param response the Response of this request, which may include error information if the
+ * request was unsuccessful
+ */
+ void onCompleted(JSONArray objects, GraphResponse response);
+ }
+
+ /**
+ * Callback for requests that result in a JSONObject.
+ */
+ public interface GraphJSONObjectCallback {
+ /**
+ * The method that will be called when the request completes.
+ *
+ * @param object the GraphObject representing the returned object, or null
+ * @param response the Response of this request, which may include error information if the
+ * request was unsuccessful
+ */
+ void onCompleted(JSONObject object, GraphResponse response);
+ }
+
+ /**
+ * Used during serialization for the graph request.
+ * @param The Parcelable type parameter.
+ */
+ public static class ParcelableResourceWithMimeType
+ implements Parcelable {
+ private final String mimeType;
+ private final RESOURCE resource;
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public RESOURCE getResource() {
+ return resource;
+ }
+
+ public int describeContents() {
+ return CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mimeType);
+ out.writeParcelable(resource, flags);
+ }
+
+ @SuppressWarnings("unused")
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public ParcelableResourceWithMimeType createFromParcel(Parcel in) {
+ return new ParcelableResourceWithMimeType(in);
+ }
+
+ public ParcelableResourceWithMimeType[] newArray(int size) {
+ return new ParcelableResourceWithMimeType[size];
+ }
+ };
+
+ /**
+ * The constructor.
+ * @param resource The resource to parcel.
+ * @param mimeType The mime type.
+ */
+ public ParcelableResourceWithMimeType(
+ RESOURCE resource,
+ String mimeType
+ ) {
+ this.mimeType = mimeType;
+ this.resource = resource;
+ }
+
+ private ParcelableResourceWithMimeType(Parcel in) {
+ mimeType = in.readString();
+ resource = in.readParcelable(FacebookSdk.getApplicationContext().getClassLoader());
+ }
+ }
+}
diff --git a/platforms/android/FacebookLib/src/com/facebook/RequestAsyncTask.java b/platforms/android/FacebookLib/src/com/facebook/GraphRequestAsyncTask.java
old mode 100644
new mode 100755
similarity index 54%
rename from platforms/android/FacebookLib/src/com/facebook/RequestAsyncTask.java
rename to platforms/android/FacebookLib/src/com/facebook/GraphRequestAsyncTask.java
index 62a2cf158..6b27bc8af
--- a/platforms/android/FacebookLib/src/com/facebook/RequestAsyncTask.java
+++ b/platforms/android/FacebookLib/src/com/facebook/GraphRequestAsyncTask.java
@@ -1,22 +1,25 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
-import android.annotation.TargetApi;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
@@ -32,12 +35,12 @@
* Defines an AsyncTask suitable for executing a Request in the background. May be subclassed
* by applications having unique threading model needs.
*/
-public class RequestAsyncTask extends AsyncTask> {
- private static final String TAG = RequestAsyncTask.class.getCanonicalName();
+public class GraphRequestAsyncTask extends AsyncTask> {
+ private static final String TAG = GraphRequestAsyncTask.class.getCanonicalName();
private static Method executeOnExecutorMethod;
private final HttpURLConnection connection;
- private final RequestBatch requests;
+ private final GraphRequestBatch requests;
private Exception exception;
@@ -45,7 +48,8 @@ public class RequestAsyncTask extends AsyncTask> {
for (Method method : AsyncTask.class.getMethods()) {
if ("executeOnExecutor".equals(method.getName())) {
Class>[] parameters = method.getParameterTypes();
- if ((parameters.length == 2) && (parameters[0] == Executor.class) && parameters[1].isArray()) {
+ if ((parameters.length == 2) &&
+ (parameters[0] == Executor.class) && parameters[1].isArray()) {
executeOnExecutorMethod = method;
break;
}
@@ -54,32 +58,32 @@ public class RequestAsyncTask extends AsyncTask> {
}
/**
- * Constructor. Serialization of the requests will be done in the background, so any serialization-
- * related errors will be returned via the Response.getException() method.
+ * Constructor. Serialization of the requests will be done in the background, so any
+ * serialization- related errors will be returned via the Response.getException() method.
*
* @param requests the requests to execute
*/
- public RequestAsyncTask(Request... requests) {
- this(null, new RequestBatch(requests));
+ public GraphRequestAsyncTask(GraphRequest... requests) {
+ this(null, new GraphRequestBatch(requests));
}
/**
- * Constructor. Serialization of the requests will be done in the background, so any serialization-
- * related errors will be returned via the Response.getException() method.
+ * Constructor. Serialization of the requests will be done in the background, so any
+ * serialization- related errors will be returned via the Response.getException() method.
*
* @param requests the requests to execute
*/
- public RequestAsyncTask(Collection requests) {
- this(null, new RequestBatch(requests));
+ public GraphRequestAsyncTask(Collection requests) {
+ this(null, new GraphRequestBatch(requests));
}
/**
- * Constructor. Serialization of the requests will be done in the background, so any serialization-
- * related errors will be returned via the Response.getException() method.
+ * Constructor. Serialization of the requests will be done in the background, so any
+ * serialization- related errors will be returned via the Response.getException() method.
*
* @param requests the requests to execute
*/
- public RequestAsyncTask(RequestBatch requests) {
+ public GraphRequestAsyncTask(GraphRequestBatch requests) {
this(null, requests);
}
@@ -92,8 +96,8 @@ public RequestAsyncTask(RequestBatch requests) {
* @param connection the HTTP connection to use to execute the requests
* @param requests the requests to execute
*/
- public RequestAsyncTask(HttpURLConnection connection, Request... requests) {
- this(connection, new RequestBatch(requests));
+ public GraphRequestAsyncTask(HttpURLConnection connection, GraphRequest... requests) {
+ this(connection, new GraphRequestBatch(requests));
}
/**
@@ -105,8 +109,8 @@ public RequestAsyncTask(HttpURLConnection connection, Request... requests) {
* @param connection the HTTP connection to use to execute the requests
* @param requests the requests to execute
*/
- public RequestAsyncTask(HttpURLConnection connection, Collection requests) {
- this(connection, new RequestBatch(requests));
+ public GraphRequestAsyncTask(HttpURLConnection connection, Collection requests) {
+ this(connection, new GraphRequestBatch(requests));
}
/**
@@ -118,7 +122,7 @@ public RequestAsyncTask(HttpURLConnection connection, Collection reques
* @param connection the HTTP connection to use to execute the requests
* @param requests the requests to execute
*/
- public RequestAsyncTask(HttpURLConnection connection, RequestBatch requests) {
+ public GraphRequestAsyncTask(HttpURLConnection connection, GraphRequestBatch requests) {
this.requests = requests;
this.connection = connection;
}
@@ -127,14 +131,20 @@ protected final Exception getException() {
return exception;
}
- protected final RequestBatch getRequests() {
+ protected final GraphRequestBatch getRequests() {
return requests;
}
@Override
public String toString() {
- return new StringBuilder().append("{RequestAsyncTask: ").append(" connection: ").append(connection)
- .append(", requests: ").append(requests).append("}").toString();
+ return new StringBuilder()
+ .append("{RequestAsyncTask: ")
+ .append(" connection: ")
+ .append(connection)
+ .append(", requests: ")
+ .append(requests)
+ .append("}")
+ .toString();
}
@Override
@@ -142,27 +152,30 @@ protected void onPreExecute() {
super.onPreExecute();
if (requests.getCallbackHandler() == null) {
- // We want any callbacks to go to a handler on this thread unless a handler has already been specified.
+ // We want any callbacks to go to a handler on this thread unless a handler has already
+ // been specified.
requests.setCallbackHandler(new Handler());
}
}
@Override
- protected void onPostExecute(List result) {
+ protected void onPostExecute(List result) {
super.onPostExecute(result);
if (exception != null) {
- Log.d(TAG, String.format("onPostExecute: exception encountered during request: %s", exception.getMessage()));
+ Log.d(TAG, String.format(
+ "onPostExecute: exception encountered during request: %s",
+ exception.getMessage()));
}
}
@Override
- protected List doInBackground(Void... params) {
+ protected List doInBackground(Void... params) {
try {
if (connection == null) {
return requests.executeAndWait();
} else {
- return Request.executeConnectionAndWait(connection, requests);
+ return GraphRequest.executeConnectionAndWait(connection, requests);
}
} catch (Exception e) {
exception = e;
@@ -170,10 +183,10 @@ protected List doInBackground(Void... params) {
}
}
- RequestAsyncTask executeOnSettingsExecutor() {
+ GraphRequestAsyncTask executeOnSettingsExecutor() {
if (executeOnExecutorMethod != null) {
try {
- executeOnExecutorMethod.invoke(this, Settings.getExecutor(), null);
+ executeOnExecutorMethod.invoke(this, FacebookSdk.getExecutor(), null);
} catch (InvocationTargetException e) {
// fall-through
} catch (IllegalAccessException e) {
diff --git a/platforms/android/FacebookLib/src/com/facebook/RequestBatch.java b/platforms/android/FacebookLib/src/com/facebook/GraphRequestBatch.java
old mode 100644
new mode 100755
similarity index 58%
rename from platforms/android/FacebookLib/src/com/facebook/RequestBatch.java
rename to platforms/android/FacebookLib/src/com/facebook/GraphRequestBatch.java
index 9ecd7be18..ff4d7a05e
--- a/platforms/android/FacebookLib/src/com/facebook/RequestBatch.java
+++ b/platforms/android/FacebookLib/src/com/facebook/GraphRequestBatch.java
@@ -1,17 +1,21 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
@@ -22,13 +26,14 @@
import java.util.concurrent.atomic.AtomicInteger;
/**
- * RequestBatch contains a list of Request objects that can be sent to Facebook in a single round-trip.
+ * RequestBatch contains a list of Request objects that can be sent to Facebook in a single
+ * round-trip.
*/
-public class RequestBatch extends AbstractList {
+public class GraphRequestBatch extends AbstractList {
private static AtomicInteger idGenerator = new AtomicInteger();
private Handler callbackHandler;
- private List requests = new ArrayList();
+ private List requests = new ArrayList();
private int timeoutInMilliseconds = 0;
private final String id = Integer.valueOf(idGenerator.incrementAndGet()).toString();
private List callbacks = new ArrayList();
@@ -37,23 +42,23 @@ public class RequestBatch extends AbstractList {
/**
* Constructor. Creates an empty batch.
*/
- public RequestBatch() {
- this.requests = new ArrayList();
+ public GraphRequestBatch() {
+ this.requests = new ArrayList();
}
/**
* Constructor.
* @param requests the requests to add to the batch
*/
- public RequestBatch(Collection requests) {
- this.requests = new ArrayList(requests);
+ public GraphRequestBatch(Collection requests) {
+ this.requests = new ArrayList(requests);
}
/**
* Constructor.
* @param requests the requests to add to the batch
*/
- public RequestBatch(Request... requests) {
+ public GraphRequestBatch(GraphRequest... requests) {
this.requests = Arrays.asList(requests);
}
@@ -61,8 +66,8 @@ public RequestBatch(Request... requests) {
* Constructor.
* @param requests the requests to add to the batch
*/
- public RequestBatch(RequestBatch requests) {
- this.requests = new ArrayList(requests);
+ public GraphRequestBatch(GraphRequestBatch requests) {
+ this.requests = new ArrayList(requests);
this.callbackHandler = requests.callbackHandler;
this.timeoutInMilliseconds = requests.timeoutInMilliseconds;
this.callbacks = new ArrayList(requests.callbacks);
@@ -88,7 +93,8 @@ public void setTimeout(int timeoutInMilliseconds) {
}
/**
- * Adds a batch-level callback which will be called when the entire batch has finished executing.
+ * Adds a batch-level callback which will be called when the entire batch has finished
+ * executing.
*
* @param callback the callback
*/
@@ -108,12 +114,12 @@ public void removeCallback(Callback callback) {
}
@Override
- public final boolean add(Request request) {
+ public final boolean add(GraphRequest request) {
return requests.add(request);
}
@Override
- public final void add(int location, Request request) {
+ public final void add(int location, GraphRequest request) {
requests.add(location, request);
}
@@ -123,17 +129,17 @@ public final void clear() {
}
@Override
- public final Request get(int i) {
+ public final GraphRequest get(int i) {
return requests.get(i);
}
@Override
- public final Request remove(int location) {
+ public final GraphRequest remove(int location) {
return requests.remove(location);
}
@Override
- public final Request set(int location, Request request) {
+ public final GraphRequest set(int location, GraphRequest request) {
return requests.set(location, request);
}
@@ -154,7 +160,7 @@ final void setCallbackHandler(Handler callbackHandler) {
this.callbackHandler = callbackHandler;
}
- final List getRequests() {
+ final List getRequests() {
return requests;
}
@@ -162,11 +168,19 @@ final List getCallbacks() {
return callbacks;
}
- final String getBatchApplicationId() {
+ /**
+ * Getter for the batch application id.
+ * @return the batch application id.
+ */
+ final public String getBatchApplicationId() {
return batchApplicationId;
}
- final void setBatchApplicationId(String batchApplicationId) {
+ /**
+ * Setter for the batch application id.
+ * @param batchApplicationId The batch application id.
+ */
+ final public void setBatchApplicationId(String batchApplicationId) {
this.batchApplicationId = batchApplicationId;
}
@@ -175,15 +189,15 @@ final void setBatchApplicationId(String batchApplicationId) {
*
* This should only be used if you have transitioned off the UI thread.
*
- * @return a list of Response objects representing the results of the requests; responses are returned in the same
- * order as the requests were specified.
- *
- * @throws FacebookException
- * If there was an error in the protocol used to communicate with the service
+ * @return a list of Response objects representing the results of the requests; responses are
+ * returned in the same order as the requests were specified.
+ * @throws FacebookException If there was an error in the protocol used to communicate
+ * with the service
* @throws IllegalArgumentException if the passed in RequestBatch is empty
- * @throws NullPointerException if the passed in RequestBatch or any of its contents are null
+ * @throws NullPointerException if the passed in RequestBatch or any of its contents are
+ * null
*/
- public final List executeAndWait() {
+ public final List executeAndWait() {
return executeAndWaitImpl();
}
@@ -191,7 +205,7 @@ public final List executeAndWait() {
* Executes this batch asynchronously. This function will return immediately, and the batch will
* be processed on a separate thread. In order to process results of a request, or determine
* whether a request succeeded or failed, a callback must be specified (see
- * {@link Request#setCallback(com.facebook.Request.Callback)})
+ * {@link GraphRequest#setCallback(GraphRequest.Callback)})
*
* This should only be called from the UI thread.
*
@@ -200,13 +214,14 @@ public final List executeAndWait() {
* @throws IllegalArgumentException if this batch is empty
* @throws NullPointerException if any of the contents of this batch are null
*/
- public final RequestAsyncTask executeAsync() {
+ public final GraphRequestAsyncTask executeAsync() {
return executeAsyncImpl();
}
/**
- * Specifies the interface that consumers of the RequestBatch class can implement in order to be notified when the
- * entire batch completes execution. It will be called after all per-Request callbacks are called.
+ * Specifies the interface that consumers of the RequestBatch class can implement in order to be
+ * notified when the entire batch completes execution. It will be called after all per-Request
+ * callbacks are called.
*/
public interface Callback {
/**
@@ -214,13 +229,13 @@ public interface Callback {
*
* @param batch the RequestBatch containing the Requests which were executed
*/
- void onBatchCompleted(RequestBatch batch);
+ void onBatchCompleted(GraphRequestBatch batch);
}
/**
- * Specifies the interface that consumers of the RequestBatch class can implement in order to be notified when the
- * batch makes progress. The frequency of the callbacks can be controlled using
- * {@link com.facebook.Settings#setOnProgressThreshold(long)}.
+ * Specifies the interface that consumers of the RequestBatch class can implement in order to be
+ * notified when the batch makes progress. The frequency of the callbacks can be controlled
+ * using {@link FacebookSdk#setOnProgressThreshold(long)}.
*/
public interface OnProgressCallback extends Callback {
/**
@@ -230,14 +245,14 @@ public interface OnProgressCallback extends Callback {
* @param current the current value of the progress
* @param max the max (target) value of the progress
*/
- void onBatchProgress(RequestBatch batch, long current, long max);
+ void onBatchProgress(GraphRequestBatch batch, long current, long max);
}
- List executeAndWaitImpl() {
- return Request.executeBatchAndWait(this);
+ List executeAndWaitImpl() {
+ return GraphRequest.executeBatchAndWait(this);
}
- RequestAsyncTask executeAsyncImpl() {
- return Request.executeBatchAsync(this);
+ GraphRequestAsyncTask executeAsyncImpl() {
+ return GraphRequest.executeBatchAsync(this);
}
}
diff --git a/platforms/android/FacebookLib/src/com/facebook/GraphResponse.java b/platforms/android/FacebookLib/src/com/facebook/GraphResponse.java
new file mode 100755
index 000000000..9793e03b5
--- /dev/null
+++ b/platforms/android/FacebookLib/src/com/facebook/GraphResponse.java
@@ -0,0 +1,465 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright 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.
+ */
+
+package com.facebook;
+
+import com.facebook.internal.FacebookRequestErrorClassification;
+import com.facebook.internal.Logger;
+import com.facebook.internal.Utility;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Encapsulates the response, successful or otherwise, of a call to the Facebook platform.
+ */
+public class GraphResponse {
+ private final HttpURLConnection connection;
+ private final JSONObject graphObject;
+ private final JSONArray graphObjectArray;
+ private final FacebookRequestError error;
+ private final String rawResponse;
+ private final GraphRequest request;
+
+ /**
+ * Property name of non-JSON results in the GraphObject. Certain calls to Facebook result in a
+ * non-JSON response (e.g., the string literal "true" or "false"). To present a consistent way
+ * of accessing results, these are represented as a GraphObject with a single string property
+ * with this name.
+ */
+ public static final String NON_JSON_RESPONSE_PROPERTY = "FACEBOOK_NON_JSON_RESULT";
+
+ // From v2.1 of the Graph API, write endpoints will now return valid JSON with the result as the
+ // value for the "success" key
+ public static final String SUCCESS_KEY = "success";
+
+ private static final String CODE_KEY = "code";
+ private static final String BODY_KEY = "body";
+
+ private static final String RESPONSE_LOG_TAG = "Response";
+
+ GraphResponse(
+ GraphRequest request,
+ HttpURLConnection connection,
+ String rawResponse,
+ JSONObject graphObject) {
+ this(request, connection, rawResponse, graphObject, null, null);
+ }
+
+ GraphResponse(
+ GraphRequest request,
+ HttpURLConnection connection,
+ String rawResponse,
+ JSONArray graphObjects) {
+ this(request, connection, rawResponse, null, graphObjects, null);
+ }
+
+ GraphResponse(
+ GraphRequest request,
+ HttpURLConnection connection,
+ FacebookRequestError error) {
+ this(request, connection, null, null, null, error);
+ }
+
+ GraphResponse(
+ GraphRequest request,
+ HttpURLConnection connection,
+ String rawResponse,
+ JSONObject graphObject,
+ JSONArray graphObjects,
+ FacebookRequestError error) {
+ this.request = request;
+ this.connection = connection;
+ this.rawResponse = rawResponse;
+ this.graphObject = graphObject;
+ this.graphObjectArray = graphObjects;
+ this.error = error;
+ }
+
+ /**
+ * Returns information about any errors that may have occurred during the request.
+ *
+ * @return the error from the server, or null if there was no server error
+ */
+ public final FacebookRequestError getError() {
+ return error;
+ }
+
+ /**
+ * The response returned for this request, if it's in object form.
+ *
+ * @return the returned JSON object, or null if none was returned (or if the result was a JSON
+ * array)
+ */
+ public final JSONObject getJSONObject() {
+ return graphObject;
+ }
+
+
+ /**
+ * The response returned for this request, if it's in array form.
+ *
+ * @return the returned JSON array, or null if none was returned (or if the result was a JSON
+ * object)
+ */
+ public final JSONArray getJSONArray() {
+ return graphObjectArray;
+ }
+
+ /**
+ * Returns the HttpURLConnection that this response was generated from. If the response was
+ * retrieved from the cache, this will be null.
+ *
+ * @return the connection, or null
+ */
+ public final HttpURLConnection getConnection() {
+ return connection;
+ }
+
+ /**
+ * Returns the request that this response is for.
+ *
+ * @return the request that this response is for
+ */
+ public GraphRequest getRequest() {
+ return request;
+ }
+
+ /**
+ * Returns the server response as a String that this response is for.
+ *
+ * @return A String representation of the actual response from the server
+ */
+ public String getRawResponse() {
+ return rawResponse;
+ }
+
+ /**
+ * Indicates whether paging is being done forward or backward.
+ */
+ public enum PagingDirection {
+ /**
+ * Indicates that paging is being performed in the forward direction.
+ */
+ NEXT,
+ /**
+ * Indicates that paging is being performed in the backward direction.
+ */
+ PREVIOUS
+ }
+
+ /**
+ * If a Response contains results that contain paging information, returns a new
+ * Request that will retrieve the next page of results, in whichever direction
+ * is desired. If no paging information is available, returns null.
+ *
+ * @param direction enum indicating whether to page forward or backward
+ * @return a Request that will retrieve the next page of results in the desired
+ * direction, or null if no paging information is available
+ */
+ public GraphRequest getRequestForPagedResults(PagingDirection direction) {
+ String link = null;
+ if (graphObject != null) {
+ JSONObject pagingInfo = graphObject.optJSONObject("paging");
+ if (pagingInfo != null) {
+ if (direction == PagingDirection.NEXT) {
+ link = pagingInfo.optString("next");
+ } else {
+ link = pagingInfo.optString("previous");
+ }
+ }
+ }
+ if (Utility.isNullOrEmpty(link)) {
+ return null;
+ }
+
+ if (link != null && link.equals(request.getUrlForSingleRequest())) {
+ // We got the same "next" link as we just tried to retrieve. This could happen if cached
+ // data is invalid. All we can do in this case is pretend we have finished.
+ return null;
+ }
+
+ GraphRequest pagingRequest;
+ try {
+ pagingRequest = new GraphRequest(request.getAccessToken(), new URL(link));
+ } catch (MalformedURLException e) {
+ return null;
+ }
+
+ return pagingRequest;
+ }
+
+ /**
+ * Provides a debugging string for this response.
+ */
+ @Override
+ public String toString() {
+ String responseCode;
+ try {
+ responseCode = String.format(
+ Locale.US,
+ "%d",
+ (connection != null) ? connection.getResponseCode() : 200);
+ } catch (IOException e) {
+ responseCode = "unknown";
+ }
+
+ return new StringBuilder()
+ .append("{Response: ")
+ .append(" responseCode: ")
+ .append(responseCode)
+ .append(", graphObject: ")
+ .append(graphObject)
+ .append(", error: ")
+ .append(error)
+ .append("}")
+ .toString();
+ }
+
+ @SuppressWarnings("resource")
+ static List fromHttpConnection(
+ HttpURLConnection connection,
+ GraphRequestBatch requests) {
+ InputStream stream = null;
+
+ try {
+ if (connection.getResponseCode() >= 400) {
+ stream = connection.getErrorStream();
+ } else {
+ stream = connection.getInputStream();
+ }
+
+ return createResponsesFromStream(stream, connection, requests);
+ } catch (FacebookException facebookException) {
+ Logger.log(
+ LoggingBehavior.REQUESTS,
+ RESPONSE_LOG_TAG,
+ "Response : %s",
+ facebookException);
+ return constructErrorResponses(requests, connection, facebookException);
+ } catch (JSONException exception) {
+ Logger.log(
+ LoggingBehavior.REQUESTS,
+ RESPONSE_LOG_TAG,
+ "Response : %s",
+ exception);
+ return constructErrorResponses(requests, connection, new FacebookException(exception));
+ } catch (IOException exception) {
+ Logger.log(
+ LoggingBehavior.REQUESTS,
+ RESPONSE_LOG_TAG,
+ "Response : %s",
+ exception);
+ return constructErrorResponses(requests, connection, new FacebookException(exception));
+ } catch (SecurityException exception) {
+ Logger.log(
+ LoggingBehavior.REQUESTS,
+ RESPONSE_LOG_TAG,
+ "Response : %s",
+ exception);
+ return constructErrorResponses(requests, connection, new FacebookException(exception));
+ } finally {
+ Utility.closeQuietly(stream);
+ }
+ }
+
+ static List createResponsesFromStream(
+ InputStream stream,
+ HttpURLConnection connection,
+ GraphRequestBatch requests
+ ) throws FacebookException, JSONException, IOException {
+
+ String responseString = Utility.readStreamToString(stream);
+ Logger.log(LoggingBehavior.INCLUDE_RAW_RESPONSES, RESPONSE_LOG_TAG,
+ "Response (raw)\n Size: %d\n Response:\n%s\n", responseString.length(),
+ responseString);
+
+ return createResponsesFromString(responseString, connection, requests);
+ }
+
+ static List createResponsesFromString(
+ String responseString,
+ HttpURLConnection connection,
+ GraphRequestBatch requests
+ ) throws FacebookException, JSONException, IOException {
+ JSONTokener tokener = new JSONTokener(responseString);
+ Object resultObject = tokener.nextValue();
+
+ List responses = createResponsesFromObject(
+ connection,
+ requests,
+ resultObject);
+ Logger.log(
+ LoggingBehavior.REQUESTS,
+ RESPONSE_LOG_TAG,
+ "Response\n Id: %s\n Size: %d\n Responses:\n%s\n",
+ requests.getId(),
+ responseString.length(),
+ responses);
+
+ return responses;
+ }
+
+ private static List createResponsesFromObject(
+ HttpURLConnection connection,
+ List requests,
+ Object object
+ ) throws FacebookException, JSONException {
+ int numRequests = requests.size();
+ List responses = new ArrayList(numRequests);
+ Object originalResult = object;
+
+ if (numRequests == 1) {
+ GraphRequest request = requests.get(0);
+ try {
+ // Single request case -- the entire response is the result, wrap it as "body" so we
+ // can handle it the same as we do in the batched case. We get the response code
+ // from the actual HTTP response, as opposed to the batched case where it is
+ // returned as a "code" element.
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put(BODY_KEY, object);
+ int responseCode = (connection != null) ? connection.getResponseCode() : 200;
+ jsonObject.put(CODE_KEY, responseCode);
+
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.put(jsonObject);
+
+ // Pretend we got an array of 1 back.
+ object = jsonArray;
+ } catch (JSONException e) {
+ responses.add(
+ new GraphResponse(
+ request,
+ connection,
+ new FacebookRequestError(connection, e)));
+ } catch (IOException e) {
+ responses.add(
+ new GraphResponse(
+ request,
+ connection,
+ new FacebookRequestError(connection, e)));
+ }
+ }
+
+ if (!(object instanceof JSONArray) || ((JSONArray) object).length() != numRequests) {
+ FacebookException exception = new FacebookException("Unexpected number of results");
+ throw exception;
+ }
+
+ JSONArray jsonArray = (JSONArray) object;
+
+ for (int i = 0; i < jsonArray.length(); ++i) {
+ GraphRequest request = requests.get(i);
+ try {
+ Object obj = jsonArray.get(i);
+ responses.add(
+ createResponseFromObject(
+ request,
+ connection,
+ obj,
+ originalResult));
+ } catch (JSONException e) {
+ responses.add(
+ new GraphResponse(
+ request,
+ connection,
+ new FacebookRequestError(connection, e)));
+ } catch (FacebookException e) {
+ responses.add(
+ new GraphResponse(
+ request,
+ connection,
+ new FacebookRequestError(connection, e)));
+ }
+ }
+
+ return responses;
+ }
+
+ private static GraphResponse createResponseFromObject(
+ GraphRequest request,
+ HttpURLConnection connection,
+ Object object,
+ Object originalResult
+ ) throws JSONException {
+ if (object instanceof JSONObject) {
+ JSONObject jsonObject = (JSONObject) object;
+
+ FacebookRequestError error =
+ FacebookRequestError.checkResponseAndCreateError(
+ jsonObject,
+ originalResult,
+ connection);
+ if (error != null) {
+ if (error.getErrorCode() == FacebookRequestErrorClassification.EC_INVALID_TOKEN
+ && Utility.isCurrentAccessToken(request.getAccessToken())) {
+ AccessToken.setCurrentAccessToken(null);
+ }
+ return new GraphResponse(request, connection, error);
+ }
+
+ Object body = Utility.getStringPropertyAsJSON(
+ jsonObject,
+ BODY_KEY,
+ NON_JSON_RESPONSE_PROPERTY);
+
+ if (body instanceof JSONObject) {
+ return new GraphResponse(request, connection, body.toString(), (JSONObject)body);
+ } else if (body instanceof JSONArray) {
+ return new GraphResponse(request, connection, body.toString(), (JSONArray)body);
+ }
+ // We didn't get a body we understand how to handle, so pretend we got nothing.
+ object = JSONObject.NULL;
+ }
+
+ if (object == JSONObject.NULL) {
+ return new GraphResponse(request, connection, object.toString(), (JSONObject)null);
+ } else {
+ throw new FacebookException("Got unexpected object type in response, class: "
+ + object.getClass().getSimpleName());
+ }
+ }
+
+ static List constructErrorResponses(
+ List requests,
+ HttpURLConnection connection,
+ FacebookException error) {
+ int count = requests.size();
+ List responses = new ArrayList(count);
+ for (int i = 0; i < count; ++i) {
+ GraphResponse response = new GraphResponse(
+ requests.get(i),
+ connection,
+ new FacebookRequestError(connection, error));
+ responses.add(response);
+ }
+ return responses;
+ }
+}
diff --git a/platforms/android/FacebookLib/src/com/facebook/HttpMethod.java b/platforms/android/FacebookLib/src/com/facebook/HttpMethod.java
old mode 100644
new mode 100755
index bf355bd9d..63b287106
--- a/platforms/android/FacebookLib/src/com/facebook/HttpMethod.java
+++ b/platforms/android/FacebookLib/src/com/facebook/HttpMethod.java
@@ -1,17 +1,21 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
diff --git a/platforms/android/FacebookLib/src/com/facebook/InsightsLogger.java b/platforms/android/FacebookLib/src/com/facebook/InsightsLogger.java
deleted file mode 100644
index 168125f73..000000000
--- a/platforms/android/FacebookLib/src/com/facebook/InsightsLogger.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/**
- * 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.content.Context;
-import android.os.Bundle;
-import com.facebook.internal.Logger;
-
-import java.math.BigDecimal;
-import java.util.Currency;
-
-/**
- * This class is deprecated. Please use {@link AppEventsLogger} instead.
- */
-@Deprecated
-public class InsightsLogger {
- private static final String EVENT_PARAMETER_PIXEL_ID = "fb_offsite_pixel_id";
- private static final String EVENT_PARAMETER_PIXEL_VALUE = "fb_offsite_pixel_value";
-
- private static final String EVENT_NAME_LOG_CONVERSION_PIXEL = "fb_log_offsite_pixel";
-
- private AppEventsLogger appEventsLogger;
-
- private InsightsLogger(Context context, String applicationId, Session session) {
- appEventsLogger = AppEventsLogger.newLogger(context, applicationId, session);
- }
-
- /**
- * Deprecated. Please use {@link AppEventsLogger} instead.
- */
- public static InsightsLogger newLogger(Context context, String clientToken) {
- return new InsightsLogger(context, null, null);
- }
-
- /**
- * Deprecated. Please use {@link AppEventsLogger} instead.
- */
- public static InsightsLogger newLogger(Context context, String clientToken, String applicationId) {
- return new InsightsLogger(context, applicationId, null);
- }
-
- /**
- * Deprecated. Please use {@link AppEventsLogger} instead.
- */
- public static InsightsLogger newLogger(Context context, String clientToken, String applicationId, Session session) {
- return new InsightsLogger(context, applicationId, session);
- }
-
- /**
- * Deprecated. Please use {@link AppEventsLogger} instead.
- */
- public void logPurchase(BigDecimal purchaseAmount, Currency currency) {
- logPurchase(purchaseAmount, currency, null);
- }
-
- /**
- * Deprecated. Please use {@link AppEventsLogger} instead.
- */
- public void logPurchase(BigDecimal purchaseAmount, Currency currency, Bundle parameters) {
- appEventsLogger.logPurchase(purchaseAmount, currency, parameters);
- }
-
- /**
- * Deprecated. Please use {@link AppEventsLogger} instead.
- */
- public void logConversionPixel(String pixelId, double valueOfPixel) {
-
- if (pixelId == null) {
- Logger.log(LoggingBehavior.DEVELOPER_ERRORS, "Insights", "pixelID cannot be null");
- return;
- }
-
- Bundle parameters = new Bundle();
- parameters.putString(EVENT_PARAMETER_PIXEL_ID, pixelId);
- parameters.putDouble(EVENT_PARAMETER_PIXEL_VALUE, valueOfPixel);
-
- appEventsLogger.logEvent(EVENT_NAME_LOG_CONVERSION_PIXEL, valueOfPixel, parameters);
- AppEventsLogger.eagerFlush();
- }
-}
diff --git a/platforms/android/FacebookLib/src/com/facebook/LegacyHelper.java b/platforms/android/FacebookLib/src/com/facebook/LegacyHelper.java
deleted file mode 100644
index dcdfd8561..000000000
--- a/platforms/android/FacebookLib/src/com/facebook/LegacyHelper.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * 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.os.Bundle;
-
-/**
- * LegacyHelper is solely for the use of other packages within the Facebook SDK for Android. Use of
- * any of the methods in this class is unsupported, and they may be modified or removed without warning at
- * any time.
- */
-public class LegacyHelper {
- @Deprecated
- public static void extendTokenCompleted(Session session, Bundle bundle) {
- session.extendTokenCompleted(bundle);
- }
-}
diff --git a/platforms/android/FacebookLib/src/com/facebook/SharedPreferencesTokenCachingStrategy.java b/platforms/android/FacebookLib/src/com/facebook/LegacyTokenHelper.java
old mode 100644
new mode 100755
similarity index 63%
rename from platforms/android/FacebookLib/src/com/facebook/SharedPreferencesTokenCachingStrategy.java
rename to platforms/android/FacebookLib/src/com/facebook/LegacyTokenHelper.java
index 10eca8b80..723955891
--- a/platforms/android/FacebookLib/src/com/facebook/SharedPreferencesTokenCachingStrategy.java
+++ b/platforms/android/FacebookLib/src/com/facebook/LegacyTokenHelper.java
@@ -1,17 +1,21 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
@@ -28,25 +32,63 @@
import org.json.JSONObject;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
-/*
- *
- * An implementation of {@link TokenCachingStrategy TokenCachingStrategy} that uses Android SharedPreferences
- * to persist information.
- *
- *
- * The data to be cached is passed in via a Bundle. Only non-null key-value-pairs where
- * the value is one of the following types (or an array of the same) are persisted:
- * boolean, byte, int, long, float, double, char. In addition, String and List
- * are also supported.
- *
- */
-public class SharedPreferencesTokenCachingStrategy extends TokenCachingStrategy {
+final class LegacyTokenHelper {
+ /**
+ * The key used by AccessTokenCache to store the token value in the Bundle during
+ * load and save.
+ */
+ public static final String TOKEN_KEY = "com.facebook.TokenCachingStrategy.Token";
- private static final String DEFAULT_CACHE_KEY = "com.facebook.SharedPreferencesTokenCachingStrategy.DEFAULT_KEY";
- private static final String TAG = SharedPreferencesTokenCachingStrategy.class.getSimpleName();
+ /**
+ * The key used by AccessTokenCache to store the expiration date value in the Bundle
+ * during load and save.
+ */
+ public static final String EXPIRATION_DATE_KEY =
+ "com.facebook.TokenCachingStrategy.ExpirationDate";
+
+ /**
+ * The key used by AccessTokenCache to store the last refresh date value in the
+ * Bundle during load and save.
+ */
+ public static final String LAST_REFRESH_DATE_KEY =
+ "com.facebook.TokenCachingStrategy.LastRefreshDate";
+
+ /**
+ * The key used by AccessTokenCache to store an enum indicating the source of the token
+ * in the Bundle during load and save.
+ */
+ public static final String TOKEN_SOURCE_KEY =
+ "com.facebook.TokenCachingStrategy.AccessTokenSource";
+
+ /**
+ * The key used by AccessTokenCache to store the list of permissions granted by the
+ * token in the Bundle during load and save.
+ */
+ public static final String PERMISSIONS_KEY = "com.facebook.TokenCachingStrategy.Permissions";
+
+ /**
+ * The key used by AccessTokenCache to store the list of permissions declined by the user in the token in
+ * the Bundle during load and save.
+ */
+ public static final String DECLINED_PERMISSIONS_KEY =
+ "com.facebook.TokenCachingStrategy.DeclinedPermissions";
+
+ public static final String APPLICATION_ID_KEY =
+ "com.facebook.TokenCachingStrategy.ApplicationId";
+
+ private static final long INVALID_BUNDLE_MILLISECONDS = Long.MIN_VALUE;
+ private static final String IS_SSO_KEY = "com.facebook.TokenCachingStrategy.IsSSO";
+
+ public static final String DEFAULT_CACHE_KEY =
+ "com.facebook.SharedPreferencesTokenCachingStrategy.DEFAULT_KEY";
+ private static final String TAG = LegacyTokenHelper.class.getSimpleName();
private static final String JSON_VALUE_TYPE = "valueType";
private static final String JSON_VALUE = "value";
@@ -75,32 +117,11 @@ public class SharedPreferencesTokenCachingStrategy extends TokenCachingStrategy
private String cacheKey;
private SharedPreferences cache;
- /**
- * Creates a default {@link SharedPreferencesTokenCachingStrategy SharedPreferencesTokenCachingStrategy}
- * instance that provides access to a single set of token information.
- *
- * @param context
- * The Context object to use to get the SharedPreferences object.
- *
- * @throws NullPointerException if the passed in Context is null
- */
- public SharedPreferencesTokenCachingStrategy(Context context) {
+ public LegacyTokenHelper(Context context) {
this(context, null);
}
- /**
- * Creates a {@link SharedPreferencesTokenCachingStrategy SharedPreferencesTokenCachingStrategy} instance
- * that is distinct for the passed in cacheKey.
- *
- * @param context
- * The Context object to use to get the SharedPreferences object.
- *
- * @param cacheKey
- * Identifies a distinct set of token information.
- *
- * @throws NullPointerException if the passed in Context is null
- */
- public SharedPreferencesTokenCachingStrategy(Context context, String cacheKey) {
+ public LegacyTokenHelper(Context context, String cacheKey) {
Validate.notNull(context, "context");
this.cacheKey = Utility.isNullOrEmpty(cacheKey) ? DEFAULT_CACHE_KEY : cacheKey;
@@ -116,11 +137,6 @@ public SharedPreferencesTokenCachingStrategy(Context context, String cacheKey) {
Context.MODE_PRIVATE);
}
- /**
- * Returns a Bundle that contains the information stored in this cache
- *
- * @return A Bundle with the information contained in this cache
- */
public Bundle load() {
Bundle settings = new Bundle();
@@ -140,13 +156,6 @@ public Bundle load() {
return settings;
}
- /**
- * Persists all supported data types present in the passed in Bundle, to the
- * cache
- *
- * @param bundle
- * The Bundle containing information to be cached
- */
public void save(Bundle bundle) {
Validate.notNull(bundle, "bundle");
@@ -157,7 +166,11 @@ public void save(Bundle bundle) {
serializeKey(key, bundle, editor);
} catch (JSONException e) {
// Error in the bundle. Don't store a partial cache.
- Logger.log(LoggingBehavior.CACHE, Log.WARN, TAG, "Error processing value for key: '" + key + "' -- " + e);
+ Logger.log(
+ LoggingBehavior.CACHE,
+ Log.WARN,
+ TAG,
+ "Error processing value for key: '" + key + "' -- " + e);
// Bypass the commit and just return. This cancels the entire edit transaction
return;
@@ -173,6 +186,142 @@ public void clear() {
cache.edit().clear().apply();
}
+ public static boolean hasTokenInformation(Bundle bundle) {
+ if (bundle == null) {
+ return false;
+ }
+
+ String token = bundle.getString(TOKEN_KEY);
+ if ((token == null) || (token.length() == 0)) {
+ return false;
+ }
+
+ long expiresMilliseconds = bundle.getLong(EXPIRATION_DATE_KEY, 0L);
+ if (expiresMilliseconds == 0L) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static String getToken(Bundle bundle) {
+ Validate.notNull(bundle, "bundle");
+ return bundle.getString(TOKEN_KEY);
+ }
+
+ public static void putToken(Bundle bundle, String value) {
+ Validate.notNull(bundle, "bundle");
+ Validate.notNull(value, "value");
+ bundle.putString(TOKEN_KEY, value);
+ }
+
+ public static Date getExpirationDate(Bundle bundle) {
+ Validate.notNull(bundle, "bundle");
+ return getDate(bundle, EXPIRATION_DATE_KEY);
+ }
+
+ public static void putExpirationDate(Bundle bundle, Date value) {
+ Validate.notNull(bundle, "bundle");
+ Validate.notNull(value, "value");
+ putDate(bundle, EXPIRATION_DATE_KEY, value);
+ }
+
+ public static long getExpirationMilliseconds(Bundle bundle) {
+ Validate.notNull(bundle, "bundle");
+ return bundle.getLong(EXPIRATION_DATE_KEY);
+ }
+
+ public static void putExpirationMilliseconds(Bundle bundle, long value) {
+ Validate.notNull(bundle, "bundle");
+ bundle.putLong(EXPIRATION_DATE_KEY, value);
+ }
+
+ public static Set getPermissions(Bundle bundle) {
+ Validate.notNull(bundle, "bundle");
+ ArrayList arrayList = bundle.getStringArrayList(PERMISSIONS_KEY);
+ if (arrayList == null) {
+ return null;
+ }
+ return new HashSet(arrayList);
+ }
+
+ public static void putPermissions(Bundle bundle, Collection value) {
+ Validate.notNull(bundle, "bundle");
+ Validate.notNull(value, "value");
+
+ bundle.putStringArrayList(PERMISSIONS_KEY, new ArrayList(value));
+ }
+
+ public static void putDeclinedPermissions(Bundle bundle, Collection value) {
+ Validate.notNull(bundle, "bundle");
+ Validate.notNull(value, "value");
+
+ bundle.putStringArrayList(DECLINED_PERMISSIONS_KEY, new ArrayList(value));
+ }
+
+ public static AccessTokenSource getSource(Bundle bundle) {
+ Validate.notNull(bundle, "bundle");
+ if (bundle.containsKey(TOKEN_SOURCE_KEY)) {
+ return (AccessTokenSource) bundle.getSerializable(TOKEN_SOURCE_KEY);
+ } else {
+ boolean isSSO = bundle.getBoolean(IS_SSO_KEY);
+ return isSSO ? AccessTokenSource.FACEBOOK_APPLICATION_WEB : AccessTokenSource.WEB_VIEW;
+ }
+ }
+
+ public static void putSource(Bundle bundle, AccessTokenSource value) {
+ Validate.notNull(bundle, "bundle");
+ bundle.putSerializable(TOKEN_SOURCE_KEY, value);
+ }
+
+ public static Date getLastRefreshDate(Bundle bundle) {
+ Validate.notNull(bundle, "bundle");
+ return getDate(bundle, LAST_REFRESH_DATE_KEY);
+ }
+
+ public static void putLastRefreshDate(Bundle bundle, Date value) {
+ Validate.notNull(bundle, "bundle");
+ Validate.notNull(value, "value");
+ putDate(bundle, LAST_REFRESH_DATE_KEY, value);
+ }
+
+ public static long getLastRefreshMilliseconds(Bundle bundle) {
+ Validate.notNull(bundle, "bundle");
+ return bundle.getLong(LAST_REFRESH_DATE_KEY);
+ }
+
+ public static void putLastRefreshMilliseconds(Bundle bundle, long value) {
+ Validate.notNull(bundle, "bundle");
+ bundle.putLong(LAST_REFRESH_DATE_KEY, value);
+ }
+
+ public static String getApplicationId(Bundle bundle) {
+ Validate.notNull(bundle, "bundle");
+ return bundle.getString(APPLICATION_ID_KEY);
+ }
+
+ public static void putApplicationId(Bundle bundle, String value) {
+ Validate.notNull(bundle, "bundle");
+ bundle.putString(APPLICATION_ID_KEY, value);
+ }
+
+ static Date getDate(Bundle bundle, String key) {
+ if (bundle == null) {
+ return null;
+ }
+
+ long n = bundle.getLong(key, INVALID_BUNDLE_MILLISECONDS);
+ if (n == INVALID_BUNDLE_MILLISECONDS) {
+ return null;
+ }
+
+ return new Date(n);
+ }
+
+ static void putDate(Bundle bundle, String key, Date date) {
+ bundle.putLong(key, date.getTime());
+ }
+
private void serializeKey(String key, Bundle bundle, SharedPreferences.Editor editor)
throws JSONException {
Object value = bundle.get(key);
@@ -380,7 +529,9 @@ private void deserializeKey(String key, Bundle bundle)
ArrayList stringList = new ArrayList(numStrings);
for (int i = 0; i < numStrings; i++) {
Object jsonStringValue = jsonArray.get(i);
- stringList.add(i, jsonStringValue == JSONObject.NULL ? null : (String)jsonStringValue);
+ stringList.add(
+ i,
+ jsonStringValue == JSONObject.NULL ? null : (String)jsonStringValue);
}
bundle.putStringArrayList(key, stringList);
} else if (valueType.equals(TYPE_ENUM)) {
diff --git a/platforms/android/FacebookLib/src/com/facebook/LoggingBehavior.java b/platforms/android/FacebookLib/src/com/facebook/LoggingBehavior.java
old mode 100644
new mode 100755
index 5fb198e9c..768a8b944
--- a/platforms/android/FacebookLib/src/com/facebook/LoggingBehavior.java
+++ b/platforms/android/FacebookLib/src/com/facebook/LoggingBehavior.java
@@ -1,17 +1,21 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
@@ -19,7 +23,7 @@
/**
* Specifies different categories of logging messages that can be generated.
*
- * @see Settings#addLoggingBehavior(LoggingBehavior)
+ * @see FacebookSdk#addLoggingBehavior(LoggingBehavior)
*/
public enum LoggingBehavior {
/**
@@ -27,7 +31,8 @@ public enum LoggingBehavior {
*/
REQUESTS,
/**
- * Indicates that access tokens should be logged as part of the request logging; normally they are not.
+ * Indicates that access tokens should be logged as part of the request logging; normally they
+ * are not.
*/
INCLUDE_ACCESS_TOKENS,
/**
@@ -43,11 +48,22 @@ public enum LoggingBehavior {
*/
APP_EVENTS,
/**
- * Indicates that likely developer errors should be logged. (This is set by default in LoggingBehavior.)
+ * Indicates that likely developer errors should be logged. (This is set by default in
+ * LoggingBehavior.)
*/
- DEVELOPER_ERRORS
- ;
+ DEVELOPER_ERRORS,
+
+ /**
+ * Log debug warnings from API response, e.g. when friends fields requested, but user_friends
+ * permission isn't granted.
+ */
+ GRAPH_API_DEBUG_WARNING,
- @Deprecated
- public static final LoggingBehavior INSIGHTS = APP_EVENTS;
+ /**
+ * Log warnings from API response, e.g. when requested feature will be deprecated in next
+ * version of API. Info is the lowest level of severity, using it will result in logging all
+ * GRAPH_API_DEBUG levels.
+ */
+ GRAPH_API_DEBUG_INFO
+ ;
}
diff --git a/platforms/android/FacebookLib/src/com/facebook/LoginActivity.java b/platforms/android/FacebookLib/src/com/facebook/LoginActivity.java
deleted file mode 100644
index 096f00eae..000000000
--- a/platforms/android/FacebookLib/src/com/facebook/LoginActivity.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * 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.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import com.facebook.android.R;
-
-/**
- * This Activity is a necessary part of the overall Facebook login process
- * but is not meant to be used directly. Add this activity to your
- * AndroidManifest.xml to ensure proper handling of Facebook login.
- *
- * {@code
- *
- * }
- *
- * Do not start this activity directly.
- */
-public class LoginActivity extends Activity {
- static final String RESULT_KEY = "com.facebook.LoginActivity:Result";
-
- private static final String TAG = LoginActivity.class.getName();
- private static final String NULL_CALLING_PKG_ERROR_MSG =
- "Cannot call LoginActivity with a null calling package. " +
- "This can occur if the launchMode of the caller is singleInstance.";
- private static final String SAVED_CALLING_PKG_KEY = "callingPackage";
- private static final String SAVED_AUTH_CLIENT = "authorizationClient";
- private static final String EXTRA_REQUEST = "request";
-
- private String callingPackage;
- private AuthorizationClient authorizationClient;
- private AuthorizationClient.AuthorizationRequest request;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.com_facebook_login_activity_layout);
-
- if (savedInstanceState != null) {
- callingPackage = savedInstanceState.getString(SAVED_CALLING_PKG_KEY);
- authorizationClient = (AuthorizationClient) savedInstanceState.getSerializable(SAVED_AUTH_CLIENT);
- } else {
- callingPackage = getCallingPackage();
- authorizationClient = new AuthorizationClient();
- request = (AuthorizationClient.AuthorizationRequest) getIntent().getSerializableExtra(EXTRA_REQUEST);
- }
-
- authorizationClient.setContext(this);
- authorizationClient.setOnCompletedListener(new AuthorizationClient.OnCompletedListener() {
- @Override
- public void onCompleted(AuthorizationClient.Result outcome) {
- onAuthClientCompleted(outcome);
- }
- });
- authorizationClient.setBackgroundProcessingListener(new AuthorizationClient.BackgroundProcessingListener() {
- @Override
- public void onBackgroundProcessingStarted() {
- findViewById(R.id.com_facebook_login_activity_progress_bar).setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onBackgroundProcessingStopped() {
- findViewById(R.id.com_facebook_login_activity_progress_bar).setVisibility(View.GONE);
- }
- });
- }
-
- private void onAuthClientCompleted(AuthorizationClient.Result outcome) {
- request = null;
-
- int resultCode = (outcome.code == AuthorizationClient.Result.Code.CANCEL) ?
- RESULT_CANCELED : RESULT_OK;
-
- Bundle bundle = new Bundle();
- bundle.putSerializable(RESULT_KEY, outcome);
-
- Intent resultIntent = new Intent();
- resultIntent.putExtras(bundle);
- setResult(resultCode, resultIntent);
-
- finish();
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- // If the calling package is null, this generally means that the callee was started
- // with a launchMode of singleInstance. Unfortunately, Android does not allow a result
- // to be set when the callee is a singleInstance, so we log an error and return.
- if (callingPackage == null) {
- Log.e(TAG, NULL_CALLING_PKG_ERROR_MSG);
- finish();
- return;
- }
-
- authorizationClient.startOrContinueAuth(request);
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- authorizationClient.cancelCurrentHandler();
- findViewById(R.id.com_facebook_login_activity_progress_bar).setVisibility(View.GONE);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putString(SAVED_CALLING_PKG_KEY, callingPackage);
- outState.putSerializable(SAVED_AUTH_CLIENT, authorizationClient);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- authorizationClient.onActivityResult(requestCode, resultCode, data);
- }
-
- static Bundle populateIntentExtras(AuthorizationClient.AuthorizationRequest request) {
- Bundle extras = new Bundle();
- extras.putSerializable(EXTRA_REQUEST, request);
- return extras;
- }
-}
diff --git a/platforms/android/FacebookLib/src/com/facebook/NativeAppCallAttachmentStore.java b/platforms/android/FacebookLib/src/com/facebook/NativeAppCallAttachmentStore.java
deleted file mode 100644
index aa7f39ed7..000000000
--- a/platforms/android/FacebookLib/src/com/facebook/NativeAppCallAttachmentStore.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/**
- * 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.content.Context;
-import android.graphics.Bitmap;
-import android.util.Log;
-import com.facebook.internal.Utility;
-import com.facebook.internal.Validate;
-
-import java.io.*;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * This class works in conjunction with {@link NativeAppCallContentProvider} to allow apps to attach binary
- * attachments (e.g., images) to native dialogs launched via the {@link com.facebook.widget.FacebookDialog}
- * class. It stores attachments in temporary files and allows the Facebook application to retrieve them via
- * the content provider.
- *
- * Callers are generally not expected to need to use this class directly;
- * see {@link com.facebook.widget.FacebookDialog.OpenGraphActionDialogBuilder#setImageAttachmentsForObject(String,
- * java.util.List) OpenGraphActionDialogBuilder.setImageAttachmentsForObject} for an example of a function
- * that will accept attachments, attach them to the native dialog call, and add them to the content provider
- * automatically.
- **/
-public final class NativeAppCallAttachmentStore implements NativeAppCallContentProvider.AttachmentDataSource {
- private static final String TAG = NativeAppCallAttachmentStore.class.getName();
- static final String ATTACHMENTS_DIR_NAME = "com.facebook.NativeAppCallAttachmentStore.files";
- private static File attachmentsDirectory;
-
- /**
- * Adds a number of bitmap attachments associated with a native app call. The attachments will be
- * served via {@link NativeAppCallContentProvider#openFile(android.net.Uri, String) openFile}.
- *
- * @param context the Context the call is being made from
- * @param callId the unique ID of the call
- * @param imageAttachments a Map of attachment names to Bitmaps; the attachment names will be part of
- * the URI processed by openFile
- * @throws java.io.IOException
- */
- public void addAttachmentsForCall(Context context, UUID callId, Map imageAttachments) {
- Validate.notNull(context, "context");
- Validate.notNull(callId, "callId");
- Validate.containsNoNulls(imageAttachments.values(), "imageAttachments");
- Validate.containsNoNullOrEmpty(imageAttachments.keySet(), "imageAttachments");
-
- addAttachments(context, callId, imageAttachments, new ProcessAttachment() {
- @Override
- public void processAttachment(Bitmap attachment, File outputFile) throws IOException {
- FileOutputStream outputStream = new FileOutputStream(outputFile);
- try {
- attachment.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
- } finally {
- Utility.closeQuietly(outputStream);
- }
- }
- });
- }
-
- /**
- * Adds a number of bitmap/video attachment files associated with a native app call. The attachments will be
- * served via {@link NativeAppCallContentProvider#openFile(android.net.Uri, String) openFile}.
- *
- * @param context the Context the call is being made from
- * @param callId the unique ID of the call
- * @param mediaAttachmentFiles a Map of attachment names to Files containing the bitmaps/videos; the attachment
- * names will be part of the URI processed by openFile
- * @throws java.io.IOException
- */
- public void addAttachmentFilesForCall(Context context, UUID callId, Map mediaAttachmentFiles) {
- Validate.notNull(context, "context");
- Validate.notNull(callId, "callId");
- Validate.containsNoNulls(mediaAttachmentFiles.values(), "mediaAttachmentFiles");
- Validate.containsNoNullOrEmpty(mediaAttachmentFiles.keySet(), "mediaAttachmentFiles");
-
- addAttachments(context, callId, mediaAttachmentFiles, new ProcessAttachment() {
- @Override
- public void processAttachment(File attachment, File outputFile) throws IOException {
- FileOutputStream outputStream = new FileOutputStream(outputFile);
- FileInputStream inputStream = null;
- try {
- inputStream = new FileInputStream(attachment);
-
- byte[] buffer = new byte[1024];
- int len;
- while ((len = inputStream.read(buffer)) > 0) {
- outputStream.write(buffer, 0, len);
- }
- } finally {
- Utility.closeQuietly(outputStream);
- Utility.closeQuietly(inputStream);
- }
- }
- });
- }
-
- private void addAttachments(Context context, UUID callId, Map attachments,
- ProcessAttachment processor) {
- if (attachments.size() == 0) {
- return;
- }
-
- // If this is the first time we've been instantiated, clean up any existing attachments.
- if (attachmentsDirectory == null) {
- cleanupAllAttachments(context);
- }
-
- ensureAttachmentsDirectoryExists(context);
-
- List filesToCleanup = new ArrayList();
-
- try {
- for (Map.Entry entry : attachments.entrySet()) {
- String attachmentName = entry.getKey();
- T attachment = entry.getValue();
-
- File file = getAttachmentFile(callId, attachmentName, true);
- filesToCleanup.add(file);
-
- processor.processAttachment(attachment, file);
- }
- } catch (IOException exception) {
- Log.e(TAG, "Got unexpected exception:" + exception);
- for (File file : filesToCleanup) {
- try {
- file.delete();
- } catch (Exception e) {
- // Always try to delete other files.
- }
- }
- throw new FacebookException(exception);
- }
-
- }
-
- interface ProcessAttachment {
- void processAttachment(T attachment, File outputFile) throws IOException;
- }
-
- /**
- * Removes any temporary files associated with a particular native app call.
- *
- * @param context the Context the call is being made from
- * @param callId the unique ID of the call
- */
- public void cleanupAttachmentsForCall(Context context, UUID callId) {
- File dir = getAttachmentsDirectoryForCall(callId, false);
- Utility.deleteDirectory(dir);
- }
-
- @Override
- public File openAttachment(UUID callId, String attachmentName) throws FileNotFoundException {
- if (Utility.isNullOrEmpty(attachmentName) ||
- callId == null) {
- throw new FileNotFoundException();
- }
-
- try {
- return getAttachmentFile(callId, attachmentName, false);
- } catch (IOException e) {
- // We don't try to create the file, so we shouldn't get any IOExceptions. But if we do, just
- // act like the file wasn't found.
- throw new FileNotFoundException();
- }
- }
-
- synchronized static File getAttachmentsDirectory(Context context) {
- if (attachmentsDirectory == null) {
- attachmentsDirectory = new File(context.getCacheDir(), ATTACHMENTS_DIR_NAME);
- }
- return attachmentsDirectory;
- }
-
- File ensureAttachmentsDirectoryExists(Context context) {
- File dir = getAttachmentsDirectory(context);
- dir.mkdirs();
- return dir;
- }
-
- File getAttachmentsDirectoryForCall(UUID callId, boolean create) {
- if (attachmentsDirectory == null) {
- return null;
- }
-
- File dir = new File(attachmentsDirectory, callId.toString());
- if (create && !dir.exists()) {
- dir.mkdirs();
- }
- return dir;
- }
-
- File getAttachmentFile(UUID callId, String attachmentName, boolean createDirs) throws IOException {
- File dir = getAttachmentsDirectoryForCall(callId, createDirs);
- if (dir == null) {
- return null;
- }
-
- try {
- return new File(dir, URLEncoder.encode(attachmentName, "UTF-8"));
- } catch (UnsupportedEncodingException e) {
- return null;
- }
- }
-
- void cleanupAllAttachments(Context context) {
- // Attachments directory may or may not exist; we won't create it if not, since we are just going to delete it.
- File dir = getAttachmentsDirectory(context);
- Utility.deleteDirectory(dir);
- }
-}
diff --git a/platforms/android/FacebookLib/src/com/facebook/NativeAppCallContentProvider.java b/platforms/android/FacebookLib/src/com/facebook/NativeAppCallContentProvider.java
deleted file mode 100644
index 374547a1c..000000000
--- a/platforms/android/FacebookLib/src/com/facebook/NativeAppCallContentProvider.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * 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.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import android.util.Pair;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.UUID;
-
-/**
- * Implements a
- * ContentProvider that can be used to provide binary attachments (e.g., images) to calls made
- * via @link FacebookDialog}. The {@link NativeAppCallAttachmentStore} class provides methods to attach
- * and clean up the attachments.
- *
- *
Note that this ContentProvider is only necessary if an application wishes to attach images, etc., that are
- * stored in memory and do not have another way to be referenced by a content URI. For images obtained from,
- * e.g., the Camera or Gallery, that already have a content URI associated with them, use of this class is not
- * necessary.
- *
- * If an application wishes to attach images that are stored in-memory within the application, this content
- * provider must be listed in the application's AndroidManifest.xml, and it should be named according to the
- * pattern "com.facebook.app.NativeAppCallContentProvider{FACEBOOK_APP_ID}"
. See the
- * {@link NativeAppCallContentProvider#getAttachmentUrl(String) getContentProviderName} method.
- */
-public class NativeAppCallContentProvider extends ContentProvider {
- private static final String TAG = NativeAppCallContentProvider.class.getName();
- private static final String ATTACHMENT_URL_BASE = "content://com.facebook.app.NativeAppCallContentProvider";
-
- private final AttachmentDataSource dataSource;
-
- public NativeAppCallContentProvider() {
- this(new NativeAppCallAttachmentStore());
- }
-
- NativeAppCallContentProvider(AttachmentDataSource dataSource) {
- this.dataSource = dataSource;
- }
-
- interface AttachmentDataSource {
- File openAttachment(UUID callId, String attachmentName) throws FileNotFoundException;
- }
-
- /**
- * Returns the name of the content provider formatted correctly for constructing URLs.
- * @param applicationId the Facebook application ID of the application
- * @return the String to use as the authority portion of a content URI.
- */
- public static String getAttachmentUrl(String applicationId, UUID callId, String attachmentName) {
- return String.format("%s%s/%s/%s", ATTACHMENT_URL_BASE, applicationId, callId.toString(), attachmentName);
- }
-
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] strings, String s, String[] strings2, String s2) {
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues contentValues) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String s, String[] strings) {
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
- return 0;
- }
-
- @Override
- public android.os.ParcelFileDescriptor openFile(android.net.Uri uri, java.lang.String mode)
- throws java.io.FileNotFoundException {
-
- Pair callIdAndAttachmentName = parseCallIdAndAttachmentName(uri);
- if (callIdAndAttachmentName == null) {
- throw new FileNotFoundException();
- }
-
- try {
- File file = dataSource.openAttachment(callIdAndAttachmentName.first, callIdAndAttachmentName.second);
-
- return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- } catch (FileNotFoundException exception) {
- Log.e(TAG, "Got unexpected exception:" + exception);
- throw exception;
- }
- }
-
- Pair parseCallIdAndAttachmentName(Uri uri) {
- try {
- // We don't do explicit format checking here. Malformed URIs may generate NullPointerExceptions or
- // array bounds exceptions, which we'll catch and return null. All of these will result in a
- // FileNotFoundException being thrown in openFile.
- String callIdAndAttachmentName = uri.getPath().substring(1);
- String [] parts = callIdAndAttachmentName.split("/");
-
- String callIdString = parts[0];
- String attachmentName = parts[1];
- UUID callId = UUID.fromString(callIdString);
-
- return new Pair(callId, attachmentName);
- } catch (Exception exception) {
- return null;
- }
- }
-}
diff --git a/platforms/android/FacebookLib/src/com/facebook/NonCachingTokenCachingStrategy.java b/platforms/android/FacebookLib/src/com/facebook/NonCachingTokenCachingStrategy.java
deleted file mode 100644
index 1f4c56216..000000000
--- a/platforms/android/FacebookLib/src/com/facebook/NonCachingTokenCachingStrategy.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * 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.os.Bundle;
-
-/**
- * Implements a trivial {@link TokenCachingStrategy} that does not actually cache any tokens.
- * It is intended for use when an access token may be used on a temporary basis but should not be
- * cached for future use (for instance, when handling a deep link).
- */
-public class NonCachingTokenCachingStrategy extends TokenCachingStrategy {
- @Override
- public Bundle load() {
- return null;
- }
-
- @Override
- public void save(Bundle bundle) {
- }
-
- @Override
- public void clear() {
- }
-}
diff --git a/platforms/android/FacebookLib/src/com/facebook/Profile.java b/platforms/android/FacebookLib/src/com/facebook/Profile.java
new file mode 100755
index 000000000..919ba1686
--- /dev/null
+++ b/platforms/android/FacebookLib/src/com/facebook/Profile.java
@@ -0,0 +1,313 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright 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.
+ */
+
+package com.facebook;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+
+import com.facebook.internal.ImageRequest;
+import com.facebook.internal.Utility;
+import com.facebook.internal.Validate;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * This class represents a basic Facebook profile.
+ */
+public final class Profile implements Parcelable {
+ private static final String ID_KEY = "id";
+ private static final String FIRST_NAME_KEY = "first_name";
+ private static final String MIDDLE_NAME_KEY = "middle_name";
+ private static final String LAST_NAME_KEY = "last_name";
+ private static final String NAME_KEY = "name";
+ private static final String LINK_URI_KEY = "link_uri";
+
+ private final String id;
+ private final String firstName;
+ private final String middleName;
+ private final String lastName;
+ private final String name;
+ private final Uri linkUri;
+
+ /**
+ * Getter for the profile that is currently logged in to the application.
+ * @return The profile that is currently logged in to the application.
+ */
+ public static Profile getCurrentProfile()
+ {
+ return ProfileManager.getInstance().getCurrentProfile();
+ }
+
+ /**
+ * Setter for the profile that is currently logged in to the application. If the access token is
+ * invalidated, the current profile will not be updated. It's only updated when there is an
+ * explicit logout, login or when permissions change via the
+ * {@link com.facebook.login.LoginManager}.
+ * @param profile The profile that is currently logged in to the application.
+ */
+ public static void setCurrentProfile(Profile profile) {
+ ProfileManager.getInstance().setCurrentProfile(profile);
+ }
+
+ /**
+ * Fetches and sets the current profile from the current access token.
+ *
+ * This should only be called from the UI thread.
+ */
+ public static void fetchProfileForCurrentAccessToken() {
+ AccessToken accessToken = AccessToken.getCurrentAccessToken();
+ if (accessToken == null) {
+ Profile.setCurrentProfile(null);
+ return;
+ }
+
+ Utility.getGraphMeRequestWithCacheAsync(accessToken.getToken(),
+ new Utility.GraphMeRequestWithCacheCallback() {
+ @Override
+ public void onSuccess(JSONObject userInfo) {
+ String id = userInfo.optString("id");
+ if (id == null) {
+ return;
+ }
+ String link = userInfo.optString("link");
+ Profile profile = new Profile(
+ id,
+ userInfo.optString("first_name"),
+ userInfo.optString("middle_name"),
+ userInfo.optString("last_name"),
+ userInfo.optString("name"),
+ link != null ? Uri.parse(link) : null
+ );
+ Profile.setCurrentProfile(profile);
+ }
+
+ @Override
+ public void onFailure(FacebookException error) {
+ return;
+ }
+ });
+ }
+
+ /**
+ * Contructor.
+ * @param id The id of the profile.
+ * @param firstName The first name of the profile. Can be null.
+ * @param middleName The middle name of the profile. Can be null.
+ * @param lastName The last name of the profile. Can be null.
+ * @param name The name of the profile. Can be null.
+ * @param linkUri The link for this profile. Can be null.
+ */
+ public Profile(
+ final String id,
+ @Nullable
+ final String firstName,
+ @Nullable
+ final String middleName,
+ @Nullable
+ final String lastName,
+ @Nullable
+ final String name,
+ @Nullable
+ final Uri linkUri) {
+ Validate.notNullOrEmpty(id, "id");
+
+ this.id = id;
+ this.firstName = firstName;
+ this.middleName = middleName;
+ this.lastName = lastName;
+ this.name = name;
+ this.linkUri = linkUri;
+ }
+
+ /**
+ * Getter for the Uri of the profile picture.
+ *
+ * @param width The desired width for the profile picture.
+ * @param height The desired height for the profile picture.
+ * @return The Uri of the profile picture.
+ */
+ public Uri getProfilePictureUri(
+ int width,
+ int height) {
+ return ImageRequest.getProfilePictureUri(this.id, width, height);
+ }
+
+ /**
+ * Getter for the id of the profile.
+ * @return id of the profile.
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Getter for the first name of the profile.
+ * @return the first name of the profile.
+ */
+ public String getFirstName() {
+ return firstName;
+ }
+
+ /**
+ * Getter for the middle name of the profile.
+ * @return the middle name of the profile.
+ */
+ public String getMiddleName() {
+ return middleName;
+ }
+
+ /**
+ * Getter for the last name of the profile.
+ * @return the last name of the profile.
+ */
+ public String getLastName() {
+ return lastName;
+ }
+
+ /**
+ * Getter for the name of the profile.
+ * @return the name of the profile.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Getter for the link of the profile.
+ * @return the link of the profile.
+ */
+ public Uri getLinkUri() {
+ return linkUri;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof Profile)) {
+ return false;
+ }
+
+ Profile o = (Profile) other;
+
+ return id.equals(o.id) &&
+ firstName == null ? o.firstName == null : firstName.equals(o.firstName) &&
+ middleName == null ? o.middleName == null : middleName.equals(o.middleName) &&
+ lastName == null ? o.lastName == null : lastName.equals(o.lastName) &&
+ name == null ? o.name == null : name.equals(o.name) &&
+ linkUri == null ? o.linkUri == null : linkUri.equals(o.linkUri);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result = result * 31 + id.hashCode();
+ if (firstName != null) {
+ result = result * 31 + firstName.hashCode();
+ }
+ if (middleName != null) {
+ result = result * 31 + middleName.hashCode();
+ }
+ if (lastName != null) {
+ result = result * 31 + lastName.hashCode();
+ }
+ if (name != null) {
+ result = result * 31 + name.hashCode();
+ }
+ if (linkUri != null) {
+ result = result * 31 + linkUri.hashCode();
+ }
+
+ return result;
+ }
+
+ JSONObject toJSONObject() {
+ JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put(ID_KEY, id);
+ jsonObject.put(FIRST_NAME_KEY, firstName);
+ jsonObject.put(MIDDLE_NAME_KEY, middleName);
+ jsonObject.put(LAST_NAME_KEY, lastName);
+ jsonObject.put(NAME_KEY, name);
+ if (linkUri != null) {
+ jsonObject.put(LINK_URI_KEY, linkUri.toString());
+ }
+ } catch (JSONException object) {
+ jsonObject = null;
+ }
+ return jsonObject;
+ }
+
+ Profile(JSONObject jsonObject) {
+ id = jsonObject.optString(ID_KEY, null);
+ firstName = jsonObject.optString(FIRST_NAME_KEY, null);
+ middleName = jsonObject.optString(MIDDLE_NAME_KEY, null);
+ lastName = jsonObject.optString(LAST_NAME_KEY, null);
+ name = jsonObject.optString(NAME_KEY, null);
+ String linkUriString = jsonObject.optString(LINK_URI_KEY, null);
+ linkUri = linkUriString == null ? null : Uri.parse(linkUriString);
+ }
+
+ private Profile(Parcel source) {
+ id = source.readString();
+ firstName = source.readString();
+ middleName = source.readString();
+ lastName = source.readString();
+ name = source.readString();
+ String linkUriString = source.readString();
+ linkUri = linkUriString == null ? null : Uri.parse(linkUriString);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeString(firstName);
+ dest.writeString(middleName);
+ dest.writeString(lastName);
+ dest.writeString(name);
+ dest.writeString(linkUri == null ? null : linkUri.toString());
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+
+ @Override
+ public Profile createFromParcel(Parcel source) {
+ return new Profile(source);
+ }
+
+ @Override
+ public Profile[] newArray(int size) {
+ return new Profile[size];
+ }
+ };
+}
diff --git a/platforms/android/FacebookLib/src/com/facebook/ProfileCache.java b/platforms/android/FacebookLib/src/com/facebook/ProfileCache.java
new file mode 100755
index 000000000..3569bab37
--- /dev/null
+++ b/platforms/android/FacebookLib/src/com/facebook/ProfileCache.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright 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.
+ */
+
+package com.facebook;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.facebook.internal.Validate;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+final class ProfileCache {
+ static final String CACHED_PROFILE_KEY = "com.facebook.ProfileManager.CachedProfile";
+ static final String SHARED_PREFERENCES_NAME =
+ "com.facebook.AccessTokenManager.SharedPreferences";
+
+ private final SharedPreferences sharedPreferences;
+
+ ProfileCache() {
+ sharedPreferences = FacebookSdk.getApplicationContext().getSharedPreferences(
+ SHARED_PREFERENCES_NAME,
+ Context.MODE_PRIVATE);
+ }
+
+ Profile load() {
+ String jsonString = sharedPreferences.getString(CACHED_PROFILE_KEY, null);
+ if (jsonString != null) {
+ try {
+ JSONObject jsonObject = new JSONObject(jsonString);
+ return new Profile(jsonObject);
+ } catch (JSONException e) {
+ // Can't recover
+ }
+ }
+ return null;
+ }
+
+ void save(Profile profile) {
+ Validate.notNull(profile, "profile");
+ JSONObject jsonObject = profile.toJSONObject();
+ if (jsonObject != null) {
+ sharedPreferences
+ .edit()
+ .putString(CACHED_PROFILE_KEY, jsonObject.toString())
+ .apply();
+ }
+ }
+
+ void clear() {
+ sharedPreferences
+ .edit()
+ .remove(CACHED_PROFILE_KEY)
+ .apply();
+ }
+}
diff --git a/platforms/android/FacebookLib/src/com/facebook/ProfileManager.java b/platforms/android/FacebookLib/src/com/facebook/ProfileManager.java
new file mode 100755
index 000000000..1f14b2554
--- /dev/null
+++ b/platforms/android/FacebookLib/src/com/facebook/ProfileManager.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright 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.
+ */
+
+package com.facebook;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.content.LocalBroadcastManager;
+
+import com.facebook.internal.Utility;
+import com.facebook.internal.Validate;
+
+final class ProfileManager {
+ static final String ACTION_CURRENT_PROFILE_CHANGED =
+ "com.facebook.sdk.ACTION_CURRENT_PROFILE_CHANGED";
+ static final String EXTRA_OLD_PROFILE =
+ "com.facebook.sdk.EXTRA_OLD_PROFILE";
+ static final String EXTRA_NEW_PROFILE =
+ "com.facebook.sdk.EXTRA_NEW_PROFILE";
+
+ private static volatile ProfileManager instance;
+
+ private final LocalBroadcastManager localBroadcastManager;
+ private final ProfileCache profileCache;
+ private Profile currentProfile;
+
+
+ ProfileManager(
+ LocalBroadcastManager localBroadcastManager,
+ ProfileCache profileCache) {
+ Validate.notNull(localBroadcastManager, "localBroadcastManager");
+ Validate.notNull(profileCache, "profileCache");
+ this.localBroadcastManager = localBroadcastManager;
+ this.profileCache = profileCache;
+ }
+
+ static ProfileManager getInstance() {
+ if (instance == null) {
+ synchronized (ProfileManager.class) {
+ if (instance == null) {
+ Context applicationContext = FacebookSdk.getApplicationContext();
+ LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+ applicationContext);
+
+ instance = new ProfileManager(localBroadcastManager, new ProfileCache());
+ }
+ }
+ }
+ return instance;
+ }
+
+ Profile getCurrentProfile() {
+ return currentProfile;
+ }
+
+ boolean loadCurrentProfile() {
+ Profile profile = profileCache.load();
+
+ if (profile != null) {
+ setCurrentProfile(profile, false);
+ return true;
+ }
+
+ return false;
+ }
+
+ void setCurrentProfile(Profile currentProfile) {
+ setCurrentProfile(currentProfile, true);
+ }
+
+ private void setCurrentProfile(Profile currentProfile, boolean writeToCache) {
+ Profile oldProfile = this.currentProfile;
+ this.currentProfile = currentProfile;
+
+ if (writeToCache) {
+ if (currentProfile != null) {
+ profileCache.save(currentProfile);
+ } else {
+ profileCache.clear();
+ }
+ }
+
+ if (!Utility.areObjectsEqual(oldProfile, currentProfile)) {
+ sendCurrentProfileChangedBroadcast(oldProfile, currentProfile);
+ }
+ }
+
+ private void sendCurrentProfileChangedBroadcast(
+ Profile oldProfile,
+ Profile currentProfile) {
+ Intent intent = new Intent(ACTION_CURRENT_PROFILE_CHANGED);
+
+ intent.putExtra(EXTRA_OLD_PROFILE, oldProfile);
+ intent.putExtra(EXTRA_NEW_PROFILE, currentProfile);
+
+ localBroadcastManager.sendBroadcast(intent);
+ }
+}
diff --git a/platforms/android/FacebookLib/src/com/facebook/ProfileTracker.java b/platforms/android/FacebookLib/src/com/facebook/ProfileTracker.java
new file mode 100755
index 000000000..8ee010c8e
--- /dev/null
+++ b/platforms/android/FacebookLib/src/com/facebook/ProfileTracker.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
+ *
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright 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.
+ */
+
+package com.facebook;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.v4.content.LocalBroadcastManager;
+
+import com.facebook.internal.Validate;
+
+/**
+ * This class can be extended to receive notifications of profile changes. The {@link
+ * #stopTracking()} method should be called in the onDestroy() method of the receiving Activity or
+ * Fragment.
+ */
+abstract public class ProfileTracker {
+
+ private final BroadcastReceiver receiver;
+ private final LocalBroadcastManager broadcastManager;
+ private boolean isTracking = false;
+
+ /**
+ * The method that will be called when the profile changes.
+ * @param oldProfile The profile before the change.
+ * @param currentProfile The new profile.
+ */
+ protected abstract void onCurrentProfileChanged(
+ Profile oldProfile,
+ Profile currentProfile);
+
+ /**
+ * Constructor.
+ */
+ public ProfileTracker() {
+ Validate.sdkInitialized();
+ this.receiver = new ProfileBroadcastReceiver();
+ this.broadcastManager = LocalBroadcastManager.getInstance(
+ FacebookSdk.getApplicationContext());
+ startTracking();
+ }
+
+ /**
+ * Starts tracking the current profile.
+ */
+ public void startTracking() {
+ if (isTracking) {
+ return;
+ }
+
+ addBroadcastReceiver();
+
+ isTracking = true;
+ }
+
+ /**
+ * Stops tracking the current profile.
+ */
+ public void stopTracking() {
+ if (!isTracking) {
+ return;
+ }
+
+ broadcastManager.unregisterReceiver(receiver);
+ isTracking = false;
+ }
+
+ /**
+ * Gets whether the tracker is tracking the current access token.
+ * @return true if the tracker is the tracking the current access token, false if not
+ */
+ public boolean isTracking() {
+ return isTracking;
+ }
+
+ private class ProfileBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ProfileManager.ACTION_CURRENT_PROFILE_CHANGED.equals(intent.getAction())) {
+
+ Profile oldProfile = (Profile) intent
+ .getParcelableExtra(ProfileManager.EXTRA_OLD_PROFILE);
+ Profile newProfile = (Profile) intent
+ .getParcelableExtra(ProfileManager.EXTRA_NEW_PROFILE);
+
+ onCurrentProfileChanged(oldProfile, newProfile);
+ }
+ }
+ }
+
+ private void addBroadcastReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ProfileManager.ACTION_CURRENT_PROFILE_CHANGED);
+
+ broadcastManager.registerReceiver(receiver, filter);
+ }
+}
diff --git a/platforms/android/FacebookLib/src/com/facebook/ProgressNoopOutputStream.java b/platforms/android/FacebookLib/src/com/facebook/ProgressNoopOutputStream.java
old mode 100644
new mode 100755
index e0e3770df..71f7bb45d
--- a/platforms/android/FacebookLib/src/com/facebook/ProgressNoopOutputStream.java
+++ b/platforms/android/FacebookLib/src/com/facebook/ProgressNoopOutputStream.java
@@ -1,17 +1,21 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
@@ -23,10 +27,10 @@
import java.util.Map;
class ProgressNoopOutputStream extends OutputStream implements RequestOutputStream {
- private final Map progressMap = new HashMap();
+ private final Map progressMap = new HashMap();
private final Handler callbackHandler;
- private Request currentRequest;
+ private GraphRequest currentRequest;
private RequestProgress currentRequestProgress;
private int batchMax;
@@ -34,16 +38,17 @@ class ProgressNoopOutputStream extends OutputStream implements RequestOutputStre
this.callbackHandler = callbackHandler;
}
- public void setCurrentRequest(Request currentRequest) {
+ public void setCurrentRequest(GraphRequest currentRequest) {
this.currentRequest = currentRequest;
- this.currentRequestProgress = currentRequest != null? progressMap.get(currentRequest) : null;
+ this.currentRequestProgress =
+ currentRequest != null? progressMap.get(currentRequest) : null;
}
int getMaxProgress() {
return batchMax;
}
- Map getProgressMap() {
+ Map getProgressMap() {
return progressMap;
}
diff --git a/platforms/android/FacebookLib/src/com/facebook/ProgressOutputStream.java b/platforms/android/FacebookLib/src/com/facebook/ProgressOutputStream.java
old mode 100644
new mode 100755
index 780baffd6..8a650e678
--- a/platforms/android/FacebookLib/src/com/facebook/ProgressOutputStream.java
+++ b/platforms/android/FacebookLib/src/com/facebook/ProgressOutputStream.java
@@ -1,17 +1,21 @@
/**
- * Copyright 2010-present Facebook.
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
- * 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
+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
+ * copy, modify, and distribute this software in source code or binary form for use
+ * in connection with the web services and APIs provided by Facebook.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * As with any software that integrates with the Facebook platform, your use of
+ * this software is subject to the Facebook Developer Principles and Policies
+ * [http://developers.facebook.com/policy/]. This copyright notice shall be
+ * included in all copies or substantial portions of the software.
*
- * 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.
+ * 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.
*/
package com.facebook;
@@ -24,20 +28,24 @@
import java.util.Map;
class ProgressOutputStream extends FilterOutputStream implements RequestOutputStream {
- private final Map progressMap;
- private final RequestBatch requests;
+ private final Map progressMap;
+ private final GraphRequestBatch requests;
private final long threshold;
private long batchProgress, lastReportedProgress, maxProgress;
private RequestProgress currentRequestProgress;
- ProgressOutputStream(OutputStream out, RequestBatch requests, Map progressMap, long maxProgress) {
+ ProgressOutputStream(
+ OutputStream out,
+ GraphRequestBatch requests,
+ Map progressMap,
+ long maxProgress) {
super(out);
this.requests = requests;
this.progressMap = progressMap;
this.maxProgress = maxProgress;
- this.threshold = Settings.getOnProgressThreshold();
+ this.threshold = FacebookSdk.getOnProgressThreshold();
}
private void addProgress(long size) {
@@ -54,12 +62,13 @@ private void addProgress(long size) {
private void reportBatchProgress() {
if (batchProgress > lastReportedProgress) {
- for (RequestBatch.Callback callback : requests.getCallbacks()) {
- if (callback instanceof RequestBatch.OnProgressCallback) {
+ for (GraphRequestBatch.Callback callback : requests.getCallbacks()) {
+ if (callback instanceof GraphRequestBatch.OnProgressCallback) {
final Handler callbackHandler = requests.getCallbackHandler();
// Keep copies to avoid threading issues
- final RequestBatch.OnProgressCallback progressCallback = (RequestBatch.OnProgressCallback) callback;
+ final GraphRequestBatch.OnProgressCallback progressCallback =
+ (GraphRequestBatch.OnProgressCallback) callback;
if (callbackHandler == null) {
progressCallback.onBatchProgress(requests, batchProgress, maxProgress);
}
@@ -67,7 +76,10 @@ private void reportBatchProgress() {
callbackHandler.post(new Runnable() {
@Override
public void run() {
- progressCallback.onBatchProgress(requests, batchProgress, maxProgress);
+ progressCallback.onBatchProgress(
+ requests,
+ batchProgress,
+ maxProgress);
}
});
}
@@ -78,7 +90,7 @@ public void run() {
}
}
- public void setCurrentRequest(Request request) {
+ public void setCurrentRequest(GraphRequest request) {
currentRequestProgress = request != null? progressMap.get(request) : null;
}
diff --git a/platforms/android/FacebookLib/src/com/facebook/Request.java b/platforms/android/FacebookLib/src/com/facebook/Request.java
deleted file mode 100644
index 32fcfd050..000000000
--- a/platforms/android/FacebookLib/src/com/facebook/Request.java
+++ /dev/null
@@ -1,2456 +0,0 @@
-/**
- * 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.content.Context;
-import android.graphics.Bitmap;
-import android.location.Location;
-import android.net.Uri;
-import android.os.*;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import com.facebook.internal.*;
-import com.facebook.model.*;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.*;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.text.SimpleDateFormat;
-import java.util.*;
-import java.util.Map.Entry;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A single request to be sent to the Facebook Platform through the Graph API. The Request class provides functionality
- * relating to serializing and deserializing requests and responses, making calls in batches (with a single round-trip
- * to the service) and making calls asynchronously.
- *
- * The particular service endpoint that a request targets is determined by a graph path (see the
- * {@link #setGraphPath(String) setGraphPath} method).
- *
- * A Request can be executed either anonymously or representing an authenticated user. In the former case, no Session
- * needs to be specified, while in the latter, a Session that is in an opened state must be provided. If requests are
- * executed in a batch, a Facebook application ID must be associated with the batch, either by supplying a Session for
- * at least one of the requests in the batch (the first one found in the batch will be used) or by calling the
- * {@link #setDefaultBatchApplicationId(String) setDefaultBatchApplicationId} method.
- *
- * After completion of a request, its Session, if any, will be checked to determine if its Facebook access token needs
- * to be extended; if so, a request to extend it will be issued in the background.
- */
-public class Request {
- /**
- * The maximum number of requests that can be submitted in a single batch. This limit is enforced on the service
- * side by the Facebook platform, not by the Request class.
- */
- public static final int MAXIMUM_BATCH_SIZE = 50;
-
- public static final String TAG = Request.class.getSimpleName();
-
- private static final String ME = "me";
- private static final String MY_FRIENDS = "me/friends";
- private static final String MY_PHOTOS = "me/photos";
- private static final String MY_VIDEOS = "me/videos";
- private static final String VIDEOS_SUFFIX = "/videos";
- private static final String SEARCH = "search";
- private static final String MY_FEED = "me/feed";
- private static final String MY_STAGING_RESOURCES = "me/staging_resources";
- private static final String MY_OBJECTS_FORMAT = "me/objects/%s";
- private static final String MY_ACTION_FORMAT = "me/%s";
-
- private static final String USER_AGENT_BASE = "FBAndroidSDK";
- private static final String USER_AGENT_HEADER = "User-Agent";
- private static final String CONTENT_TYPE_HEADER = "Content-Type";
- private static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language";
-
- // Parameter names/values
- private static final String PICTURE_PARAM = "picture";
- private static final String FORMAT_PARAM = "format";
- private static final String FORMAT_JSON = "json";
- private static final String SDK_PARAM = "sdk";
- private static final String SDK_ANDROID = "android";
- private static final String ACCESS_TOKEN_PARAM = "access_token";
- private static final String BATCH_ENTRY_NAME_PARAM = "name";
- private static final String BATCH_ENTRY_OMIT_RESPONSE_ON_SUCCESS_PARAM = "omit_response_on_success";
- private static final String BATCH_ENTRY_DEPENDS_ON_PARAM = "depends_on";
- private static final String BATCH_APP_ID_PARAM = "batch_app_id";
- private static final String BATCH_RELATIVE_URL_PARAM = "relative_url";
- private static final String BATCH_BODY_PARAM = "body";
- private static final String BATCH_METHOD_PARAM = "method";
- private static final String BATCH_PARAM = "batch";
- private static final String ATTACHMENT_FILENAME_PREFIX = "file";
- private static final String ATTACHED_FILES_PARAM = "attached_files";
- private static final String ISO_8601_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssZ";
- private static final String STAGING_PARAM = "file";
- private static final String OBJECT_PARAM = "object";
-
- private static final String MIME_BOUNDARY = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f";
-
- private static String defaultBatchApplicationId;
-
- // Group 1 in the pattern is the path without the version info
- private static Pattern versionPattern = Pattern.compile("^/?v\\d+\\.\\d+/(.*)");
-
- private Session session;
- private HttpMethod httpMethod;
- private String graphPath;
- private GraphObject graphObject;
- private String batchEntryName;
- private String batchEntryDependsOn;
- private boolean batchEntryOmitResultOnSuccess = true;
- private Bundle parameters;
- private Callback callback;
- private String overriddenURL;
- private Object tag;
- private String version;
- private boolean skipClientToken = false;
-
- /**
- * Constructs a request without a session, graph path, or any other parameters.
- */
- public Request() {
- this(null, null, null, null, null);
- }
-
- /**
- * Constructs a request with a Session to retrieve a particular graph path. A Session need not be provided, in which
- * case the request is sent without an access token and thus is not executed in the context of any particular user.
- * Only certain graph requests can be expected to succeed in this case. If a Session is provided, it must be in an
- * opened state or the request will fail.
- *
- * @param session
- * the Session to use, or null
- * @param graphPath
- * the graph path to retrieve
- */
- public Request(Session session, String graphPath) {
- this(session, graphPath, null, null, null);
- }
-
- /**
- * Constructs a request with a specific Session, graph path, parameters, and HTTP method. A Session need not be
- * provided, in which case the request is sent without an access token and thus is not executed in the context of
- * any particular user. Only certain graph requests can be expected to succeed in this case. If a Session is
- * provided, it must be in an opened state or the request will fail.
- *
- * Depending on the httpMethod parameter, the object at the graph path may be retrieved, created, or deleted.
- *
- * @param session
- * the Session to use, or null
- * @param graphPath
- * the graph path to retrieve, create, or delete
- * @param parameters
- * additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers,
- * Bitmaps, Dates, or Byte arrays.
- * @param httpMethod
- * the {@link HttpMethod} to use for the request, or null for default (HttpMethod.GET)
- */
- public Request(Session session, String graphPath, Bundle parameters, HttpMethod httpMethod) {
- this(session, graphPath, parameters, httpMethod, null);
- }
-
- /**
- * Constructs a request with a specific Session, graph path, parameters, and HTTP method. A Session need not be
- * provided, in which case the request is sent without an access token and thus is not executed in the context of
- * any particular user. Only certain graph requests can be expected to succeed in this case. If a Session is
- * provided, it must be in an opened state or the request will fail.
- *
- * Depending on the httpMethod parameter, the object at the graph path may be retrieved, created, or deleted.
- *
- * @param session
- * the Session to use, or null
- * @param graphPath
- * the graph path to retrieve, create, or delete
- * @param parameters
- * additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers,
- * Bitmaps, Dates, or Byte arrays.
- * @param httpMethod
- * the {@link HttpMethod} to use for the request, or null for default (HttpMethod.GET)
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- */
- public Request(Session session, String graphPath, Bundle parameters, HttpMethod httpMethod, Callback callback) {
- this(session, graphPath, parameters, httpMethod, callback, null);
- }
-
- /**
- * Constructs a request with a specific Session, graph path, parameters, and HTTP method. A Session need not be
- * provided, in which case the request is sent without an access token and thus is not executed in the context of
- * any particular user. Only certain graph requests can be expected to succeed in this case. If a Session is
- * provided, it must be in an opened state or the request will fail.
- *
- * Depending on the httpMethod parameter, the object at the graph path may be retrieved, created, or deleted.
- *
- * @param session
- * the Session to use, or null
- * @param graphPath
- * the graph path to retrieve, create, or delete
- * @param parameters
- * additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers,
- * Bitmaps, Dates, or Byte arrays.
- * @param httpMethod
- * the {@link HttpMethod} to use for the request, or null for default (HttpMethod.GET)
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @param version
- * the version of the Graph API
- */
- public Request(Session session, String graphPath, Bundle parameters, HttpMethod httpMethod, Callback callback, String version) {
- this.session = session;
- this.graphPath = graphPath;
- this.callback = callback;
- this.version = version;
-
- setHttpMethod(httpMethod);
-
- if (parameters != null) {
- this.parameters = new Bundle(parameters);
- } else {
- this.parameters = new Bundle();
- }
-
- if (this.version == null) {
- this.version = ServerProtocol.getAPIVersion();
- }
- }
-
- Request(Session session, URL overriddenURL) {
- this.session = session;
- this.overriddenURL = overriddenURL.toString();
-
- setHttpMethod(HttpMethod.GET);
-
- this.parameters = new Bundle();
- }
-
- /**
- * Creates a new Request configured to post a GraphObject to a particular graph path, to either create or update the
- * object at that path.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param graphPath
- * the graph path to retrieve, create, or delete
- * @param graphObject
- * the GraphObject to create or update
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newPostRequest(Session session, String graphPath, GraphObject graphObject, Callback callback) {
- Request request = new Request(session, graphPath, null, HttpMethod.POST , callback);
- request.setGraphObject(graphObject);
- return request;
- }
-
- /**
- * Creates a new Request configured to retrieve a user's own profile.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newMeRequest(Session session, final GraphUserCallback callback) {
- Callback wrapper = new Callback() {
- @Override
- public void onCompleted(Response response) {
- if (callback != null) {
- callback.onCompleted(response.getGraphObjectAs(GraphUser.class), response);
- }
- }
- };
- return new Request(session, ME, null, null, wrapper);
- }
-
- /**
- * Creates a new Request configured to retrieve a user's friend list.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newMyFriendsRequest(Session session, final GraphUserListCallback callback) {
- Callback wrapper = new Callback() {
- @Override
- public void onCompleted(Response response) {
- if (callback != null) {
- callback.onCompleted(typedListFromResponse(response, GraphUser.class), response);
- }
- }
- };
- return new Request(session, MY_FRIENDS, null, null, wrapper);
- }
-
- /**
- * Creates a new Request configured to upload a photo to the user's default photo album.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param image
- * the image to upload
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newUploadPhotoRequest(Session session, Bitmap image, Callback callback) {
- Bundle parameters = new Bundle(1);
- parameters.putParcelable(PICTURE_PARAM, image);
-
- return new Request(session, MY_PHOTOS, parameters, HttpMethod.POST, callback);
- }
-
- /**
- * Creates a new Request configured to upload a photo to the user's default photo album. The photo
- * will be read from the specified stream.
- *
- * @param session the Session to use, or null; if non-null, the session must be in an opened state
- * @param file the file containing the photo to upload
- * @param callback a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newUploadPhotoRequest(Session session, File file,
- Callback callback) throws FileNotFoundException {
- ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- Bundle parameters = new Bundle(1);
- parameters.putParcelable(PICTURE_PARAM, descriptor);
-
- return new Request(session, MY_PHOTOS, parameters, HttpMethod.POST, callback);
- }
-
- /**
- * Creates a new Request configured to upload a photo to the user's default photo album. The photo
- * will be read from the specified file descriptor.
- *
- * @param session the Session to use, or null; if non-null, the session must be in an opened state
- * @param file the file to upload
- * @param callback a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newUploadVideoRequest(Session session, File file,
- Callback callback) throws FileNotFoundException {
- ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- Bundle parameters = new Bundle(1);
- parameters.putParcelable(file.getName(), descriptor);
-
- return new Request(session, MY_VIDEOS, parameters, HttpMethod.POST, callback);
- }
-
- /**
- * Creates a new Request configured to retrieve a particular graph path.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param graphPath
- * the graph path to retrieve
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newGraphPathRequest(Session session, String graphPath, Callback callback) {
- return new Request(session, graphPath, null, null, callback);
- }
-
- /**
- * Creates a new Request that is configured to perform a search for places near a specified location via the Graph
- * API. At least one of location or searchText must be specified.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param location
- * the location around which to search; only the latitude and longitude components of the location are
- * meaningful
- * @param radiusInMeters
- * the radius around the location to search, specified in meters; this is ignored if
- * no location is specified
- * @param resultsLimit
- * the maximum number of results to return
- * @param searchText
- * optional text to search for as part of the name or type of an object
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- *
- * @throws FacebookException If neither location nor searchText is specified
- */
- public static Request newPlacesSearchRequest(Session session, Location location, int radiusInMeters,
- int resultsLimit, String searchText, final GraphPlaceListCallback callback) {
- if (location == null && Utility.isNullOrEmpty(searchText)) {
- throw new FacebookException("Either location or searchText must be specified.");
- }
-
- Bundle parameters = new Bundle(5);
- parameters.putString("type", "place");
- parameters.putInt("limit", resultsLimit);
- if (location != null) {
- parameters.putString("center",
- String.format(Locale.US, "%f,%f", location.getLatitude(), location.getLongitude()));
- parameters.putInt("distance", radiusInMeters);
- }
- if (!Utility.isNullOrEmpty(searchText)) {
- parameters.putString("q", searchText);
- }
-
- Callback wrapper = new Callback() {
- @Override
- public void onCompleted(Response response) {
- if (callback != null) {
- callback.onCompleted(typedListFromResponse(response, GraphPlace.class), response);
- }
- }
- };
-
- return new Request(session, SEARCH, parameters, HttpMethod.GET, wrapper);
- }
-
- /**
- * Creates a new Request configured to post a status update to a user's feed.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param message
- * the text of the status update
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newStatusUpdateRequest(Session session, String message, Callback callback) {
- return newStatusUpdateRequest(session, message, (String)null, null, callback);
- }
-
- /**
- * Creates a new Request configured to post a status update to a user's feed.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param message
- * the text of the status update
- * @param placeId
- * an optional place id to associate with the post
- * @param tagIds
- * an optional list of user ids to tag in the post
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- private static Request newStatusUpdateRequest(Session session, String message, String placeId, List tagIds,
- Callback callback) {
-
- Bundle parameters = new Bundle();
- parameters.putString("message", message);
-
- if (placeId != null) {
- parameters.putString("place", placeId);
- }
-
- if (tagIds != null && tagIds.size() > 0) {
- String tags = TextUtils.join(",", tagIds);
- parameters.putString("tags", tags);
- }
-
- return new Request(session, MY_FEED, parameters, HttpMethod.POST, callback);
- }
-
- /**
- * Creates a new Request configured to post a status update to a user's feed.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param message
- * the text of the status update
- * @param place
- * an optional place to associate with the post
- * @param tags
- * an optional list of users to tag in the post
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newStatusUpdateRequest(Session session, String message, GraphPlace place,
- List tags, Callback callback) {
-
- List tagIds = null;
- if (tags != null) {
- tagIds = new ArrayList(tags.size());
- for (GraphUser tag: tags) {
- tagIds.add(tag.getId());
- }
- }
- String placeId = place == null ? null : place.getId();
- return newStatusUpdateRequest(session, message, placeId, tagIds, callback);
- }
-
- /**
- * Creates a new Request configured to retrieve an App User ID for the app's Facebook user. Callers
- * will send this ID back to their own servers, collect up a set to create a Facebook Custom Audience with,
- * and then use the resultant Custom Audience to target ads.
- *
- * The GraphObject in the response will include an "custom_audience_third_party_id" property, with the value
- * being the ID retrieved. This ID is an encrypted encoding of the Facebook user's ID and the
- * invoking Facebook app ID. Multiple calls with the same user will return different IDs, thus these IDs cannot be
- * used to correlate behavior across devices or applications, and are only meaningful when sent back to Facebook
- * for creating Custom Audiences.
- *
- * The ID retrieved represents the Facebook user identified in the following way: if the specified session
- * (or activeSession if the specified session is `null`) is open, the ID will represent the user associated with
- * the activeSession; otherwise the ID will represent the user logged into the native Facebook app on the device.
- * A `null` ID will be provided into the callback if a) there is no native Facebook app, b) no one is logged into
- * it, or c) the app has previously called
- * {@link Settings#setLimitEventAndDataUsage(android.content.Context, boolean)} with `true` for this user.
- * You must call this method from a background thread for it to work properly.
- *
- * @param session
- * the Session to issue the Request on, or null; if non-null, the session must be in an opened state.
- * If there is no logged-in Facebook user, null is the expected choice.
- * @param context
- * the Application context from which the app ID will be pulled, and from which the 'attribution ID'
- * for the Facebook user is determined. If there has been no app ID set, an exception will be thrown.
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions.
- * The GraphObject in the Response will contain a "custom_audience_third_party_id" property that
- * represents the user as described above.
- * @return a Request that is ready to execute
- */
- public static Request newCustomAudienceThirdPartyIdRequest(Session session, Context context, Callback callback) {
- return newCustomAudienceThirdPartyIdRequest(session, context, null, callback);
- }
-
- /**
- * Creates a new Request configured to retrieve an App User ID for the app's Facebook user. Callers
- * will send this ID back to their own servers, collect up a set to create a Facebook Custom Audience with,
- * and then use the resultant Custom Audience to target ads.
- *
- * The GraphObject in the response will include an "custom_audience_third_party_id" property, with the value
- * being the ID retrieved. This ID is an encrypted encoding of the Facebook user's ID and the
- * invoking Facebook app ID. Multiple calls with the same user will return different IDs, thus these IDs cannot be
- * used to correlate behavior across devices or applications, and are only meaningful when sent back to Facebook
- * for creating Custom Audiences.
- *
- * The ID retrieved represents the Facebook user identified in the following way: if the specified session
- * (or activeSession if the specified session is `null`) is open, the ID will represent the user associated with
- * the activeSession; otherwise the ID will represent the user logged into the native Facebook app on the device.
- * A `null` ID will be provided into the callback if a) there is no native Facebook app, b) no one is logged into
- * it, or c) the app has previously called
- * {@link Settings#setLimitEventAndDataUsage(android.content.Context, boolean)} ;} with `true` for this user.
- * You must call this method from a background thread for it to work properly.
- *
- * @param session
- * the Session to issue the Request on, or null; if non-null, the session must be in an opened state.
- * If there is no logged-in Facebook user, null is the expected choice.
- * @param context
- * the Application context from which the app ID will be pulled, and from which the 'attribution ID'
- * for the Facebook user is determined. If there has been no app ID set, an exception will be thrown.
- * @param applicationId
- * explicitly specified Facebook App ID. If null, and there's a valid session, then the application ID
- * from the session will be used, otherwise the application ID from metadata will be used.
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions.
- * The GraphObject in the Response will contain a "custom_audience_third_party_id" property that
- * represents the user as described above.
- * @return a Request that is ready to execute
- */
- public static Request newCustomAudienceThirdPartyIdRequest(Session session,
- Context context, String applicationId, Callback callback) {
-
- // if provided session or activeSession is opened, use it.
- if (session == null) {
- session = Session.getActiveSession();
- }
-
- if (session != null && !session.isOpened()) {
- session = null;
- }
-
- if (applicationId == null) {
- if (session != null) {
- applicationId = session.getApplicationId();
- } else {
- applicationId = Utility.getMetadataApplicationId(context);
- }
- }
-
- if (applicationId == null) {
- throw new FacebookException("Facebook App ID cannot be determined");
- }
-
- String endpoint = applicationId + "/custom_audience_third_party_id";
- AttributionIdentifiers attributionIdentifiers = AttributionIdentifiers.getAttributionIdentifiers(context);
- Bundle parameters = new Bundle();
-
- if (session == null) {
- // Only use the attributionID if we don't have an open session. If we do have an open session, then
- // the user token will be used to identify the user, and is more reliable than the attributionID.
- String udid = attributionIdentifiers.getAttributionId() != null
- ? attributionIdentifiers.getAttributionId()
- : attributionIdentifiers.getAndroidAdvertiserId();
- if (attributionIdentifiers.getAttributionId() != null) {
- parameters.putString("udid", udid);
- }
- }
-
- // Server will choose to not provide the App User ID in the event that event usage has been limited for
- // this user for this app.
- if (Settings.getLimitEventAndDataUsage(context) || attributionIdentifiers.isTrackingLimited()) {
- parameters.putString("limit_event_usage", "1");
- }
-
- return new Request(session, endpoint, parameters, HttpMethod.GET, callback);
- }
-
- /**
- * Creates a new Request configured to upload an image to create a staging resource. Staging resources
- * allow you to post binary data such as images, in preparation for a post of an Open Graph object or action
- * which references the image. The URI returned when uploading a staging resource may be passed as the image
- * property for an Open Graph object or action.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param image
- * the image to upload
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newUploadStagingResourceWithImageRequest(Session session,
- Bitmap image, Callback callback) {
- Bundle parameters = new Bundle(1);
- parameters.putParcelable(STAGING_PARAM, image);
-
- return new Request(session, MY_STAGING_RESOURCES, parameters, HttpMethod.POST, callback);
- }
-
- /**
- * Creates a new Request configured to upload an image to create a staging resource. Staging resources
- * allow you to post binary data such as images, in preparation for a post of an Open Graph object or action
- * which references the image. The URI returned when uploading a staging resource may be passed as the image
- * property for an Open Graph object or action.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param file
- * the file containing the image to upload
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newUploadStagingResourceWithImageRequest(Session session,
- File file, Callback callback) throws FileNotFoundException {
- ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- ParcelFileDescriptorWithMimeType descriptorWithMimeType = new ParcelFileDescriptorWithMimeType(descriptor, "image/png");
- Bundle parameters = new Bundle(1);
- parameters.putParcelable(STAGING_PARAM, descriptorWithMimeType);
-
- return new Request(session, MY_STAGING_RESOURCES, parameters, HttpMethod.POST, callback);
- }
-
- /**
- * Creates a new Request configured to create a user owned Open Graph object.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param openGraphObject
- * the Open Graph object to create; must not be null, and must have a non-empty type and title
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newPostOpenGraphObjectRequest(Session session,
- OpenGraphObject openGraphObject, Callback callback) {
- if (openGraphObject == null) {
- throw new FacebookException("openGraphObject cannot be null");
- }
- if (Utility.isNullOrEmpty(openGraphObject.getType())) {
- throw new FacebookException("openGraphObject must have non-null 'type' property");
- }
- if (Utility.isNullOrEmpty(openGraphObject.getTitle())) {
- throw new FacebookException("openGraphObject must have non-null 'title' property");
- }
-
- String path = String.format(MY_OBJECTS_FORMAT, openGraphObject.getType());
- Bundle bundle = new Bundle();
- bundle.putString(OBJECT_PARAM, openGraphObject.getInnerJSONObject().toString());
- return new Request(session, path, bundle, HttpMethod.POST, callback);
- }
-
- /**
- * Creates a new Request configured to create a user owned Open Graph object.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param type
- * the fully-specified Open Graph object type (e.g., my_app_namespace:my_object_name); must not be null
- * @param title
- * the title of the Open Graph object; must not be null
- * @param imageUrl
- * the link to an image to be associated with the Open Graph object; may be null
- * @param url
- * the url to be associated with the Open Graph object; may be null
- * @param description
- * the description to be associated with the object; may be null
- * @param objectProperties
- * any additional type-specific properties for the Open Graph object; may be null
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions;
- * may be null
- * @return a Request that is ready to execute
- */
- public static Request newPostOpenGraphObjectRequest(Session session, String type, String title, String imageUrl,
- String url, String description, GraphObject objectProperties, Callback callback) {
- OpenGraphObject openGraphObject = OpenGraphObject.Factory.createForPost(OpenGraphObject.class, type, title,
- imageUrl, url, description);
- if (objectProperties != null) {
- openGraphObject.setData(objectProperties);
- }
-
- return newPostOpenGraphObjectRequest(session, openGraphObject, callback);
- }
-
- /**
- * Creates a new Request configured to publish an Open Graph action.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param openGraphAction
- * the Open Graph object to create; must not be null, and must have a non-empty 'type'
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newPostOpenGraphActionRequest(Session session, OpenGraphAction openGraphAction,
- Callback callback) {
- if (openGraphAction == null) {
- throw new FacebookException("openGraphAction cannot be null");
- }
- if (Utility.isNullOrEmpty(openGraphAction.getType())) {
- throw new FacebookException("openGraphAction must have non-null 'type' property");
- }
-
- String path = String.format(MY_ACTION_FORMAT, openGraphAction.getType());
- return newPostRequest(session, path, openGraphAction, callback);
- }
-
- /**
- * Creates a new Request configured to delete a resource through the Graph API.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param id
- * the id of the object to delete
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newDeleteObjectRequest(Session session, String id, Callback callback) {
- return new Request(session, id, null, HttpMethod.DELETE, callback);
- }
-
- /**
- * Creates a new Request configured to update a user owned Open Graph object.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param openGraphObject
- * the Open Graph object to update, which must have a valid 'id' property
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newUpdateOpenGraphObjectRequest(Session session, OpenGraphObject openGraphObject,
- Callback callback) {
- if (openGraphObject == null) {
- throw new FacebookException("openGraphObject cannot be null");
- }
-
- String path = openGraphObject.getId();
- if (path == null) {
- throw new FacebookException("openGraphObject must have an id");
- }
-
- Bundle bundle = new Bundle();
- bundle.putString(OBJECT_PARAM, openGraphObject.getInnerJSONObject().toString());
- return new Request(session, path, bundle, HttpMethod.POST, callback);
- }
-
- /**
- * Creates a new Request configured to update a user owned Open Graph object.
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param id
- * the id of the Open Graph object
- * @param title
- * the title of the Open Graph object
- * @param imageUrl
- * the link to an image to be associated with the Open Graph object
- * @param url
- * the url to be associated with the Open Graph object
- * @param description
- * the description to be associated with the object
- * @param objectProperties
- * any additional type-specific properties for the Open Graph object
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a Request that is ready to execute
- */
- public static Request newUpdateOpenGraphObjectRequest(Session session, String id, String title, String imageUrl,
- String url, String description, GraphObject objectProperties, Callback callback) {
- OpenGraphObject openGraphObject = OpenGraphObject.Factory.createForPost(OpenGraphObject.class, null, title,
- imageUrl, url, description);
- openGraphObject.setId(id);
- openGraphObject.setData(objectProperties);
-
- return newUpdateOpenGraphObjectRequest(session, openGraphObject, callback);
- }
-
- /**
- * Returns the GraphObject, if any, associated with this request.
- *
- * @return the GraphObject associated with this requeset, or null if there is none
- */
- public final GraphObject getGraphObject() {
- return this.graphObject;
- }
-
- /**
- * Sets the GraphObject associated with this request. This is meaningful only for POST requests.
- *
- * @param graphObject
- * the GraphObject to upload along with this request
- */
- public final void setGraphObject(GraphObject graphObject) {
- this.graphObject = graphObject;
- }
-
- /**
- * Returns the graph path of this request, if any.
- *
- * @return the graph path of this request, or null if there is none
- */
- public final String getGraphPath() {
- return this.graphPath;
- }
-
- /**
- * Sets the graph path of this request.
- *
- * @param graphPath
- * the graph path for this request
- */
- public final void setGraphPath(String graphPath) {
- this.graphPath = graphPath;
- }
-
- /**
- * Returns the {@link HttpMethod} to use for this request.
- *
- * @return the HttpMethod
- */
- public final HttpMethod getHttpMethod() {
- return this.httpMethod;
- }
-
- /**
- * Sets the {@link HttpMethod} to use for this request.
- *
- * @param httpMethod
- * the HttpMethod, or null for the default (HttpMethod.GET).
- */
- public final void setHttpMethod(HttpMethod httpMethod) {
- if (overriddenURL != null && httpMethod != HttpMethod.GET) {
- throw new FacebookException("Can't change HTTP method on request with overridden URL.");
- }
- this.httpMethod = (httpMethod != null) ? httpMethod : HttpMethod.GET;
- }
-
- /**
- * Returns the version of the API that this request will use. By default this is the current API at the time
- * the SDK is released.
- *
- * @return the version that this request will use
- */
- public final String getVersion() {
- return this.version;
- }
-
- /**
- * Set the version to use for this request. By default the version will be the current API at the time the SDK
- * is released. Only use this if you need to explicitly override.
- *
- * @param version The version to use. Should look like "v2.0"
- */
- public final void setVersion(String version) {
- this.version = version;
- }
-
- /**
- * This is an internal function that is not meant to be used by developers.
- */
- public final void setSkipClientToken(boolean skipClientToken) {
- this.skipClientToken = skipClientToken;
- }
-
- /**
- * Returns the parameters for this request.
- *
- * @return the parameters
- */
- public final Bundle getParameters() {
- return this.parameters;
- }
-
- /**
- * Sets the parameters for this request.
- *
- * @param parameters
- * the parameters
- */
- public final void setParameters(Bundle parameters) {
- this.parameters = parameters;
- }
-
- /**
- * Returns the Session associated with this request.
- *
- * @return the Session associated with this request, or null if none has been specified
- */
- public final Session getSession() {
- return this.session;
- }
-
- /**
- * Sets the Session to use for this request. The Session does not need to be opened at the time it is specified, but
- * it must be opened by the time the request is executed.
- *
- * @param session
- * the Session to use for this request
- */
- public final void setSession(Session session) {
- this.session = session;
- }
-
- /**
- * Returns the name of this request's entry in a batched request.
- *
- * @return the name of this request's batch entry, or null if none has been specified
- */
- public final String getBatchEntryName() {
- return this.batchEntryName;
- }
-
- /**
- * Sets the name of this request's entry in a batched request. This value is only used if this request is submitted
- * as part of a batched request. It can be used to specified dependencies between requests. See Batch Requests in the Graph API
- * documentation for more details.
- *
- * @param batchEntryName
- * the name of this request's entry in a batched request, which must be unique within a particular batch
- * of requests
- */
- public final void setBatchEntryName(String batchEntryName) {
- this.batchEntryName = batchEntryName;
- }
-
- /**
- * Returns the name of the request that this request entry explicitly depends on in a batched request.
- *
- * @return the name of this request's dependency, or null if none has been specified
- */
- public final String getBatchEntryDependsOn() {
- return this.batchEntryDependsOn;
- }
-
- /**
- * Sets the name of the request entry that this request explicitly depends on in a batched request. This value is
- * only used if this request is submitted as part of a batched request. It can be used to specified dependencies
- * between requests. See Batch Requests in
- * the Graph API documentation for more details.
- *
- * @param batchEntryDependsOn
- * the name of the request entry that this entry depends on in a batched request
- */
- public final void setBatchEntryDependsOn(String batchEntryDependsOn) {
- this.batchEntryDependsOn = batchEntryDependsOn;
- }
-
-
- /**
- * Returns whether or not this batch entry will return a response if it is successful. Only applies if another
- * request entry in the batch specifies this entry as a dependency.
- *
- * @return the name of this request's dependency, or null if none has been specified
- */
- public final boolean getBatchEntryOmitResultOnSuccess() {
- return this.batchEntryOmitResultOnSuccess;
- }
-
- /**
- * Sets whether or not this batch entry will return a response if it is successful. Only applies if another
- * request entry in the batch specifies this entry as a dependency. See
- * Batch Requests in the Graph API
- * documentation for more details.
- *
- * @param batchEntryOmitResultOnSuccess
- * the name of the request entry that this entry depends on in a batched request
- */
- public final void setBatchEntryOmitResultOnSuccess(boolean batchEntryOmitResultOnSuccess) {
- this.batchEntryOmitResultOnSuccess = batchEntryOmitResultOnSuccess;
- }
-
- /**
- * Gets the default Facebook application ID that will be used to submit batched requests if none of those requests
- * specifies a Session. Batched requests require an application ID, so either at least one request in a batch must
- * specify a Session or the application ID must be specified explicitly.
- *
- * @return the Facebook application ID to use for batched requests if none can be determined
- */
- public static final String getDefaultBatchApplicationId() {
- return Request.defaultBatchApplicationId;
- }
-
- /**
- * Sets the default application ID that will be used to submit batched requests if none of those requests specifies
- * a Session. Batched requests require an application ID, so either at least one request in a batch must specify a
- * Session or the application ID must be specified explicitly.
- *
- * @param applicationId
- * the Facebook application ID to use for batched requests if none can be determined
- */
- public static final void setDefaultBatchApplicationId(String applicationId) {
- defaultBatchApplicationId = applicationId;
- }
-
- /**
- * Returns the callback which will be called when the request finishes.
- *
- * @return the callback
- */
- public final Callback getCallback() {
- return callback;
- }
-
- /**
- * Sets the callback which will be called when the request finishes.
- *
- * @param callback
- * the callback
- */
- public final void setCallback(Callback callback) {
- this.callback = callback;
- }
-
- /**
- * Sets the tag on the request; this is an application-defined object that can be used to distinguish
- * between different requests. Its value has no effect on the execution of the request.
- *
- * @param tag an object to serve as a tag, or null
- */
- public final void setTag(Object tag) {
- this.tag = tag;
- }
-
- /**
- * Gets the tag on the request; this is an application-defined object that can be used to distinguish
- * between different requests. Its value has no effect on the execution of the request.
- *
- * @return an object that serves as a tag, or null
- */
- public final Object getTag() {
- return tag;
- }
-
- /**
- * Starts a new Request configured to post a GraphObject to a particular graph path, to either create or update the
- * object at that path.
- *
- * This should only be called from the UI thread.
- *
- * This method is deprecated. Prefer to call Request.newPostRequest(...).executeAsync();
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param graphPath
- * the graph path to retrieve, create, or delete
- * @param graphObject
- * the GraphObject to create or update
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a RequestAsyncTask that is executing the request
- */
- @Deprecated
- public static RequestAsyncTask executePostRequestAsync(Session session, String graphPath, GraphObject graphObject,
- Callback callback) {
- return newPostRequest(session, graphPath, graphObject, callback).executeAsync();
- }
-
- /**
- * Starts a new Request configured to retrieve a user's own profile.
- *
- * This should only be called from the UI thread.
- *
- * This method is deprecated. Prefer to call Request.newMeRequest(...).executeAsync();
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a RequestAsyncTask that is executing the request
- */
- @Deprecated
- public static RequestAsyncTask executeMeRequestAsync(Session session, GraphUserCallback callback) {
- return newMeRequest(session, callback).executeAsync();
- }
-
- /**
- * Starts a new Request configured to retrieve a user's friend list.
- *
- * This should only be called from the UI thread.
- *
- * This method is deprecated. Prefer to call Request.newMyFriendsRequest(...).executeAsync();
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a RequestAsyncTask that is executing the request
- */
- @Deprecated
- public static RequestAsyncTask executeMyFriendsRequestAsync(Session session, GraphUserListCallback callback) {
- return newMyFriendsRequest(session, callback).executeAsync();
- }
-
- /**
- * Starts a new Request configured to upload a photo to the user's default photo album.
- *
- * This should only be called from the UI thread.
- *
- * This method is deprecated. Prefer to call Request.newUploadPhotoRequest(...).executeAsync();
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param image
- * the image to upload
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a RequestAsyncTask that is executing the request
- */
- @Deprecated
- public static RequestAsyncTask executeUploadPhotoRequestAsync(Session session, Bitmap image, Callback callback) {
- return newUploadPhotoRequest(session, image, callback).executeAsync();
- }
-
- /**
- * Starts a new Request configured to upload a photo to the user's default photo album. The photo
- * will be read from the specified stream.
- *
- * This should only be called from the UI thread.
- *
- * This method is deprecated. Prefer to call Request.newUploadPhotoRequest(...).executeAsync();
- *
- * @param session the Session to use, or null; if non-null, the session must be in an opened state
- * @param file the file containing the photo to upload
- * @param callback a callback that will be called when the request is completed to handle success or error conditions
- * @return a RequestAsyncTask that is executing the request
- */
- @Deprecated
- public static RequestAsyncTask executeUploadPhotoRequestAsync(Session session, File file,
- Callback callback) throws FileNotFoundException {
- return newUploadPhotoRequest(session, file, callback).executeAsync();
- }
-
- /**
- * Starts a new Request configured to retrieve a particular graph path.
- *
- * This should only be called from the UI thread.
- *
- * This method is deprecated. Prefer to call Request.newGraphPathRequest(...).executeAsync();
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param graphPath
- * the graph path to retrieve
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a RequestAsyncTask that is executing the request
- */
- @Deprecated
- public static RequestAsyncTask executeGraphPathRequestAsync(Session session, String graphPath, Callback callback) {
- return newGraphPathRequest(session, graphPath, callback).executeAsync();
- }
-
- /**
- * Starts a new Request that is configured to perform a search for places near a specified location via the Graph
- * API.
- *
- * This should only be called from the UI thread.
- *
- * This method is deprecated. Prefer to call Request.newPlacesSearchRequest(...).executeAsync();
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param location
- * the location around which to search; only the latitude and longitude components of the location are
- * meaningful
- * @param radiusInMeters
- * the radius around the location to search, specified in meters
- * @param resultsLimit
- * the maximum number of results to return
- * @param searchText
- * optional text to search for as part of the name or type of an object
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a RequestAsyncTask that is executing the request
- *
- * @throws FacebookException If neither location nor searchText is specified
- */
- @Deprecated
- public static RequestAsyncTask executePlacesSearchRequestAsync(Session session, Location location,
- int radiusInMeters, int resultsLimit, String searchText, GraphPlaceListCallback callback) {
- return newPlacesSearchRequest(session, location, radiusInMeters, resultsLimit, searchText, callback)
- .executeAsync();
- }
-
- /**
- * Starts a new Request configured to post a status update to a user's feed.
- *
- * This should only be called from the UI thread.
- *
- * This method is deprecated. Prefer to call Request.newStatusUpdateRequest(...).executeAsync();
- *
- * @param session
- * the Session to use, or null; if non-null, the session must be in an opened state
- * @param message
- * the text of the status update
- * @param callback
- * a callback that will be called when the request is completed to handle success or error conditions
- * @return a RequestAsyncTask that is executing the request
- */
- @Deprecated
- public static RequestAsyncTask executeStatusUpdateRequestAsync(Session session, String message, Callback callback) {
- return newStatusUpdateRequest(session, message, callback).executeAsync();
- }
-
- /**
- * Executes this request and returns the response.
- *
- * This should only be called if you have transitioned off the UI thread.
- *
- * @return the Response object representing the results of the request
- *
- * @throws FacebookException
- * If there was an error in the protocol used to communicate with the service
- * @throws IllegalArgumentException
- */
- public final Response executeAndWait() {
- return Request.executeAndWait(this);
- }
-
- /**
- * Executes this request and returns the response.
- *
- * This should only be called from the UI thread.
- *
- * @return a RequestAsyncTask that is executing the request
- *
- * @throws IllegalArgumentException
- */
- public final RequestAsyncTask executeAsync() {
- return Request.executeBatchAsync(this);
- }
-
- /**
- * Serializes one or more requests but does not execute them. The resulting HttpURLConnection can be executed
- * explicitly by the caller.
- *
- * @param requests
- * one or more Requests to serialize
- * @return an HttpURLConnection which is ready to execute
- *
- * @throws FacebookException
- * If any of the requests in the batch are badly constructed or if there are problems
- * contacting the service
- * @throws IllegalArgumentException if the passed in array is zero-length
- * @throws NullPointerException if the passed in array or any of its contents are null
- */
- public static HttpURLConnection toHttpConnection(Request... requests) {
- return toHttpConnection(Arrays.asList(requests));
- }
-
- /**
- * Serializes one or more requests but does not execute them. The resulting HttpURLConnection can be executed
- * explicitly by the caller.
- *
- * @param requests
- * one or more Requests to serialize
- * @return an HttpURLConnection which is ready to execute
- *
- * @throws FacebookException
- * If any of the requests in the batch are badly constructed or if there are problems
- * contacting the service
- * @throws IllegalArgumentException if the passed in collection is empty
- * @throws NullPointerException if the passed in collection or any of its contents are null
- */
- public static HttpURLConnection toHttpConnection(Collection requests) {
- Validate.notEmptyAndContainsNoNulls(requests, "requests");
-
- return toHttpConnection(new RequestBatch(requests));
- }
-
-
- /**
- * Serializes one or more requests but does not execute them. The resulting HttpURLConnection can be executed
- * explicitly by the caller.
- *
- * @param requests
- * a RequestBatch to serialize
- * @return an HttpURLConnection which is ready to execute
- *
- * @throws FacebookException
- * If any of the requests in the batch are badly constructed or if there are problems
- * contacting the service
- * @throws IllegalArgumentException
- */
- public static HttpURLConnection toHttpConnection(RequestBatch requests) {
-
- URL url = null;
- try {
- if (requests.size() == 1) {
- // Single request case.
- Request request = requests.get(0);
- // In the non-batch case, the URL we use really is the same one returned by getUrlForSingleRequest.
- url = new URL(request.getUrlForSingleRequest());
- } else {
- // Batch case -- URL is just the graph API base, individual request URLs are serialized
- // as relative_url parameters within each batch entry.
- url = new URL(ServerProtocol.getGraphUrlBase());
- }
- } catch (MalformedURLException e) {
- throw new FacebookException("could not construct URL for request", e);
- }
-
- HttpURLConnection connection;
- try {
- connection = createConnection(url);
-
- serializeToUrlConnection(requests, connection);
- } catch (IOException e) {
- throw new FacebookException("could not construct request body", e);
- } catch (JSONException e) {
- throw new FacebookException("could not construct request body", e);
- }
-
- return connection;
- }
-
- /**
- * Executes a single request on the current thread and returns the response.
- *
- * This should only be used if you have transitioned off the UI thread.
- *
- * @param request
- * the Request to execute
- *
- * @return the Response object representing the results of the request
- *
- * @throws FacebookException
- * If there was an error in the protocol used to communicate with the service
- */
- public static Response executeAndWait(Request request) {
- List responses = executeBatchAndWait(request);
-
- if (responses == null || responses.size() != 1) {
- throw new FacebookException("invalid state: expected a single response");
- }
-
- return responses.get(0);
- }
-
- /**
- * Executes requests on the current thread as a single batch and returns the responses.
- *
- * This should only be used if you have transitioned off the UI thread.
- *
- * @param requests
- * the Requests to execute
- *
- * @return a list of Response objects representing the results of the requests; responses are returned in the same
- * order as the requests were specified.
- *
- * @throws NullPointerException
- * In case of a null request
- * @throws FacebookException
- * If there was an error in the protocol used to communicate with the service
- */
- public static List executeBatchAndWait(Request... requests) {
- Validate.notNull(requests, "requests");
-
- return executeBatchAndWait(Arrays.asList(requests));
- }
-
- /**
- * Executes requests as a single batch on the current thread and returns the responses.
- *
- * This should only be used if you have transitioned off the UI thread.
- *
- * @param requests
- * the Requests to execute
- *
- * @return a list of Response objects representing the results of the requests; responses are returned in the same
- * order as the requests were specified.
- *
- * @throws FacebookException
- * If there was an error in the protocol used to communicate with the service
- */
- public static List executeBatchAndWait(Collection requests) {
- return executeBatchAndWait(new RequestBatch(requests));
- }
-
- /**
- * Executes requests on the current thread as a single batch and returns the responses.
- *
- * This should only be used if you have transitioned off the UI thread.
- *
- * @param requests
- * the batch of Requests to execute
- *
- * @return a list of Response objects representing the results of the requests; responses are returned in the same
- * order as the requests were specified.
- *
- * @throws FacebookException
- * If there was an error in the protocol used to communicate with the service
- * @throws IllegalArgumentException if the passed in RequestBatch is empty
- * @throws NullPointerException if the passed in RequestBatch or any of its contents are null
- */
- public static List executeBatchAndWait(RequestBatch requests) {
- Validate.notEmptyAndContainsNoNulls(requests, "requests");
-
- HttpURLConnection connection = null;
- try {
- connection = toHttpConnection(requests);
- } catch (Exception ex) {
- List responses = Response.constructErrorResponses(requests.getRequests(), null, new FacebookException(ex));
- runCallbacks(requests, responses);
- return responses;
- }
-
- List responses = executeConnectionAndWait(connection, requests);
- return responses;
- }
-
- /**
- * Executes requests as a single batch asynchronously. This function will return immediately, and the requests will
- * be processed on a separate thread. In order to process results of a request, or determine whether a request
- * succeeded or failed, a callback must be specified (see the {@link #setCallback(Callback) setCallback} method).
- *
- * This should only be called from the UI thread.
- *
- * @param requests
- * the Requests to execute
- * @return a RequestAsyncTask that is executing the request
- *
- * @throws NullPointerException
- * If a null request is passed in
- */
- public static RequestAsyncTask executeBatchAsync(Request... requests) {
- Validate.notNull(requests, "requests");
-
- return executeBatchAsync(Arrays.asList(requests));
- }
-
- /**
- * Executes requests as a single batch asynchronously. This function will return immediately, and the requests will
- * be processed on a separate thread. In order to process results of a request, or determine whether a request
- * succeeded or failed, a callback must be specified (see the {@link #setCallback(Callback) setCallback} method).
- *
- * This should only be called from the UI thread.
- *
- * @param requests
- * the Requests to execute
- * @return a RequestAsyncTask that is executing the request
- *
- * @throws IllegalArgumentException if the passed in collection is empty
- * @throws NullPointerException if the passed in collection or any of its contents are null
- */
- public static RequestAsyncTask executeBatchAsync(Collection requests) {
- return executeBatchAsync(new RequestBatch(requests));
- }
-
- /**
- * Executes requests as a single batch asynchronously. This function will return immediately, and the requests will
- * be processed on a separate thread. In order to process results of a request, or determine whether a request
- * succeeded or failed, a callback must be specified (see the {@link #setCallback(Callback) setCallback} method).
- *
- * This should only be called from the UI thread.
- *
- * @param requests
- * the RequestBatch to execute
- * @return a RequestAsyncTask that is executing the request
- *
- * @throws IllegalArgumentException if the passed in RequestBatch is empty
- * @throws NullPointerException if the passed in RequestBatch or any of its contents are null
- */
- public static RequestAsyncTask executeBatchAsync(RequestBatch requests) {
- Validate.notEmptyAndContainsNoNulls(requests, "requests");
-
- RequestAsyncTask asyncTask = new RequestAsyncTask(requests);
- asyncTask.executeOnSettingsExecutor();
- return asyncTask;
- }
-
- /**
- * Executes requests that have already been serialized into an HttpURLConnection. No validation is done that the
- * contents of the connection actually reflect the serialized requests, so it is the caller's responsibility to
- * ensure that it will correctly generate the desired responses.
- *
- * This should only be called if you have transitioned off the UI thread.
- *
- * @param connection
- * the HttpURLConnection that the requests were serialized into
- * @param requests
- * the requests represented by the HttpURLConnection
- * @return a list of Responses corresponding to the requests
- *
- * @throws FacebookException
- * If there was an error in the protocol used to communicate with the service
- */
- public static List executeConnectionAndWait(HttpURLConnection connection, Collection requests) {
- return executeConnectionAndWait(connection, new RequestBatch(requests));
- }
-
- /**
- * Executes requests that have already been serialized into an HttpURLConnection. No validation is done that the
- * contents of the connection actually reflect the serialized requests, so it is the caller's responsibility to
- * ensure that it will correctly generate the desired responses.
- *
- * This should only be called if you have transitioned off the UI thread.
- *
- * @param connection
- * the HttpURLConnection that the requests were serialized into
- * @param requests
- * the RequestBatch represented by the HttpURLConnection
- * @return a list of Responses corresponding to the requests
- *
- * @throws FacebookException
- * If there was an error in the protocol used to communicate with the service
- */
- public static List executeConnectionAndWait(HttpURLConnection connection, RequestBatch requests) {
- List responses = Response.fromHttpConnection(connection, requests);
-
- Utility.disconnectQuietly(connection);
-
- int numRequests = requests.size();
- if (numRequests != responses.size()) {
- throw new FacebookException(String.format("Received %d responses while expecting %d", responses.size(),
- numRequests));
- }
-
- runCallbacks(requests, responses);
-
- // See if any of these sessions needs its token to be extended. We do this after issuing the request so as to
- // reduce network contention.
- HashSet