From 84c95a725f8eaa1e519030ef543bcb05b3a2b71b Mon Sep 17 00:00:00 2001 From: Antoine Vianey Date: Sun, 10 Aug 2014 14:25:38 +0200 Subject: [PATCH] Upgrade to facebook sdk 3.17.1 --- facebook/AndroidManifest.xml | 2 +- facebook/project.properties | 2 +- .../main/java/com/facebook/AccessToken.java | 14 +- .../java/com/facebook/AppEventsConstants.java | 7 +- .../java/com/facebook/AppEventsLogger.java | 366 ++++++++++++------ .../main/java/com/facebook/AppLinkData.java | 9 +- .../com/facebook/AuthorizationClient.java | 14 +- .../BoltsMeasurementEventListener.java | 1 - .../com/facebook/FacebookAppLinkResolver.java | 12 +- .../facebook/FacebookBroadcastReceiver.java | 1 - .../com/facebook/FacebookRequestError.java | 72 +++- .../java/com/facebook/FacebookSdkVersion.java | 2 +- .../com/facebook/FacebookTimeSpentData.java | 226 +++++++++++ .../java/com/facebook/GetTokenClient.java | 2 +- .../NativeAppCallAttachmentStore.java | 5 +- .../NativeAppCallContentProvider.java | 5 +- .../src/main/java/com/facebook/Request.java | 123 +----- .../src/main/java/com/facebook/Response.java | 8 +- .../src/main/java/com/facebook/Session.java | 9 +- .../com/facebook/SessionDefaultAudience.java | 2 +- .../src/main/java/com/facebook/Settings.java | 29 +- .../main/java/com/facebook/TestSession.java | 95 ++--- .../java/com/facebook/UiLifecycleHelper.java | 9 +- .../java/com/facebook/android/FbDialog.java | 4 +- .../internal/AttributionIdentifiers.java | 8 + .../facebook/internal/ImageDownloader.java | 3 +- .../com/facebook/internal/NativeProtocol.java | 156 +++++++- .../com/facebook/internal/ServerProtocol.java | 9 +- .../java/com/facebook/internal/Utility.java | 32 +- .../com/facebook/widget/FacebookDialog.java | 113 +++++- .../facebook/widget/GraphObjectAdapter.java | 2 +- .../widget/GraphObjectPagingLoader.java | 2 +- .../java/com/facebook/widget/LoginButton.java | 7 +- .../com/facebook/widget/PickerFragment.java | 7 +- .../facebook/widget/PlacePickerFragment.java | 2 +- .../com/facebook/widget/ToolTipPopup.java | 8 +- .../java/com/facebook/widget/WebDialog.java | 4 +- 37 files changed, 941 insertions(+), 431 deletions(-) create mode 100644 facebook/src/main/java/com/facebook/FacebookTimeSpentData.java diff --git a/facebook/AndroidManifest.xml b/facebook/AndroidManifest.xml index 2f9284f..1c7e076 100644 --- a/facebook/AndroidManifest.xml +++ b/facebook/AndroidManifest.xml @@ -18,5 +18,5 @@ - + diff --git a/facebook/project.properties b/facebook/project.properties index cd0ca12..131a554 100644 --- a/facebook/project.properties +++ b/facebook/project.properties @@ -12,4 +12,4 @@ android.library=true # Project target. -target=android-8 +target=android-9 diff --git a/facebook/src/main/java/com/facebook/AccessToken.java b/facebook/src/main/java/com/facebook/AccessToken.java index 392d677..898abb5 100644 --- a/facebook/src/main/java/com/facebook/AccessToken.java +++ b/facebook/src/main/java/com/facebook/AccessToken.java @@ -27,11 +27,7 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; +import java.util.*; /** * This class represents an access token returned by the Facebook Login service, along with associated @@ -240,9 +236,11 @@ static AccessToken createFromWebBundle(List requestedPermissions, Bundle static AccessToken createFromRefresh(AccessToken current, Bundle bundle) { // Only tokens obtained via SSO support refresh. Token refresh returns the expiration date in // seconds from the epoch rather than seconds from now. - assert (current.source == AccessTokenSource.FACEBOOK_APPLICATION_WEB || - current.source == AccessTokenSource.FACEBOOK_APPLICATION_NATIVE || - current.source == AccessTokenSource.FACEBOOK_APPLICATION_SERVICE); + if (current.source != AccessTokenSource.FACEBOOK_APPLICATION_WEB && + current.source != AccessTokenSource.FACEBOOK_APPLICATION_NATIVE && + current.source != AccessTokenSource.FACEBOOK_APPLICATION_SERVICE) { + throw new FacebookException("Invalid token source: " + current.source); + } Date expires = getBundleLongAsDate(bundle, EXPIRES_IN_KEY, new Date(0)); String token = bundle.getString(ACCESS_TOKEN_KEY); diff --git a/facebook/src/main/java/com/facebook/AppEventsConstants.java b/facebook/src/main/java/com/facebook/AppEventsConstants.java index 24fd138..51ba60e 100644 --- a/facebook/src/main/java/com/facebook/AppEventsConstants.java +++ b/facebook/src/main/java/com/facebook/AppEventsConstants.java @@ -28,6 +28,12 @@ public class AppEventsConstants { /** Log this event when an app is being activated. */ public static final String EVENT_NAME_ACTIVATED_APP = "fb_mobile_activate_app"; + public static final String EVENT_NAME_DEACTIVATED_APP = "fb_mobile_deactivate_app"; + + public static final String EVENT_NAME_SESSION_INTERRUPTIONS = "fb_mobile_app_interruptions"; + + public static final String EVENT_NAME_TIME_BETWEEN_SESSIONS = "fb_mobile_time_between_sessions"; + /** Log this event when a user has completed registration with the app. */ public static final String EVENT_NAME_COMPLETED_REGISTRATION = "fb_mobile_complete_registration"; @@ -164,5 +170,4 @@ public class AppEventsConstants { /** No-valued parameter value to be used with parameter keys that need a Yes/No value */ public static final String EVENT_PARAM_VALUE_NO = "0"; - } diff --git a/facebook/src/main/java/com/facebook/AppEventsLogger.java b/facebook/src/main/java/com/facebook/AppEventsLogger.java index e35789a..05e747c 100644 --- a/facebook/src/main/java/com/facebook/AppEventsLogger.java +++ b/facebook/src/main/java/com/facebook/AppEventsLogger.java @@ -21,7 +21,10 @@ import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; -import com.facebook.internal.*; +import com.facebook.internal.AttributionIdentifiers; +import com.facebook.internal.Logger; +import com.facebook.internal.Utility; +import com.facebook.internal.Validate; import com.facebook.model.GraphObject; import org.json.JSONArray; import org.json.JSONException; @@ -31,6 +34,8 @@ import java.math.BigDecimal; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** @@ -122,41 +127,13 @@ public enum FlushBehavior { EXPLICIT_ONLY, } - private enum SuppressionTimeoutBehavior { - // Successfully logging an event will reset the timeout period (i.e., events will log no more than every N - // seconds). - RESET_TIMEOUT_WHEN_LOG_SUCCESSFUL, - // Attempting to log an event, even if it is suppressed, will reset the timeout period (i.e., events will not - // be logged until they have been "silent" for at least N seconds). - RESET_TIMEOUT_WHEN_LOG_ATTEMPTED, - } - - private static class EventSuppression { - // Timeout period in seconds - private int timeoutPeriod; - private SuppressionTimeoutBehavior behavior; - - EventSuppression(int timeoutPeriod, SuppressionTimeoutBehavior behavior) { - this.timeoutPeriod = timeoutPeriod; - this.behavior = behavior; - } - - int getTimeoutPeriod() { - return timeoutPeriod; - } - - SuppressionTimeoutBehavior getBehavior() { - return behavior; - } - } - // Constants private static final String TAG = AppEventsLogger.class.getCanonicalName(); private static final int NUM_LOG_EVENTS_TO_TRY_TO_FLUSH_AFTER = 100; private static final int FLUSH_PERIOD_IN_SECONDS = 60; private static final int APP_SUPPORTS_ATTRIBUTION_ID_RECHECK_PERIOD_IN_SECONDS = 60 * 60 * 24; - private static final int APP_ACTIVATE_SUPPRESSION_PERIOD_IN_SECONDS = 5 * 60; + private static final int FLUSH_APP_SESSION_INFO_IN_SECONDS = 30; // Instance member variables private final Context context; @@ -164,22 +141,12 @@ SuppressionTimeoutBehavior getBehavior() { private static Map stateMap = new ConcurrentHashMap(); - private static Timer flushTimer; - private static Timer supportsAttributionRecheckTimer; + private static ScheduledThreadPoolExecutor backgroundExecutor; private static FlushBehavior flushBehavior = FlushBehavior.AUTO; private static boolean requestInFlight; private static Context applicationContext; private static Object staticLock = new Object(); private static String hashedDeviceAndAppId; - private static Map mapEventsToSuppressionTime = new HashMap(); - @SuppressWarnings("serial") - private static Map mapEventNameToSuppress = new HashMap() { - { - put(AppEventsConstants.EVENT_NAME_ACTIVATED_APP, - new EventSuppression(APP_ACTIVATE_SUPPRESSION_PERIOD_IN_SECONDS, - SuppressionTimeoutBehavior.RESET_TIMEOUT_WHEN_LOG_ATTEMPTED)); - } - }; // Rather than retaining Sessions, we extract the information we need and track app events by // application ID and access token (which may be null for Session-less calls). This avoids needing to @@ -291,8 +258,60 @@ public static void activateApp(Context context, String applicationId) { // can't reliably infer install state for all conditions of an app activate. Settings.publishInstallAsync(context, applicationId, null); - AppEventsLogger logger = new AppEventsLogger(context, applicationId, null); - logger.logEvent(AppEventsConstants.EVENT_NAME_ACTIVATED_APP); + final AppEventsLogger logger = new AppEventsLogger(context, applicationId, null); + final long eventTime = System.currentTimeMillis(); + backgroundExecutor.execute(new Runnable() { + @Override + public void run() { + logger.logAppSessionResumeEvent(eventTime); + } + }); + } + + /** + * Notifies the events system that the app has been deactivated (put in the background) and + * tracks the application session information. Should be called whenever your app becomes + * inactive, typically in the onPause() method of each long-running Activity of your app. + * + * Use this method if your application ID is stored in application metadata, otherwise see + * {@link AppEventsLogger#deactivateApp(android.content.Context, String)}. + * + * @param context Used to access the applicationId and the attributionId for non-authenticated users. + */ + public static void deactivateApp(Context context) { + deactivateApp(context, Utility.getMetadataApplicationId(context)); + } + + /** + * Notifies the events system that the app has been deactivated (put in the background) and + * tracks the application session information. Should be called whenever your app becomes + * inactive, typically in the onPause() method of each long-running Activity of your app. + * + * @param context Used to access the attributionId for non-authenticated users. + * + * @param applicationId The specific applicationId to track session information for. + */ + public static void deactivateApp(Context context, String applicationId) { + if (context == null || applicationId == null) { + throw new IllegalArgumentException("Both context and applicationId must be non-null"); + } + + final AppEventsLogger logger = new AppEventsLogger(context, applicationId, null); + final long eventTime = System.currentTimeMillis(); + backgroundExecutor.execute(new Runnable() { + @Override + public void run() { + logger.logAppSessionSuspendEvent(eventTime); + } + }); + } + + private void logAppSessionResumeEvent(long eventTime) { + PersistedAppSessionInfo.onResume(applicationContext, accessTokenAppId, this, eventTime); + } + + private void logAppSessionSuspendEvent(long eventTime) { + PersistedAppSessionInfo.onSuspend(applicationContext, accessTokenAppId, this, eventTime); } /** @@ -538,6 +557,7 @@ public String getApplicationId() { // Private implementation // + @SuppressWarnings("UnusedDeclaration") private enum FlushReason { EXPLICIT, TIMER, @@ -547,6 +567,7 @@ private enum FlushReason { EAGER_FLUSHING_EVENT, } + @SuppressWarnings("UnusedDeclaration") private enum FlushResult { SUCCESS, SERVER_ERROR, @@ -595,85 +616,65 @@ private AppEventsLogger(Context context, String applicationId, Session session) private static void initializeTimersIfNeeded() { synchronized (staticLock) { - if (flushTimer != null) { + if (backgroundExecutor != null) { return; } - flushTimer = new Timer(); - supportsAttributionRecheckTimer = new Timer(); + backgroundExecutor = new ScheduledThreadPoolExecutor(1); } - flushTimer.schedule( - new TimerTask() { - @Override - public void run() { - if (getFlushBehavior() != FlushBehavior.EXPLICIT_ONLY) { - flushAndWait(FlushReason.TIMER); - } - } - }, - 0, // start immediately - FLUSH_PERIOD_IN_SECONDS * 1000); - - supportsAttributionRecheckTimer.schedule( - new TimerTask() { - @Override - public void run() { - Set applicationIds = new HashSet(); - synchronized (staticLock) { - for (AccessTokenAppIdPair accessTokenAppId : stateMap.keySet()) { - applicationIds.add(accessTokenAppId.getApplicationId()); - } - } - for (String applicationId : applicationIds) { - Utility.queryAppSettings(applicationId, true); - } + final Runnable flushRunnable = new Runnable() { + @Override + public void run() { + if (getFlushBehavior() != FlushBehavior.EXPLICIT_ONLY) { + flushAndWait(FlushReason.TIMER); + } + } + }; + + backgroundExecutor.scheduleAtFixedRate( + flushRunnable, + 0, + FLUSH_PERIOD_IN_SECONDS, + TimeUnit.SECONDS + ); + + final Runnable attributionRecheckRunnable = new Runnable() { + @Override + public void run() { + Set applicationIds = new HashSet(); + synchronized (staticLock) { + for (AccessTokenAppIdPair accessTokenAppId : stateMap.keySet()) { + applicationIds.add(accessTokenAppId.getApplicationId()); } - }, - 0, // start immediately - APP_SUPPORTS_ATTRIBUTION_ID_RECHECK_PERIOD_IN_SECONDS * 1000); + } + for (String applicationId : applicationIds) { + Utility.queryAppSettings(applicationId, true); + } + } + }; + + backgroundExecutor.scheduleAtFixedRate( + attributionRecheckRunnable, + 0, + APP_SUPPORTS_ATTRIBUTION_ID_RECHECK_PERIOD_IN_SECONDS, + TimeUnit.SECONDS + ); } private void logEvent(String eventName, Double valueToSum, Bundle parameters, boolean isImplicitlyLogged) { - AppEvent event = new AppEvent(this.context, eventName, valueToSum, parameters, isImplicitlyLogged); logEvent(context, event, accessTokenAppId); } - private static void logEvent(Context context, AppEvent event, AccessTokenAppIdPair accessTokenAppId) { - if(shouldSuppressEvent(event)) { - return; - } - - SessionEventsState state = getSessionEventsState(context, accessTokenAppId); - state.addEvent(event); - - flushIfNecessary(); - } - - // This will also update the timestamp based on specified behavior. - private static boolean shouldSuppressEvent(AppEvent event) { - EventSuppression suppressionInfo = mapEventNameToSuppress.get(event.getName()); - if (suppressionInfo == null) { - return false; - } - - Date timestamp = mapEventsToSuppressionTime.get(event.getName()); - boolean suppressed; - if (timestamp == null) { - suppressed = false; - } else { - long delta = new Date().getTime() - timestamp.getTime(); - suppressed = delta < (suppressionInfo.getTimeoutPeriod() * 1000); - } - - // Update the time if we're not suppressed, OR if we are suppressed but the behavior is to reset even on - // suppressed events. - if (!suppressed || - suppressionInfo.getBehavior() == SuppressionTimeoutBehavior.RESET_TIMEOUT_WHEN_LOG_ATTEMPTED) { - mapEventsToSuppressionTime.put(event.getName(), new Date()); - } - - return suppressed; + private static void logEvent(final Context context, final AppEvent event, final AccessTokenAppIdPair accessTokenAppId) { + Settings.getExecutor().execute(new Runnable() { + @Override + public void run() { + SessionEventsState state = getSessionEventsState(context, accessTokenAppId); + state.addEvent(event); + flushIfNecessary(); + } + }); } static void eagerFlush() { @@ -705,13 +706,20 @@ private static int getAccumulatedEventCount() { // Creates a new SessionEventsState if not already in the map. private static SessionEventsState getSessionEventsState(Context context, AccessTokenAppIdPair accessTokenAppId) { + // Do this work outside of the lock to prevent deadlocks in implementation of + // AdvertisingIdClient.getAdvertisingIdInfo, because that implementation blocks waiting on the main thread, + // which may also grab this staticLock. + SessionEventsState state = stateMap.get(accessTokenAppId); + AttributionIdentifiers attributionIdentifiers = null; + if (state == null) { + // Retrieve attributionId, but we will only send it if attribution is supported for the app. + attributionIdentifiers = AttributionIdentifiers.getAttributionIdentifiers(context); + } + synchronized (staticLock) { - SessionEventsState state = stateMap.get(accessTokenAppId); + // Check state again while we're locked. + state = stateMap.get(accessTokenAppId); if (state == null) { - // Retrieve attributionId, but we will only send it if attribution is supported for the app. - AttributionIdentifiers attributionIdentifiers = - AttributionIdentifiers.getAttributionIdentifiers(context); - state = new SessionEventsState(attributionIdentifiers, context.getPackageName(), hashedDeviceAndAppId); stateMap.put(accessTokenAppId, state); } @@ -1216,6 +1224,134 @@ public String toString() { } } + static class PersistedAppSessionInfo { + private static final String PERSISTED_SESSION_INFO_FILENAME = + "AppEventsLogger.persistedsessioninfo"; + + private static final Object staticLock = new Object(); + private static boolean hasChanges = false; + private static boolean isLoaded = false; + private static Map appSessionInfoMap; + + private static final Runnable appSessionInfoFlushRunnable = new Runnable() { + @Override + public void run() { + PersistedAppSessionInfo.saveAppSessionInformation(applicationContext); + } + }; + + @SuppressWarnings("unchecked") + private static void restoreAppSessionInformation(Context context) { + ObjectInputStream ois = null; + + synchronized (staticLock) { + if (!isLoaded) { + try { + ois = + new ObjectInputStream( + context.openFileInput(PERSISTED_SESSION_INFO_FILENAME)); + appSessionInfoMap = + (HashMap) ois.readObject(); + Logger.log( + LoggingBehavior.APP_EVENTS, + "AppEvents", + "App session info loaded"); + } catch (FileNotFoundException fex) { + } catch (Exception e) { + Log.d(TAG, "Got unexpected exception: " + e.toString()); + } finally { + Utility.closeQuietly(ois); + context.deleteFile(PERSISTED_SESSION_INFO_FILENAME); + if (appSessionInfoMap == null) { + appSessionInfoMap = + new HashMap(); + } + // Regardless of the outcome of the load, the session information cache + // is always deleted. Therefore, always treat the session information cache + // as loaded + isLoaded = true; + hasChanges = false; + } + } + } + } + + static void saveAppSessionInformation(Context context) { + ObjectOutputStream oos = null; + + synchronized (staticLock) { + if (hasChanges) { + try { + oos = new ObjectOutputStream( + new BufferedOutputStream( + context.openFileOutput( + PERSISTED_SESSION_INFO_FILENAME, + Context.MODE_PRIVATE))); + oos.writeObject(appSessionInfoMap); + hasChanges = false; + Logger.log(LoggingBehavior.APP_EVENTS, "AppEvents", "App session info saved"); + } catch (Exception e) { + Log.d(TAG, "Got unexpected exception: " + e.toString()); + } finally { + Utility.closeQuietly(oos); + } + } + } + } + + static void onResume( + Context context, + AccessTokenAppIdPair accessTokenAppId, + AppEventsLogger logger, + long eventTime + ) { + synchronized (staticLock) { + FacebookTimeSpentData timeSpentData = getTimeSpentData(context, accessTokenAppId); + timeSpentData.onResume(logger, eventTime); + onTimeSpentDataUpdate(); + } + } + + static void onSuspend( + Context context, + AccessTokenAppIdPair accessTokenAppId, + AppEventsLogger logger, + long eventTime + ) { + synchronized (staticLock) { + FacebookTimeSpentData timeSpentData = getTimeSpentData(context, accessTokenAppId); + timeSpentData.onSuspend(logger, eventTime); + onTimeSpentDataUpdate(); + } + } + + private static FacebookTimeSpentData getTimeSpentData( + Context context, + AccessTokenAppIdPair accessTokenAppId + ) { + restoreAppSessionInformation(context); + FacebookTimeSpentData result = null; + + result = appSessionInfoMap.get(accessTokenAppId); + if (result == null) { + result = new FacebookTimeSpentData(); + appSessionInfoMap.put(accessTokenAppId, result); + } + + return result; + } + + private static void onTimeSpentDataUpdate() { + if (!hasChanges) { + hasChanges = true; + backgroundExecutor.schedule( + appSessionInfoFlushRunnable, + FLUSH_APP_SESSION_INFO_IN_SECONDS, + TimeUnit.SECONDS); + } + } + } + // Read/write operations are thread-safe/atomic across all instances of PersistedEvents, but modifications // to any individual instance are not thread-safe. static class PersistedEvents { diff --git a/facebook/src/main/java/com/facebook/AppLinkData.java b/facebook/src/main/java/com/facebook/AppLinkData.java index 5309337..5dd2135 100644 --- a/facebook/src/main/java/com/facebook/AppLinkData.java +++ b/facebook/src/main/java/com/facebook/AppLinkData.java @@ -21,10 +21,11 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; +import android.text.TextUtils; import android.util.Log; -import com.facebook.internal.*; +import com.facebook.internal.AttributionIdentifiers; +import com.facebook.internal.Utility; +import com.facebook.internal.Validate; import com.facebook.model.GraphObject; import org.json.JSONArray; import org.json.JSONException; @@ -150,7 +151,7 @@ private static void fetchDeferredAppLinkFromServer( final String appLinkClassName = jsonResponse.optString(DEFERRED_APP_LINK_CLASS_FIELD); final String appLinkUrl = jsonResponse.optString(DEFERRED_APP_LINK_URL_FIELD); - if (appLinkArgsJsonString != null && appLinkArgsJsonString != "") { + if (!TextUtils.isEmpty(appLinkArgsJsonString)) { appLinkData = createFromJson(appLinkArgsJsonString); if (tapTimeUtc != -1) { diff --git a/facebook/src/main/java/com/facebook/AuthorizationClient.java b/facebook/src/main/java/com/facebook/AuthorizationClient.java index f0f31f3..00a0c07 100644 --- a/facebook/src/main/java/com/facebook/AuthorizationClient.java +++ b/facebook/src/main/java/com/facebook/AuthorizationClient.java @@ -37,7 +37,10 @@ import org.json.JSONObject; import java.io.Serializable; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; class AuthorizationClient implements Serializable { private static final long serialVersionUID = 1L; @@ -72,10 +75,6 @@ class AuthorizationClient implements Serializable { static final String EVENT_EXTRAS_MISSING_INTERNET_PERMISSION = "no_internet_permission"; static final String EVENT_EXTRAS_NOT_TRIED = "not_tried"; static final String EVENT_EXTRAS_NEW_PERMISSIONS = "new_permissions"; - static final String EVENT_EXTRAS_SERVICE_DISABLED = "service_disabled"; - static final String EVENT_EXTRAS_APP_CALL_ID = "call_id"; - static final String EVENT_EXTRAS_PROTOCOL_VERSION = "protocol_version"; - static final String EVENT_EXTRAS_WRITE_PRIVACY = "write_privacy"; List handlersToTry; AuthHandler currentHandler; @@ -598,6 +597,9 @@ boolean tryAuthorize(final AuthorizationRequest request) { addLoggingExtra(ServerProtocol.DIALOG_PARAM_SCOPE, scope); } + SessionDefaultAudience audience = request.getDefaultAudience(); + parameters.putString(ServerProtocol.DIALOG_PARAM_DEFAULT_AUDIENCE, audience.getNativeProtocolAudience()); + String previousToken = request.getPreviousAccessToken(); if (!Utility.isNullOrEmpty(previousToken) && (previousToken.equals(loadCookieToken()))) { parameters.putString(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, previousToken); @@ -810,7 +812,7 @@ boolean tryAuthorize(AuthorizationRequest request) { String e2e = getE2E(); Intent intent = NativeProtocol.createProxyAuthIntent(context, request.getApplicationId(), - request.getPermissions(), e2e, request.isRerequest()); + request.getPermissions(), e2e, request.isRerequest(), request.getDefaultAudience()); addLoggingExtra(ServerProtocol.DIALOG_PARAM_E2E, e2e); diff --git a/facebook/src/main/java/com/facebook/BoltsMeasurementEventListener.java b/facebook/src/main/java/com/facebook/BoltsMeasurementEventListener.java index 8a1eaca..6e1d710 100644 --- a/facebook/src/main/java/com/facebook/BoltsMeasurementEventListener.java +++ b/facebook/src/main/java/com/facebook/BoltsMeasurementEventListener.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; public class BoltsMeasurementEventListener extends BroadcastReceiver { diff --git a/facebook/src/main/java/com/facebook/FacebookAppLinkResolver.java b/facebook/src/main/java/com/facebook/FacebookAppLinkResolver.java index e05e05f..c36631e 100644 --- a/facebook/src/main/java/com/facebook/FacebookAppLinkResolver.java +++ b/facebook/src/main/java/com/facebook/FacebookAppLinkResolver.java @@ -36,6 +36,7 @@ */ public class FacebookAppLinkResolver implements AppLinkResolver { + private static final String APP_LINK_KEY = "app_links"; private static final String APP_LINK_ANDROID_TARGET_KEY = "android"; private static final String APP_LINK_WEB_TARGET_KEY = "web"; private static final String APP_LINK_TARGET_PACKAGE_KEY = "package"; @@ -105,11 +106,12 @@ public Task> getAppLinkFromUrlsInBackground(List uris) { final Task>.TaskCompletionSource taskCompletionSource = Task.create(); Bundle appLinkRequestParameters = new Bundle(); - appLinkRequestParameters.putString("type", "al"); + appLinkRequestParameters.putString("ids", graphRequestFields.toString()); appLinkRequestParameters.putString( "fields", - String.format("%s,%s", APP_LINK_ANDROID_TARGET_KEY, APP_LINK_WEB_TARGET_KEY)); + String.format("%s.fields(%s,%s)", APP_LINK_KEY, APP_LINK_ANDROID_TARGET_KEY, APP_LINK_WEB_TARGET_KEY)); + Request appLinkRequest = new Request( null, /* Session */ @@ -141,7 +143,9 @@ public void onCompleted(Response response) { JSONObject urlData = null; try { urlData = responseJson.getJSONObject(uri.toString()); - JSONArray rawTargets = urlData.getJSONArray(APP_LINK_ANDROID_TARGET_KEY); + JSONObject appLinkData = urlData.getJSONObject(APP_LINK_KEY); + + JSONArray rawTargets = appLinkData.getJSONArray(APP_LINK_ANDROID_TARGET_KEY); int targetsCount = rawTargets.length(); List targets = new ArrayList(targetsCount); @@ -153,7 +157,7 @@ public void onCompleted(Response response) { } } - Uri webFallbackUrl = getWebFallbackUriFromJson(uri, urlData); + Uri webFallbackUrl = getWebFallbackUriFromJson(uri, appLinkData); AppLink appLink = new AppLink(uri, targets, webFallbackUrl); appLinkResults.put(uri, appLink); diff --git a/facebook/src/main/java/com/facebook/FacebookBroadcastReceiver.java b/facebook/src/main/java/com/facebook/FacebookBroadcastReceiver.java index ec2556c..1704cda 100644 --- a/facebook/src/main/java/com/facebook/FacebookBroadcastReceiver.java +++ b/facebook/src/main/java/com/facebook/FacebookBroadcastReceiver.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.util.Log; import com.facebook.internal.NativeProtocol; /** diff --git a/facebook/src/main/java/com/facebook/FacebookRequestError.java b/facebook/src/main/java/com/facebook/FacebookRequestError.java index 937e2b5..954d0ec 100644 --- a/facebook/src/main/java/com/facebook/FacebookRequestError.java +++ b/facebook/src/main/java/com/facebook/FacebookRequestError.java @@ -58,6 +58,9 @@ public final class FacebookRequestError { private static final String ERROR_SUB_CODE_KEY = "error_subcode"; private static final String ERROR_MSG_KEY = "error_msg"; private static final String ERROR_REASON_KEY = "error_reason"; + private static final String ERROR_USER_TITLE_KEY = "error_user_title"; + private static final String ERROR_USER_MSG_KEY = "error_user_msg"; + private static final String ERROR_IS_TRANSIENT_KEY = "is_transient"; private static class Range { private final int start, end; @@ -98,6 +101,9 @@ boolean contains(int value) { private final int subErrorCode; private final String errorType; private final String errorMessage; + private final String errorUserTitle; + private final String errorUserMessage; + private final boolean errorIsTransient; private final JSONObject requestResult; private final JSONObject requestResultBody; private final Object batchRequestResult; @@ -105,9 +111,9 @@ boolean contains(int value) { private final FacebookException exception; private FacebookRequestError(int requestStatusCode, int errorCode, - int subErrorCode, String errorType, String errorMessage, JSONObject requestResultBody, - JSONObject requestResult, Object batchRequestResult, HttpURLConnection connection, - FacebookException exception) { + int subErrorCode, String errorType, String errorMessage, String errorUserTitle, String errorUserMessage, + boolean errorIsTransient, JSONObject requestResultBody, JSONObject requestResult, Object batchRequestResult, + HttpURLConnection connection, FacebookException exception) { this.requestStatusCode = requestStatusCode; this.errorCode = errorCode; this.subErrorCode = subErrorCode; @@ -117,6 +123,9 @@ private FacebookRequestError(int requestStatusCode, int errorCode, this.requestResult = requestResult; this.batchRequestResult = batchRequestResult; this.connection = connection; + this.errorUserTitle = errorUserTitle; + this.errorUserMessage = errorUserMessage; + this.errorIsTransient = errorIsTransient; boolean isLocalException = false; if (exception != null) { @@ -172,28 +181,32 @@ private FacebookRequestError(int requestStatusCode, int errorCode, } } + // Notify user when error_user_msg is present + shouldNotify = errorUserMessage!= null && errorUserMessage.length() > 0; + this.category = errorCategory; this.userActionMessageId = messageId; this.shouldNotifyUser = shouldNotify; } private FacebookRequestError(int requestStatusCode, int errorCode, - int subErrorCode, String errorType, String errorMessage, JSONObject requestResultBody, - JSONObject requestResult, Object batchRequestResult, HttpURLConnection connection) { - this(requestStatusCode, errorCode, subErrorCode, errorType, errorMessage, - requestResultBody, requestResult, batchRequestResult, connection, null); + int subErrorCode, String errorType, String errorMessage, String errorUserTitle, String errorUserMessage, + boolean errorIsTransient, JSONObject requestResultBody, JSONObject requestResult, Object batchRequestResult, + HttpURLConnection connection) { + this(requestStatusCode, errorCode, subErrorCode, errorType, errorMessage, errorUserTitle, errorUserMessage, + errorIsTransient, requestResultBody, requestResult, batchRequestResult, connection, null); } FacebookRequestError(HttpURLConnection connection, Exception exception) { this(INVALID_HTTP_STATUS_CODE, INVALID_ERROR_CODE, INVALID_ERROR_CODE, - null, null, null, null, null, connection, + null, null, null, null, false, null, null, null, connection, (exception instanceof FacebookException) ? (FacebookException) exception : new FacebookException(exception)); } public FacebookRequestError(int errorCode, String errorType, String errorMessage) { this(INVALID_HTTP_STATUS_CODE, errorCode, INVALID_ERROR_CODE, errorType, errorMessage, - null, null, null, null, null); + null, null, false, null, null, null, null, null); } /** @@ -279,6 +292,36 @@ public String getErrorMessage() { } } + /** + * A message suitable for display to the user, describing a user action necessary to enable Facebook functionality. + * Not all Facebook errors yield a message suitable for user display; however in all cases where + * shouldNotifyUser() returns true, this method returns a non-null message suitable for display. + * + * @return the error message returned from Facebook + */ + public String getErrorUserMessage() { + return errorUserMessage; + } + + /** + * A short summary of the error suitable for display to the user. + * Not all Facebook errors yield a title/message suitable for user display; however in all cases where + * getErrorUserTitle() returns valid String - user should be notified. + * + * @return the error message returned from Facebook + */ + public String getErrorUserTitle() { + return errorUserTitle; + } + + /** + * @return true if given error is transient and may succeed if the initial action is retried as-is. + * Application may use this information to display a "Retry" button, if user should be notified about this error. + */ + public boolean getErrorIsTransient() { + return errorIsTransient; + } + /** * Returns the body portion of the response corresponding to the request from Facebook. * @@ -359,6 +402,9 @@ static FacebookRequestError checkResponseAndCreateError(JSONObject singleResult, // with several sub-properties, or else one or more top-level fields containing error info. String errorType = null; String errorMessage = null; + String errorUserMessage = null; + String errorUserTitle = null; + boolean errorIsTransient = false; int errorCode = INVALID_ERROR_CODE; int errorSubCode = INVALID_ERROR_CODE; @@ -371,6 +417,9 @@ static FacebookRequestError checkResponseAndCreateError(JSONObject singleResult, errorMessage = error.optString(ERROR_MESSAGE_FIELD_KEY, null); errorCode = error.optInt(ERROR_CODE_FIELD_KEY, INVALID_ERROR_CODE); errorSubCode = error.optInt(ERROR_SUB_CODE_KEY, INVALID_ERROR_CODE); + errorUserMessage = error.optString(ERROR_USER_MSG_KEY, null); + errorUserTitle = error.optString(ERROR_USER_TITLE_KEY, null); + errorIsTransient = error.optBoolean(ERROR_IS_TRANSIENT_KEY, false); hasError = true; } else if (jsonBody.has(ERROR_CODE_KEY) || jsonBody.has(ERROR_MSG_KEY) || jsonBody.has(ERROR_REASON_KEY)) { @@ -383,14 +432,15 @@ static FacebookRequestError checkResponseAndCreateError(JSONObject singleResult, if (hasError) { return new FacebookRequestError(responseCode, errorCode, errorSubCode, - errorType, errorMessage, jsonBody, singleResult, batchResult, connection); + errorType, errorMessage, errorUserTitle, errorUserMessage, errorIsTransient, jsonBody, + singleResult, batchResult, connection); } } // If we didn't get error details, but we did get a failure response code, report it. if (!HTTP_RANGE_SUCCESS.contains(responseCode)) { return new FacebookRequestError(responseCode, INVALID_ERROR_CODE, - INVALID_ERROR_CODE, null, null, + INVALID_ERROR_CODE, null, null, null, null, false, singleResult.has(BODY_KEY) ? (JSONObject) Utility.getStringPropertyAsJSON( singleResult, BODY_KEY, Response.NON_JSON_RESPONSE_PROPERTY) : null, diff --git a/facebook/src/main/java/com/facebook/FacebookSdkVersion.java b/facebook/src/main/java/com/facebook/FacebookSdkVersion.java index 4e0e5ea..7067b76 100644 --- a/facebook/src/main/java/com/facebook/FacebookSdkVersion.java +++ b/facebook/src/main/java/com/facebook/FacebookSdkVersion.java @@ -17,5 +17,5 @@ package com.facebook; final class FacebookSdkVersion { - public static final String BUILD = "3.16.0"; + public static final String BUILD = "3.17.0"; } diff --git a/facebook/src/main/java/com/facebook/FacebookTimeSpentData.java b/facebook/src/main/java/com/facebook/FacebookTimeSpentData.java new file mode 100644 index 0000000..08c2920 --- /dev/null +++ b/facebook/src/main/java/com/facebook/FacebookTimeSpentData.java @@ -0,0 +1,226 @@ +package com.facebook; + +import android.os.Bundle; +import android.text.format.DateUtils; + +import com.facebook.internal.Logger; + +import java.io.Serializable; + +class FacebookTimeSpentData implements Serializable { + // Constants + private static final long serialVersionUID = 1L; + private static final String TAG = AppEventsLogger.class.getCanonicalName(); + private static final long FIRST_TIME_LOAD_RESUME_TIME = -1; + private static final long INTERRUPTION_THRESHOLD_MILLISECONDS = 1000; + private static final long NUM_MILLISECONDS_IDLE_TO_BE_NEW_SESSION = + 60 * DateUtils.SECOND_IN_MILLIS; + private static final long APP_ACTIVATE_SUPPRESSION_PERIOD_IN_MILLISECONDS = + 5 * DateUtils.MINUTE_IN_MILLIS; + + // Should be kept in sync with the iOS sdk + private static final long[] INACTIVE_SECONDS_QUANTA = + new long[] { + 5 * DateUtils.MINUTE_IN_MILLIS, + 15 * DateUtils.MINUTE_IN_MILLIS, + 30 * DateUtils.MINUTE_IN_MILLIS, + 1 * DateUtils.HOUR_IN_MILLIS, + 6 * DateUtils.HOUR_IN_MILLIS, + 12 * DateUtils.HOUR_IN_MILLIS, + 1 * DateUtils.DAY_IN_MILLIS, + 2 * DateUtils.DAY_IN_MILLIS, + 3 * DateUtils.DAY_IN_MILLIS, + 7 * DateUtils.DAY_IN_MILLIS, + 14 * DateUtils.DAY_IN_MILLIS, + 21 * DateUtils.DAY_IN_MILLIS, + 28 * DateUtils.DAY_IN_MILLIS, + 60 * DateUtils.DAY_IN_MILLIS, + 90 * DateUtils.DAY_IN_MILLIS, + 120 * DateUtils.DAY_IN_MILLIS, + 150 * DateUtils.DAY_IN_MILLIS, + 180 * DateUtils.DAY_IN_MILLIS, + 365 * DateUtils.DAY_IN_MILLIS, + }; + + private boolean isWarmLaunch; + private boolean isAppActive; + private long lastActivateEventLoggedTime; + + // Member data that's persisted to disk + private long lastResumeTime; + private long lastSuspendTime; + private long millisecondsSpentInSession; + private int interruptionCount; + + /** + * Serialization proxy for the FacebookTimeSpentData class. This is version 1 of + * serialization. Future serializations may differ in format. This + * class should not be modified. If serializations formats change, + * create a new class SerializationProxyVx. + */ + private static class SerializationProxyV1 implements Serializable { + private static final long serialVersionUID = 6L; + + private final long lastResumeTime; + private final long lastSuspendTime; + private final long millisecondsSpentInSession; + private final int interruptionCount; + + SerializationProxyV1( + long lastResumeTime, + long lastSuspendTime, + long millisecondsSpentInSession, + int interruptionCount + ) { + this.lastResumeTime = lastResumeTime; + this.lastSuspendTime = lastSuspendTime; + this.millisecondsSpentInSession = millisecondsSpentInSession; + this.interruptionCount = interruptionCount; + } + + private Object readResolve() { + return new FacebookTimeSpentData( + lastResumeTime, + lastSuspendTime, + millisecondsSpentInSession, + interruptionCount); + } + } + + FacebookTimeSpentData() { + resetSession(); + } + + /** + * Constructor to be used for V1 serialization only, DO NOT CHANGE. + */ + private FacebookTimeSpentData( + long lastResumeTime, + long lastSuspendTime, + long millisecondsSpentInSession, + int interruptionCount + ) { + resetSession(); + this.lastResumeTime = lastResumeTime; + this.lastSuspendTime = lastSuspendTime; + this.millisecondsSpentInSession = millisecondsSpentInSession; + this.interruptionCount = interruptionCount; + } + + private Object writeReplace() { + return new SerializationProxyV1( + lastResumeTime, + lastSuspendTime, + millisecondsSpentInSession, + interruptionCount + ); + } + + void onSuspend(AppEventsLogger logger, long eventTime) { + if (!isAppActive) { + Logger.log(LoggingBehavior.APP_EVENTS, TAG, "Suspend for inactive app"); + return; + } + + long now = eventTime; + long delta = (now - lastResumeTime); + if (delta < 0) { + Logger.log(LoggingBehavior.APP_EVENTS, TAG, "Clock skew detected"); + delta = 0; + } + millisecondsSpentInSession += delta; + lastSuspendTime = now; + isAppActive = false; + } + + void onResume(AppEventsLogger logger, long eventTime) { + long now = eventTime; + + // Retain old behavior for activated app event - log the event if the event hasn't + // been logged in the previous suppression interval or this is a cold launch. + // If this is a cold launch, always log the event. Otherwise, use the last + // event log time to determine if the app activate should be suppressed or not. + if (isColdLaunch() || + ((now - lastActivateEventLoggedTime) > APP_ACTIVATE_SUPPRESSION_PERIOD_IN_MILLISECONDS)) { + logger.logEvent(AppEventsConstants.EVENT_NAME_ACTIVATED_APP); + lastActivateEventLoggedTime = now; + } + + // If this is an application that's not calling onSuspend yet, log and return. We can't + // track time spent for this application as there are no calls to onSuspend. + if (isAppActive) { + Logger.log(LoggingBehavior.APP_EVENTS, TAG, "Resume for active app"); + return; + } + + long interruptionDurationMillis = wasSuspendedEver() ? now - lastSuspendTime : 0; + if (interruptionDurationMillis < 0) { + Logger.log(LoggingBehavior.APP_EVENTS, TAG, "Clock skew detected"); + interruptionDurationMillis = 0; + } + + // If interruption duration is > new session threshold, then log old session + // event and start a new session. + if (interruptionDurationMillis > NUM_MILLISECONDS_IDLE_TO_BE_NEW_SESSION) { + logAppDeactivatedEvent(logger, interruptionDurationMillis); + } else { + // We're not logging this resume event - check to see if this should count + // as an interruption + if (interruptionDurationMillis > INTERRUPTION_THRESHOLD_MILLISECONDS) { + interruptionCount++; + } + } + + lastResumeTime = now; + isAppActive = true; + } + + private void logAppDeactivatedEvent(AppEventsLogger logger, long interruptionDurationMillis) { + // Log the old session information and clear the data + Bundle eventParams = new Bundle(); + eventParams.putInt( + AppEventsConstants.EVENT_NAME_SESSION_INTERRUPTIONS, + interruptionCount); + eventParams.putInt( + AppEventsConstants.EVENT_NAME_TIME_BETWEEN_SESSIONS, + getQuantaIndex(interruptionDurationMillis)); + logger.logEvent( + AppEventsConstants.EVENT_NAME_DEACTIVATED_APP, + (millisecondsSpentInSession/DateUtils.SECOND_IN_MILLIS), + eventParams); + resetSession(); + } + + private static int getQuantaIndex(long timeBetweenSessions) { + int quantaIndex = 0; + + while ( + quantaIndex < INACTIVE_SECONDS_QUANTA.length && + INACTIVE_SECONDS_QUANTA[quantaIndex] < timeBetweenSessions + ) { + ++quantaIndex; + } + + return quantaIndex; + } + + private void resetSession() { + isAppActive = false; + lastResumeTime = FIRST_TIME_LOAD_RESUME_TIME; + lastSuspendTime = FIRST_TIME_LOAD_RESUME_TIME; + interruptionCount = 0; + millisecondsSpentInSession = 0; + } + + private boolean wasSuspendedEver() { + return lastSuspendTime != FIRST_TIME_LOAD_RESUME_TIME; + } + + private boolean isColdLaunch() { + // On the very first call in the process lifecycle, this will always + // return true. After that, it will always return false. + boolean result = !isWarmLaunch; + isWarmLaunch = true; + return result; + } +} diff --git a/facebook/src/main/java/com/facebook/GetTokenClient.java b/facebook/src/main/java/com/facebook/GetTokenClient.java index 23cb31b..c0f1a70 100644 --- a/facebook/src/main/java/com/facebook/GetTokenClient.java +++ b/facebook/src/main/java/com/facebook/GetTokenClient.java @@ -18,7 +18,7 @@ import android.content.Context; -import android.os.*; +import android.os.Bundle; import com.facebook.internal.NativeProtocol; import com.facebook.internal.PlatformServiceClient; diff --git a/facebook/src/main/java/com/facebook/NativeAppCallAttachmentStore.java b/facebook/src/main/java/com/facebook/NativeAppCallAttachmentStore.java index dc41086..3704be2 100644 --- a/facebook/src/main/java/com/facebook/NativeAppCallAttachmentStore.java +++ b/facebook/src/main/java/com/facebook/NativeAppCallAttachmentStore.java @@ -24,7 +24,10 @@ import java.io.*; import java.net.URLEncoder; -import java.util.*; +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 diff --git a/facebook/src/main/java/com/facebook/NativeAppCallContentProvider.java b/facebook/src/main/java/com/facebook/NativeAppCallContentProvider.java index bde165f..374547a 100644 --- a/facebook/src/main/java/com/facebook/NativeAppCallContentProvider.java +++ b/facebook/src/main/java/com/facebook/NativeAppCallContentProvider.java @@ -24,8 +24,9 @@ import android.util.Log; import android.util.Pair; -import java.io.*; -import java.util.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.UUID; /** *

Implements a diff --git a/facebook/src/main/java/com/facebook/Request.java b/facebook/src/main/java/com/facebook/Request.java index 1bf5bc9..9b95938 100644 --- a/facebook/src/main/java/com/facebook/Request.java +++ b/facebook/src/main/java/com/facebook/Request.java @@ -42,15 +42,13 @@ import java.util.regex.Pattern; /** - * A single request to be sent to the Facebook Platform through either the Graph API or REST API. The Request class provides functionality + * 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 either a graph path (see the - * {@link #setGraphPath(String) setGraphPath} method) or a REST method name (see the {@link #setRestMethod(String) - * setRestMethod} method); a single request may not target both. + * 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 @@ -118,7 +116,6 @@ public class Request { private HttpMethod httpMethod; private String graphPath; private GraphObject graphObject; - private String restMethod; private String batchEntryName; private String batchEntryDependsOn; private boolean batchEntryOmitResultOnSuccess = true; @@ -266,26 +263,6 @@ public static Request newPostRequest(Session session, String graphPath, GraphObj return request; } - /** - * Creates a new Request configured to make a call to the Facebook REST API. - * - * @param session - * the Session to use, or null; if non-null, the session must be in an opened state - * @param restMethod - * the method in the Facebook REST API to execute - * @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 HTTP method to use for the request; must be one of GET, POST, or DELETE - * @return a Request that is ready to execute - */ - public static Request newRestRequest(Session session, String restMethod, Bundle parameters, HttpMethod httpMethod) { - Request request = new Request(session, null, parameters, httpMethod); - request.setRestMethod(restMethod); - return request; - } - /** * Creates a new Request configured to retrieve a user's own profile. * @@ -543,6 +520,7 @@ public static Request newStatusUpdateRequest(Session session, String message, Gr * 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. @@ -577,6 +555,7 @@ public static Request newCustomAudienceThirdPartyIdRequest(Session session, Cont * 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. @@ -875,7 +854,7 @@ public final String getGraphPath() { } /** - * Sets the graph path of this request. A graph path may not be set if a REST method has been specified. + * Sets the graph path of this request. * * @param graphPath * the graph path for this request @@ -945,25 +924,6 @@ public final void setParameters(Bundle parameters) { this.parameters = parameters; } - /** - * Returns the REST method to call for this request. - * - * @return the REST method - */ - public final String getRestMethod() { - return this.restMethod; - } - - /** - * Sets the REST method to call for this request. A REST method may not be set if a graph path has been specified. - * - * @param restMethod - * the REST method to call - */ - public final void setRestMethod(String restMethod) { - this.restMethod = restMethod; - } - /** * Returns the Session associated with this request. * @@ -1139,30 +1099,6 @@ public static RequestAsyncTask executePostRequestAsync(Session session, String g return newPostRequest(session, graphPath, graphObject, callback).executeAsync(); } - /** - * Starts a new Request configured to make a call to the Facebook REST API. - *

- * This should only be called from the UI thread. - * - * This method is deprecated. Prefer to call Request.newRestRequest(...).executeAsync(); - * - * @param session - * the Session to use, or null; if non-null, the session must be in an opened state - * @param restMethod - * the method in the Facebook REST API to execute - * @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 HTTP method to use for the request; must be one of GET, POST, or DELETE - * @return a RequestAsyncTask that is executing the request - */ - @Deprecated - public static RequestAsyncTask executeRestRequestAsync(Session session, String restMethod, Bundle parameters, - HttpMethod httpMethod) { - return newRestRequest(session, restMethod, parameters, httpMethod).executeAsync(); - } - /** * Starts a new Request configured to retrieve a user's own profile. *

@@ -1392,10 +1328,6 @@ public static HttpURLConnection toHttpConnection(Collection requests) { */ public static HttpURLConnection toHttpConnection(RequestBatch requests) { - for (Request request : requests) { - request.validate(); - } - URL url = null; try { if (requests.size() == 1) { @@ -1702,8 +1634,8 @@ public static RequestAsyncTask executeConnectionAsync(Handler callbackHandler, H @Override public String toString() { return new StringBuilder().append("{Request: ").append(" session: ").append(session).append(", graphPath: ") - .append(graphPath).append(", graphObject: ").append(graphObject).append(", restMethod: ") - .append(restMethod).append(", httpMethod: ").append(httpMethod).append(", parameters: ") + .append(graphPath).append(", graphObject: ").append(graphObject) + .append(", httpMethod: ").append(httpMethod).append(", parameters: ") .append(parameters).append("}").toString(); } @@ -1813,13 +1745,7 @@ final String getUrlForBatchedRequest() { throw new FacebookException("Can't override URL for a batch request"); } - String baseUrl; - if (this.restMethod != null) { - baseUrl = getRestPathWithVersion(); - } else { - baseUrl = getGraphPathWithVersion(); - } - + String baseUrl = getGraphPathWithVersion(); addCommonParameters(); return appendParametersToBaseUrl(baseUrl); } @@ -1829,18 +1755,13 @@ final String getUrlForSingleRequest() { return overriddenURL.toString(); } - String baseUrl; - if (this.restMethod != null) { - baseUrl = String.format("%s/%s", ServerProtocol.getRestUrlBase(), getRestPathWithVersion()); + String graphBaseUrlBase; + if (this.getHttpMethod() == HttpMethod.POST && graphPath != null && graphPath.endsWith(VIDEOS_SUFFIX)) { + graphBaseUrlBase = ServerProtocol.getGraphVideoUrlBase(); } else { - String graphBaseUrlBase; - if (this.getHttpMethod() == HttpMethod.POST && graphPath != null && graphPath.endsWith(VIDEOS_SUFFIX)) { - graphBaseUrlBase = ServerProtocol.getGraphVideoUrlBase(); - } else { - graphBaseUrlBase = ServerProtocol.getGraphUrlBase(); - } - baseUrl = String.format("%s/%s", graphBaseUrlBase, getGraphPathWithVersion()); + graphBaseUrlBase = ServerProtocol.getGraphUrlBase(); } + String baseUrl = String.format("%s/%s", graphBaseUrlBase, getGraphPathWithVersion()); addCommonParameters(); return appendParametersToBaseUrl(baseUrl); @@ -1854,14 +1775,6 @@ private String getGraphPathWithVersion() { return String.format("%s/%s", this.version, this.graphPath); } - private String getRestPathWithVersion() { - Matcher matcher = versionPattern.matcher(this.restMethod); - if (matcher.matches()) { - return this.restMethod; - } - return String.format("%s/%s/%s", this.version, ServerProtocol.REST_METHOD_BASE, this.restMethod); - } - private static class Attachment { private final Request request; private final Object value; @@ -1933,12 +1846,6 @@ public void writeString(String key, String value) throws IOException { batch.put(batchEntry); } - private void validate() { - if (graphPath != null && restMethod != null) { - throw new IllegalArgumentException("Only one of a graph path or REST method may be specified per request."); - } - } - private static boolean hasOnProgressCallbacks(RequestBatch requests) { for (RequestBatch.Callback callback : requests.getCallbacks()) { if (callback instanceof RequestBatch.OnProgressCallback) { diff --git a/facebook/src/main/java/com/facebook/Response.java b/facebook/src/main/java/com/facebook/Response.java index a5e408d..f562025 100644 --- a/facebook/src/main/java/com/facebook/Response.java +++ b/facebook/src/main/java/com/facebook/Response.java @@ -17,7 +17,10 @@ package com.facebook; import android.content.Context; -import com.facebook.internal.*; +import com.facebook.internal.CacheableRequestBatch; +import com.facebook.internal.FileLruCache; +import com.facebook.internal.Logger; +import com.facebook.internal.Utility; import com.facebook.model.GraphObject; import com.facebook.model.GraphObjectList; import org.json.JSONArray; @@ -52,6 +55,9 @@ public class Response { */ 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 int INVALID_SESSION_FACEBOOK_ERROR_CODE = 190; private static final String CODE_KEY = "code"; diff --git a/facebook/src/main/java/com/facebook/Session.java b/facebook/src/main/java/com/facebook/Session.java index d0d2b56..4fe2311 100644 --- a/facebook/src/main/java/com/facebook/Session.java +++ b/facebook/src/main/java/com/facebook/Session.java @@ -16,6 +16,7 @@ package com.facebook; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.*; import android.content.pm.ResolveInfo; @@ -24,7 +25,10 @@ import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; -import com.facebook.internal.*; +import com.facebook.internal.NativeProtocol; +import com.facebook.internal.SessionAuthorizationType; +import com.facebook.internal.Utility; +import com.facebook.internal.Validate; import com.facebook.model.GraphMultiResult; import com.facebook.model.GraphObject; import com.facebook.model.GraphObjectList; @@ -124,8 +128,6 @@ public class Session implements Serializable { private static final String PUBLISH_PERMISSION_PREFIX = "publish"; private static final String MANAGE_PERMISSION_PREFIX = "manage"; - private static final String BASIC_INFO_PERMISSION = "basic_info"; - @SuppressWarnings("serial") private static final Set OTHER_PUBLISH_PERMISSIONS = new HashSet() {{ add("ads_management"); @@ -190,6 +192,7 @@ private Object readResolve() { * class should not be modified. If serializations formats change, * create a new class SerializationProxyVx. */ + @SuppressWarnings("UnusedDeclaration") private static class SerializationProxyV2 implements Serializable { private static final long serialVersionUID = 7663436173185080064L; private final String applicationId; diff --git a/facebook/src/main/java/com/facebook/SessionDefaultAudience.java b/facebook/src/main/java/com/facebook/SessionDefaultAudience.java index 2fdac3d..3f1aea8 100644 --- a/facebook/src/main/java/com/facebook/SessionDefaultAudience.java +++ b/facebook/src/main/java/com/facebook/SessionDefaultAudience.java @@ -51,7 +51,7 @@ private SessionDefaultAudience(String protocol) { nativeProtocolAudience = protocol; } - String getNativeProtocolAudience() { + public String getNativeProtocolAudience() { return nativeProtocolAudience; } } diff --git a/facebook/src/main/java/com/facebook/Settings.java b/facebook/src/main/java/com/facebook/Settings.java index b4608a4..c08376c 100644 --- a/facebook/src/main/java/com/facebook/Settings.java +++ b/facebook/src/main/java/com/facebook/Settings.java @@ -30,13 +30,16 @@ import com.facebook.android.BuildConfig; import com.facebook.internal.AttributionIdentifiers; import com.facebook.internal.Utility; -import com.facebook.model.GraphObject; import com.facebook.internal.Validate; +import com.facebook.model.GraphObject; import org.json.JSONException; import org.json.JSONObject; import java.lang.reflect.Field; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -58,6 +61,7 @@ public final class Settings { private static volatile String facebookDomain = FACEBOOK_COM; private static AtomicLong onProgressThreshold = new AtomicLong(65536); private static volatile boolean platformCompatibilityEnabled; + private static volatile boolean isLoggingEnabled = BuildConfig.DEBUG; private static final int DEFAULT_CORE_POOL_SIZE = 5; private static final int DEFAULT_MAXIMUM_POOL_SIZE = 128; @@ -180,10 +184,26 @@ public static final void clearLoggingBehaviors() { */ public static final boolean isLoggingBehaviorEnabled(LoggingBehavior behavior) { synchronized (loggingBehaviors) { - return BuildConfig.DEBUG && loggingBehaviors.contains(behavior); + return Settings.isLoggingEnabled() && loggingBehaviors.contains(behavior); } } + /** + * Indicates if logging is enabled. + */ + public static final boolean isLoggingEnabled() { + return isLoggingEnabled; + } + + /** + * Used to enable or disable logging, defaults to BuildConfig.DEBUG. + * @param enabled + * Logging is enabled if true, disabled if false. + */ + public static final void setIsLoggingEnabled(boolean enabled) { + isLoggingEnabled = enabled; + } + /** * Returns the Executor used by the SDK for non-AsyncTask background work. * @@ -364,7 +384,8 @@ static Response publishInstallAndWaitForResponse( } else { return new Response(null, null, null, graphObject, true); } - } else if (identifiers.getAndroidAdvertiserId() == null && identifiers.getAttributionId() == null) { + } else if (identifiers == null || + (identifiers.getAndroidAdvertiserId() == null && identifiers.getAttributionId() == null)) { throw new FacebookException("No attribution id available to send to server."); } else { if (!Utility.queryAppSettings(applicationId, false).supportsAttribution()) { diff --git a/facebook/src/main/java/com/facebook/TestSession.java b/facebook/src/main/java/com/facebook/TestSession.java index 821c1f9..ef8b7dd 100644 --- a/facebook/src/main/java/com/facebook/TestSession.java +++ b/facebook/src/main/java/com/facebook/TestSession.java @@ -20,13 +20,12 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; -import com.facebook.model.GraphObject; -import com.facebook.model.GraphObjectList; import com.facebook.internal.Logger; import com.facebook.internal.Utility; import com.facebook.internal.Validate; -import org.json.JSONException; -import org.json.JSONObject; +import com.facebook.model.GraphObject; +import com.facebook.model.GraphObjectList; +import com.facebook.model.GraphUser; import java.util.*; @@ -232,70 +231,48 @@ private static synchronized void retrieveTestAccountsForAppIfNeeded() { appTestAccounts = new HashMap(); - // The data we need is split across two different FQL tables. We construct two queries, submit them + // The data we need is split across two different graph API queries. We construct two queries, submit them // together (the second one refers to the first one), then cross-reference the results. - // Get the test accounts for this app. - String testAccountQuery = String.format("SELECT id,access_token FROM test_account WHERE app_id = %s", - testApplicationId); - // Get the user names for those accounts. - String userQuery = "SELECT uid,name FROM user WHERE uid IN (SELECT id FROM #test_accounts)"; + Request.setDefaultBatchApplicationId(testApplicationId); Bundle parameters = new Bundle(); - - // Build a JSON string that contains our queries and pass it as the 'q' parameter of the query. - JSONObject multiquery; - try { - multiquery = new JSONObject(); - multiquery.put("test_accounts", testAccountQuery); - multiquery.put("users", userQuery); - } catch (JSONException exception) { - throw new FacebookException(exception); - } - parameters.putString("q", multiquery.toString()); - - // We need to authenticate as this app. parameters.putString("access_token", getAppAccessToken()); - Request request = new Request(null, "fql", parameters, null); - Response response = request.executeAndWait(); + Request requestTestUsers = new Request(null, "app/accounts/test-users", parameters, null); + requestTestUsers.setBatchEntryName("testUsers"); + requestTestUsers.setBatchEntryOmitResultOnSuccess(false); - if (response.getError() != null) { - throw response.getError().getException(); - } + Bundle testUserNamesParam = new Bundle(); + testUserNamesParam.putString("access_token", getAppAccessToken()); + testUserNamesParam.putString("ids", "{result=testUsers:$.data.*.id}"); + testUserNamesParam.putString("fields", "name"); - FqlResponse fqlResponse = response.getGraphObjectAs(FqlResponse.class); + Request requestTestUserNames = new Request(null, "", testUserNamesParam, null); + requestTestUserNames.setBatchEntryDependsOn("testUsers"); - GraphObjectList fqlResults = fqlResponse.getData(); - if (fqlResults == null || fqlResults.size() != 2) { - throw new FacebookException("Unexpected number of results from FQL query"); + List responses = Request.executeBatchAndWait(requestTestUsers, requestTestUserNames); + if (responses == null || responses.size() != 2) { + throw new FacebookException("Unexpected number of results from TestUsers batch query"); } - // We get back two sets of results. The first is from the test_accounts query, the second from the users query. - Collection testAccounts = fqlResults.get(0).getFqlResultSet().castToListOf(TestAccount.class); - Collection userAccounts = fqlResults.get(1).getFqlResultSet().castToListOf(UserAccount.class); + TestAccountsResponse testAccountsResponse = responses.get(0).getGraphObjectAs(TestAccountsResponse.class); + GraphObjectList testAccounts = testAccountsResponse.getData(); - // Use both sets of results to populate our static array of accounts. - populateTestAccounts(testAccounts, userAccounts); + // Response should contain a map of test accounts: { id's => { GraphUser } } + GraphObject userAccountsMap = responses.get(1).getGraphObject(); + populateTestAccounts(testAccounts, userAccountsMap); return; } private static synchronized void populateTestAccounts(Collection testAccounts, - Collection userAccounts) { - // We get different sets of data from each of these queries. We want to combine them into a single data - // structure. We have added a Name property to the TestAccount interface, even though we don't really get - // a name back from the service from that query. We stick the Name from the corresponding UserAccount in it. + GraphObject userAccountsMap) { for (TestAccount testAccount : testAccounts) { + GraphUser testUser = userAccountsMap.getPropertyAs(testAccount.getId(), GraphUser.class); + testAccount.setName(testUser.getName()); storeTestAccount(testAccount); } - - for (UserAccount userAccount : userAccounts) { - TestAccount testAccount = appTestAccounts.get(userAccount.getUid()); - if (testAccount != null) { - testAccount.setName(userAccount.getName()); - } - } } private static synchronized void storeTestAccount(TestAccount testAccount) { @@ -442,7 +419,8 @@ private void deleteTestAccount(String testAccountId, String appAccessToken) { GraphObject graphObject = response.getGraphObject(); if (error != null) { Log.w(LOG_TAG, String.format("Could not delete test account %s: %s", testAccountId, error.getException().toString())); - } else if (graphObject.getProperty(Response.NON_JSON_RESPONSE_PROPERTY) == (Boolean) false) { + } else if (graphObject.getProperty(Response.NON_JSON_RESPONSE_PROPERTY) == (Boolean) false + || graphObject.getProperty(Response.SUCCESS_KEY) == (Boolean) false) { Log.w(LOG_TAG, String.format("Could not delete test account %s: unknown reason", testAccountId)); } } @@ -484,27 +462,14 @@ private interface TestAccount extends GraphObject { String getAccessToken(); - // Note: We don't actually get Name from our FQL query. We fill it in by correlating with UserAccounts. - String getName(); - - void setName(String name); - } - - private interface UserAccount extends GraphObject { - String getUid(); - + // Note: We don't actually get Name from our accounts/test-users query. We fill it in by correlating with GraphUser. String getName(); void setName(String name); } - private interface FqlResult extends GraphObject { - GraphObjectList getFqlResultSet(); - - } - - private interface FqlResponse extends GraphObject { - GraphObjectList getData(); + private interface TestAccountsResponse extends GraphObject { + GraphObjectList getData(); } private static final class TestTokenCachingStrategy extends TokenCachingStrategy { diff --git a/facebook/src/main/java/com/facebook/UiLifecycleHelper.java b/facebook/src/main/java/com/facebook/UiLifecycleHelper.java index d1656b5..8d5b730 100644 --- a/facebook/src/main/java/com/facebook/UiLifecycleHelper.java +++ b/facebook/src/main/java/com/facebook/UiLifecycleHelper.java @@ -269,14 +269,7 @@ private boolean handleFacebookDialogActivityResult(int requestCode, int resultCo return true; } - String callIdString = data.getStringExtra(NativeProtocol.EXTRA_PROTOCOL_CALL_ID); - UUID callId = null; - if (callIdString != null) { - try { - callId = UUID.fromString(callIdString); - } catch (IllegalArgumentException exception) { - } - } + UUID callId = NativeProtocol.getCallIdFromIntent(data); // Was this result for the call we are waiting on? if (callId != null && pendingFacebookDialogCall.getCallId().equals(callId)) { diff --git a/facebook/src/main/java/com/facebook/android/FbDialog.java b/facebook/src/main/java/com/facebook/android/FbDialog.java index 603e692..e35bbaa 100644 --- a/facebook/src/main/java/com/facebook/android/FbDialog.java +++ b/facebook/src/main/java/com/facebook/android/FbDialog.java @@ -18,7 +18,9 @@ import android.content.Context; import android.os.Bundle; -import com.facebook.*; +import com.facebook.FacebookDialogException; +import com.facebook.FacebookException; +import com.facebook.FacebookOperationCanceledException; import com.facebook.android.Facebook.DialogListener; import com.facebook.widget.WebDialog; diff --git a/facebook/src/main/java/com/facebook/internal/AttributionIdentifiers.java b/facebook/src/main/java/com/facebook/internal/AttributionIdentifiers.java index 1c8f630..4efe7a3 100644 --- a/facebook/src/main/java/com/facebook/internal/AttributionIdentifiers.java +++ b/facebook/src/main/java/com/facebook/internal/AttributionIdentifiers.java @@ -19,8 +19,11 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.os.Looper; import android.util.Log; +import com.facebook.FacebookException; + import java.lang.reflect.Method; /** @@ -51,6 +54,11 @@ public class AttributionIdentifiers { private static AttributionIdentifiers getAndroidId(Context context) { AttributionIdentifiers identifiers = new AttributionIdentifiers(); try { + // We can't call getAdvertisingIdInfo on the main thread or the app will potentially + // freeze, if this is the case throw: + if (Looper.myLooper() == Looper.getMainLooper()) { + throw new FacebookException("getAndroidId cannot be called on the main thread."); + } Method isGooglePlayServicesAvailable = Utility.getMethodQuietly( "com.google.android.gms.common.GooglePlayServicesUtil", "isGooglePlayServicesAvailable", diff --git a/facebook/src/main/java/com/facebook/internal/ImageDownloader.java b/facebook/src/main/java/com/facebook/internal/ImageDownloader.java index dc55fb9..69bea7f 100644 --- a/facebook/src/main/java/com/facebook/internal/ImageDownloader.java +++ b/facebook/src/main/java/com/facebook/internal/ImageDownloader.java @@ -30,7 +30,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.util.*; +import java.util.HashMap; +import java.util.Map; public class ImageDownloader { private static final int DOWNLOAD_QUEUE_MAX_CONCURRENT = WorkQueue.DEFAULT_MAX_CONCURRENT; diff --git a/facebook/src/main/java/com/facebook/internal/NativeProtocol.java b/facebook/src/main/java/com/facebook/internal/NativeProtocol.java index 7a7186c..a2cf019 100644 --- a/facebook/src/main/java/com/facebook/internal/NativeProtocol.java +++ b/facebook/src/main/java/com/facebook/internal/NativeProtocol.java @@ -25,7 +25,10 @@ import android.os.Build; import android.os.Bundle; import android.text.TextUtils; -import com.facebook.*; +import com.facebook.FacebookException; +import com.facebook.FacebookOperationCanceledException; +import com.facebook.SessionDefaultAudience; +import com.facebook.Settings; import java.util.*; @@ -57,12 +60,26 @@ public final class NativeProtocol { public static final int PROTOCOL_VERSION_20131107 = 20131107; public static final int PROTOCOL_VERSION_20140204 = 20140204; public static final int PROTOCOL_VERSION_20140324 = 20140324; + public static final int PROTOCOL_VERSION_20140701 = 20140701; public static final String EXTRA_PROTOCOL_VERSION = "com.facebook.platform.protocol.PROTOCOL_VERSION"; public static final String EXTRA_PROTOCOL_ACTION = "com.facebook.platform.protocol.PROTOCOL_ACTION"; public static final String EXTRA_PROTOCOL_CALL_ID = "com.facebook.platform.protocol.CALL_ID"; public static final String EXTRA_GET_INSTALL_DATA_PACKAGE = "com.facebook.platform.extra.INSTALLDATA_PACKAGE"; + public static final String EXTRA_PROTOCOL_BRIDGE_ARGS = + "com.facebook.platform.protocol.BRIDGE_ARGS"; + + public static final String EXTRA_PROTOCOL_METHOD_ARGS = + "com.facebook.platform.protocol.METHOD_ARGS"; + + public static final String EXTRA_PROTOCOL_METHOD_RESULTS = + "com.facebook.platform.protocol.RESULT_ARGS"; + + public static final String BRIDGE_ARG_APP_NAME_STRING = "app_name"; + public static final String BRIDGE_ARG_ACTION_ID_STRING = "action_id"; + public static final String BRIDGE_ARG_ERROR_BUNDLE = "error"; + // Messages supported by PlatformService: public static final int MESSAGE_GET_ACCESS_TOKEN_REQUEST = 0x10000; public static final int MESSAGE_GET_ACCESS_TOKEN_REPLY = 0x10001; @@ -126,12 +143,27 @@ public final class NativeProtocol { public static final String EXTRA_DATA_FAILURES_FATAL = "com.facebook.platform.extra.DATA_FAILURES_FATAL"; public static final String EXTRA_PHOTOS = "com.facebook.platform.extra.PHOTOS"; + public static final String METHOD_ARGS_PLACE_TAG = "PLACE"; + public static final String METHOD_ARGS_FRIEND_TAGS = "FRIENDS"; + public static final String METHOD_ARGS_LINK = "LINK"; + public static final String METHOD_ARGS_IMAGE = "IMAGE"; + public static final String METHOD_ARGS_TITLE = "TITLE"; + public static final String METHOD_ARGS_SUBTITLE = "SUBTITLE"; + public static final String METHOD_ARGS_DESCRIPTION = "DESCRIPTION"; + public static final String METHOD_ARGS_REF = "REF"; + public static final String METHOD_ARGS_DATA_FAILURES_FATAL = "DATA_FAILURES_FATAL"; + public static final String METHOD_ARGS_PHOTOS = "PHOTOS"; + // Extras supported for ACTION_OGACTIONPUBLISH_DIALOG: public static final String EXTRA_ACTION = "com.facebook.platform.extra.ACTION"; public static final String EXTRA_ACTION_TYPE = "com.facebook.platform.extra.ACTION_TYPE"; public static final String EXTRA_PREVIEW_PROPERTY_NAME = "com.facebook.platform.extra.PREVIEW_PROPERTY_NAME"; + public static final String METHOD_ARGS_ACTION = "ACTION"; + public static final String METHOD_ARGS_ACTION_TYPE = "ACTION_TYPE"; + public static final String METHOD_ARGS_PREVIEW_PROPERTY_NAME = "PREVIEW_PROPERTY_NAME"; + // OG objects will have this key to set to true if they should be created as part of OG Action publish public static final String OPEN_GRAPH_CREATE_OBJECT_KEY = "fbsdk:create_object"; // Determines whether an image is user generated @@ -157,9 +189,9 @@ public final class NativeProtocol { public static final String ERROR_PERMISSION_DENIED = "PermissionDenied"; public static final String ERROR_SERVICE_DISABLED = "ServiceDisabled"; - public static final String AUDIENCE_ME = "SELF"; - public static final String AUDIENCE_FRIENDS = "ALL_FRIENDS"; - public static final String AUDIENCE_EVERYONE = "EVERYONE"; + public static final String AUDIENCE_ME = "only_me"; + public static final String AUDIENCE_FRIENDS = "friends"; + public static final String AUDIENCE_EVERYONE = "everyone"; // Request codes for different categories of native protocol calls. public static final int DIALOG_REQUEST_CODE = 0xfacf; @@ -170,9 +202,6 @@ public final class NativeProtocol { // Columns returned by PlatformProvider private static final String PLATFORM_PROVIDER_VERSION_COLUMN = "version"; - // Broadcast action for asynchronously-executing AppCalls - private static final String PLATFORM_ASYNC_APPCALL_ACTION = "com.facebook.platform.AppCallResultBroadcast"; - private static abstract class NativeAppInfo { abstract protected String getPackage(); @@ -308,7 +337,7 @@ static Intent validateServiceIntent(Context context, Intent intent, NativeAppInf } public static Intent createProxyAuthIntent(Context context, String applicationId, List permissions, - String e2e, boolean isRerequest) { + String e2e, boolean isRerequest, SessionDefaultAudience defaultAudience) { for (NativeAppInfo appInfo : facebookAppInfoList) { Intent intent = new Intent() .setClassName(appInfo.getPackage(), FACEBOOK_PROXY_AUTH_ACTIVITY) @@ -323,6 +352,7 @@ public static Intent createProxyAuthIntent(Context context, String applicationId intent.putExtra(ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE, ServerProtocol.DIALOG_RESPONSE_TYPE_TOKEN); intent.putExtra(ServerProtocol.DIALOG_PARAM_RETURN_SCOPES, ServerProtocol.DIALOG_RETURN_SCOPES_TRUE); + intent.putExtra(ServerProtocol.DIALOG_PARAM_DEFAULT_AUDIENCE, defaultAudience.getNativeProtocolAudience()); if (!Settings.getPlatformCompatibilityEnabled()) { // Override the API Version for Auth @@ -360,6 +390,7 @@ public static Intent createTokenRefreshIntent(Context context) { // Note: be sure this stays sorted in descending order; add new versions at the beginning private static final List KNOWN_PROTOCOL_VERSIONS = Arrays.asList( + PROTOCOL_VERSION_20140701, PROTOCOL_VERSION_20140324, PROTOCOL_VERSION_20140204, PROTOCOL_VERSION_20131107, @@ -389,15 +420,43 @@ private static Intent findActivityIntent(Context context, String activityAction, return intent; } - public static Intent createPlatformActivityIntent(Context context, String action, int version, Bundle extras) { + public static boolean isVersionCompatibleWithBucketedIntent(int version) { + return KNOWN_PROTOCOL_VERSIONS.contains(version) && version >= PROTOCOL_VERSION_20140701; + } + + public static Intent createPlatformActivityIntent( + Context context, + String callId, + String action, + int version, + String applicationName, + Bundle extras) { Intent intent = findActivityIntent(context, INTENT_ACTION_PLATFORM_ACTIVITY, action); if (intent == null) { return null; } - intent.putExtras(extras) - .putExtra(EXTRA_PROTOCOL_VERSION, version) - .putExtra(EXTRA_PROTOCOL_ACTION, action); + String applicationId = Utility.getMetadataApplicationId(context); + + intent.putExtra(EXTRA_PROTOCOL_VERSION, version) + .putExtra(EXTRA_PROTOCOL_ACTION, action) + .putExtra(EXTRA_APPLICATION_ID, applicationId); + + if (isVersionCompatibleWithBucketedIntent(version)) { + // This is a bucketed intent + Bundle bridgeArguments = new Bundle(); + bridgeArguments.putString(BRIDGE_ARG_ACTION_ID_STRING, callId); + bridgeArguments.putString(BRIDGE_ARG_APP_NAME_STRING, applicationName); + intent.putExtra(EXTRA_PROTOCOL_BRIDGE_ARGS, bridgeArguments); + + Bundle methodArguments = extras == null ? new Bundle() : extras; + intent.putExtra(EXTRA_PROTOCOL_METHOD_ARGS, methodArguments); + } else { + // This is the older flat intent + intent.putExtra(EXTRA_PROTOCOL_CALL_ID, callId); + intent.putExtra(EXTRA_APPLICATION_NAME, applicationName); + intent.putExtras(extras); + } return intent; } @@ -415,8 +474,58 @@ public static Intent createPlatformServiceIntent(Context context) { return null; } + public static int getProtocolVersionFromIntent(Intent intent) { + return intent.getIntExtra(EXTRA_PROTOCOL_VERSION, 0); + } + + public static UUID getCallIdFromIntent(Intent intent) { + int version = getProtocolVersionFromIntent(intent); + String callIdString = null; + if (isVersionCompatibleWithBucketedIntent(version)) { + Bundle bridgeArgs = intent.getBundleExtra(EXTRA_PROTOCOL_BRIDGE_ARGS); + if (bridgeArgs != null) { + callIdString = bridgeArgs.getString(BRIDGE_ARG_ACTION_ID_STRING); + } + } else { + callIdString = intent.getStringExtra(EXTRA_PROTOCOL_CALL_ID); + } + + UUID callId = null; + if (callIdString != null) { + try { + callId = UUID.fromString(callIdString); + } catch (IllegalArgumentException exception) { + } + } + return callId; + } + + public static Bundle getBridgeArgumentsFromIntent(Intent intent) { + int version = getProtocolVersionFromIntent(intent); + if (!isVersionCompatibleWithBucketedIntent(version)) { + return null; + } + + return intent.getBundleExtra(EXTRA_PROTOCOL_BRIDGE_ARGS); + } + + public static Bundle getSuccessResultsFromIntent(Intent resultIntent) { + int version = getProtocolVersionFromIntent(resultIntent); + Bundle extras = resultIntent.getExtras(); + if (!isVersionCompatibleWithBucketedIntent(version) || extras == null) { + return extras; + } + + return extras.getBundle(EXTRA_PROTOCOL_METHOD_RESULTS); + } + public static boolean isErrorResult(Intent resultIntent) { - return resultIntent.hasExtra(STATUS_ERROR_TYPE); + Bundle bridgeArgs = getBridgeArgumentsFromIntent(resultIntent); + if (bridgeArgs != null) { + return bridgeArgs.containsKey(BRIDGE_ARG_ERROR_BUNDLE); + } else { + return resultIntent.hasExtra(STATUS_ERROR_TYPE); + } } public static Exception getErrorFromResult(Intent resultIntent) { @@ -424,12 +533,27 @@ public static Exception getErrorFromResult(Intent resultIntent) { return null; } - String type = resultIntent.getStringExtra(STATUS_ERROR_TYPE); - String description = resultIntent.getStringExtra(STATUS_ERROR_DESCRIPTION); + Bundle bridgeArgs = getBridgeArgumentsFromIntent(resultIntent); + if (bridgeArgs != null) { + Bundle errorBundle = bridgeArgs.getBundle(BRIDGE_ARG_ERROR_BUNDLE); + if (errorBundle != null) { + return getErrorFromResult(errorBundle); + } + } - if (type.equalsIgnoreCase(ERROR_USER_CANCELED)) { + return getErrorFromResult(resultIntent.getExtras()); + } + + public static Exception getErrorFromResult(Bundle errorBundle) { + // TODO This is not going to work for JS dialogs, where the keys are not STATUS_ERROR_TYPE etc. + // TODO However, it should keep existing dialogs functional + String type = errorBundle.getString(STATUS_ERROR_TYPE); + String description = errorBundle.getString(STATUS_ERROR_DESCRIPTION); + + if (type != null && type.equalsIgnoreCase(ERROR_USER_CANCELED)) { return new FacebookOperationCanceledException(description); } + /* TODO parse error values and create appropriate exception class */ return new FacebookException(description); } diff --git a/facebook/src/main/java/com/facebook/internal/ServerProtocol.java b/facebook/src/main/java/com/facebook/internal/ServerProtocol.java index 06155ce..bc9beec 100644 --- a/facebook/src/main/java/com/facebook/internal/ServerProtocol.java +++ b/facebook/src/main/java/com/facebook/internal/ServerProtocol.java @@ -39,6 +39,7 @@ public final class ServerProtocol { public static final String DIALOG_PARAM_RESPONSE_TYPE = "response_type"; public static final String DIALOG_PARAM_RETURN_SCOPES = "return_scopes"; public static final String DIALOG_PARAM_SCOPE = "scope"; + public static final String DIALOG_PARAM_DEFAULT_AUDIENCE = "default_audience"; public static final String DIALOG_REREQUEST_AUTH_TYPE = "rerequest"; public static final String DIALOG_RESPONSE_TYPE_TOKEN = "token"; public static final String DIALOG_RETURN_SCOPES_TRUE = "true"; @@ -46,9 +47,7 @@ public final class ServerProtocol { // URL components private static final String GRAPH_VIDEO_URL_FORMAT = "https://graph-video.%s"; private static final String GRAPH_URL_FORMAT = "https://graph.%s"; - private static final String REST_URL_FORMAT = "https://api.%s"; - public static final String REST_METHOD_BASE = "method"; - public static final String GRAPH_API_VERSION = "v2.0"; + public static final String GRAPH_API_VERSION = "v2.1"; private static final String LEGACY_API_VERSION = "v1.0"; @@ -69,10 +68,6 @@ public static final String getGraphVideoUrlBase() { return String.format(GRAPH_VIDEO_URL_FORMAT, Settings.getFacebookDomain()); } - public static final String getRestUrlBase() { - return String.format(REST_URL_FORMAT, Settings.getFacebookDomain()); - } - public static final String getAPIVersion() { if (Settings.getPlatformCompatibilityEnabled()) { return LEGACY_API_VERSION; diff --git a/facebook/src/main/java/com/facebook/internal/Utility.java b/facebook/src/main/java/com/facebook/internal/Utility.java index 41fd4e3..19a8f40 100644 --- a/facebook/src/main/java/com/facebook/internal/Utility.java +++ b/facebook/src/main/java/com/facebook/internal/Utility.java @@ -17,27 +17,19 @@ package com.facebook.internal; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.BatteryManager; -import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.Parcelable; -import android.os.StatFs; import android.provider.Settings.Secure; -import android.telephony.TelephonyManager; import android.text.TextUtils; -import android.util.DisplayMetrics; import android.util.Log; -import android.view.Display; -import android.view.WindowManager; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; -import com.facebook.*; +import com.facebook.FacebookException; +import com.facebook.Request; +import com.facebook.Settings; import com.facebook.android.BuildConfig; import com.facebook.model.GraphObject; import org.json.JSONArray; @@ -85,12 +77,6 @@ public final class Utility { private static Map fetchedAppSettings = new ConcurrentHashMap(); - private static int sNumCPUCores = 0; - private static long sTotalExternalStorageBytes = -1; - private static long sAvailableExternalStorageBytes = -1; - private static String sCarrierName = null; - - public static class FetchedAppSettings { private boolean supportsAttribution; private boolean supportsImplicitLogging; @@ -366,13 +352,13 @@ public static void clearFacebookCookies(Context context) { } public static void logd(String tag, Exception e) { - if (BuildConfig.DEBUG && tag != null && e != null) { + if (Settings.isLoggingEnabled() && tag != null && e != null) { Log.d(tag, e.getClass().getSimpleName() + ": " + e.getMessage()); } } public static void logd(String tag, String msg) { - if (BuildConfig.DEBUG && tag != null && msg != null) { + if (Settings.isLoggingEnabled() && tag != null && msg != null) { Log.d(tag, msg); } } @@ -516,14 +502,6 @@ public static void setAppEventExtendedDeviceInfoParameters(GraphObject params, C params.setProperty("extinfo", extraInfoArray.toString()); } - private static void silentJSONObjectPut(JSONObject object, String key, T data) { - try { - object.put(key, data); - } catch (JSONException e) { - // Swallow - } - } - public static Method getMethodQuietly(Class clazz, String methodName, Class... parameterTypes) { try { return clazz.getMethod(methodName, parameterTypes); diff --git a/facebook/src/main/java/com/facebook/widget/FacebookDialog.java b/facebook/src/main/java/com/facebook/widget/FacebookDialog.java index 1322228..226e120 100644 --- a/facebook/src/main/java/com/facebook/widget/FacebookDialog.java +++ b/facebook/src/main/java/com/facebook/widget/FacebookDialog.java @@ -29,7 +29,10 @@ import com.facebook.internal.NativeProtocol; import com.facebook.internal.Utility; import com.facebook.internal.Validate; -import com.facebook.model.*; +import com.facebook.model.GraphObject; +import com.facebook.model.GraphObjectList; +import com.facebook.model.OpenGraphAction; +import com.facebook.model.OpenGraphObject; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -341,9 +344,11 @@ public static boolean handleActivityResult(Context context, PendingCall appCall, if (callback != null) { if (NativeProtocol.isErrorResult(data)) { Exception error = NativeProtocol.getErrorFromResult(data); + + // TODO - data.getExtras() doesn't work for the bucketed protocol. callback.onError(appCall, error, data.getExtras()); } else { - callback.onComplete(appCall, data.getExtras()); + callback.onComplete(appCall, NativeProtocol.getSuccessResultsFromIntent(data)); } } @@ -480,7 +485,14 @@ static private String getEventName(String action, boolean hasPhotos) { return eventName; } - abstract static class Builder> { + /** + * Provides a base class for various FacebookDialog builders. This is public primarily to allow its use elsewhere + * in the Android SDK; developers are discouraged from constructing their own FacebookDialog builders as the + * internal API may change. + * + * @param The concrete base class of the builder. + */ + public abstract static class Builder> { final protected Activity activity; final protected String applicationId; final protected PendingCall appCall; @@ -489,7 +501,12 @@ abstract static class Builder> { protected HashMap imageAttachments = new HashMap(); protected HashMap imageAttachmentFiles = new HashMap(); - Builder(Activity activity) { + /** + * Constructor. + * + * @param activity the Activity which is presenting the native Share dialog; must not be null + */ + public Builder(Activity activity) { Validate.notNull(activity, "activity"); this.activity = activity; @@ -549,16 +566,26 @@ public CONCRETE setFragment(Fragment fragment) { public FacebookDialog build() { validate(); - Bundle extras = new Bundle(); - putExtra(extras, NativeProtocol.EXTRA_APPLICATION_ID, applicationId); - putExtra(extras, NativeProtocol.EXTRA_APPLICATION_NAME, applicationName); - extras = setBundleExtras(extras); - String action = getActionForFeatures(getDialogFeatures()); int protocolVersion = getProtocolVersionForNativeDialog(activity, action, getMinVersionForFeatures(getDialogFeatures())); - Intent intent = NativeProtocol.createPlatformActivityIntent(activity, action, protocolVersion, extras); + Bundle extras = null; + if (NativeProtocol.isVersionCompatibleWithBucketedIntent(protocolVersion)) { + // Facebook app supports the new bucketed protocol + extras = getMethodArguments(); + } else { + // Facebook app only supports the old flat protocol + extras = setBundleExtras(new Bundle()); + } + + Intent intent = NativeProtocol.createPlatformActivityIntent( + activity, + appCall.getCallId().toString(), + action, + protocolVersion, + applicationName, + extras); if (intent == null) { logDialogActivity(activity, fragment, getEventName(action, extras.containsKey(NativeProtocol.EXTRA_PHOTOS)), @@ -567,6 +594,7 @@ public FacebookDialog build() { throw new FacebookException( "Unable to create Intent; this likely means the Facebook app is not installed."); } + appCall.setRequestIntent(intent); return new FacebookDialog(activity, fragment, appCall, getOnPresentCallback()); @@ -636,9 +664,11 @@ List getImageAttachmentNames() { return new ArrayList(imageAttachments.keySet()); } - abstract Bundle setBundleExtras(Bundle extras); + protected abstract Bundle setBundleExtras(Bundle extras); - void putExtra(Bundle extras, String key, String value) { + protected abstract Bundle getMethodArguments(); + + protected void putExtra(Bundle extras, String key, String value) { if (value != null) { extras.putString(key, value); } @@ -800,7 +830,7 @@ public CONCRETE setDataErrorsFatal(boolean dataErrorsFatal) { } @Override - Bundle setBundleExtras(Bundle extras) { + protected Bundle setBundleExtras(Bundle extras) { putExtra(extras, NativeProtocol.EXTRA_APPLICATION_ID, applicationId); putExtra(extras, NativeProtocol.EXTRA_APPLICATION_NAME, applicationName); putExtra(extras, NativeProtocol.EXTRA_TITLE, name); @@ -818,6 +848,27 @@ Bundle setBundleExtras(Bundle extras) { } return extras; } + + @Override + protected Bundle getMethodArguments() { + Bundle methodArguments = new Bundle(); + + putExtra(methodArguments, NativeProtocol.METHOD_ARGS_TITLE, name); + putExtra(methodArguments, NativeProtocol.METHOD_ARGS_SUBTITLE, caption); + putExtra(methodArguments, NativeProtocol.METHOD_ARGS_DESCRIPTION, description); + putExtra(methodArguments, NativeProtocol.METHOD_ARGS_LINK, link); + putExtra(methodArguments, NativeProtocol.METHOD_ARGS_IMAGE, picture); + putExtra(methodArguments, NativeProtocol.METHOD_ARGS_PLACE_TAG, place); + putExtra(methodArguments, NativeProtocol.METHOD_ARGS_TITLE, name); + putExtra(methodArguments, NativeProtocol.METHOD_ARGS_REF, ref); + + methodArguments.putBoolean(NativeProtocol.METHOD_ARGS_DATA_FAILURES_FATAL, dataErrorsFatal); + if (!Utility.isNullOrEmpty(friends)) { + methodArguments.putStringArrayList(NativeProtocol.METHOD_ARGS_FRIEND_TAGS, friends); + } + + return methodArguments; + } } /** @@ -937,7 +988,7 @@ void validate() { } @Override - Bundle setBundleExtras(Bundle extras) { + protected Bundle setBundleExtras(Bundle extras) { putExtra(extras, NativeProtocol.EXTRA_APPLICATION_ID, applicationId); putExtra(extras, NativeProtocol.EXTRA_APPLICATION_NAME, applicationName); putExtra(extras, NativeProtocol.EXTRA_PLACE_TAG, place); @@ -948,6 +999,20 @@ Bundle setBundleExtras(Bundle extras) { } return extras; } + + @Override + protected Bundle getMethodArguments() { + Bundle methodArgs = new Bundle(); + + putExtra(methodArgs, NativeProtocol.METHOD_ARGS_PLACE_TAG, place); + methodArgs.putStringArrayList(NativeProtocol.METHOD_ARGS_PHOTOS, imageAttachmentUrls); + + if (!Utility.isNullOrEmpty(friends)) { + methodArgs.putStringArrayList(NativeProtocol.METHOD_ARGS_FRIEND_TAGS, friends); + } + + return methodArgs; + } } /** @@ -1399,7 +1464,7 @@ void updateObjectAttachmentUrls(String objectProperty, List attachmentUr } @Override - Bundle setBundleExtras(Bundle extras) { + protected Bundle setBundleExtras(Bundle extras) { putExtra(extras, NativeProtocol.EXTRA_PREVIEW_PROPERTY_NAME, previewPropertyName); putExtra(extras, NativeProtocol.EXTRA_ACTION_TYPE, actionType); extras.putBoolean(NativeProtocol.EXTRA_DATA_FAILURES_FATAL, dataErrorsFatal); @@ -1413,6 +1478,23 @@ Bundle setBundleExtras(Bundle extras) { return extras; } + @Override + protected Bundle getMethodArguments() { + Bundle methodArgs = new Bundle(); + + putExtra(methodArgs, NativeProtocol.METHOD_ARGS_PREVIEW_PROPERTY_NAME, previewPropertyName); + putExtra(methodArgs, NativeProtocol.METHOD_ARGS_ACTION_TYPE, actionType); + methodArgs.putBoolean(NativeProtocol.METHOD_ARGS_DATA_FAILURES_FATAL, dataErrorsFatal); + + JSONObject jsonAction = action.getInnerJSONObject(); + jsonAction = flattenChildrenOfGraphObject(jsonAction); + + String jsonString = jsonAction.toString(); + putExtra(methodArgs, NativeProtocol.METHOD_ARGS_ACTION, jsonString); + + return methodArgs; + } + private JSONObject flattenChildrenOfGraphObject(JSONObject graphObject) { try { // Clone the existing object to avoid modifying it from under the caller. @@ -1577,7 +1659,6 @@ private PendingCall(Parcel in) { private void setRequestIntent(Intent requestIntent) { this.requestIntent = requestIntent; - this.requestIntent.putExtra(NativeProtocol.EXTRA_PROTOCOL_CALL_ID, callId.toString()); } /** diff --git a/facebook/src/main/java/com/facebook/widget/GraphObjectAdapter.java b/facebook/src/main/java/com/facebook/widget/GraphObjectAdapter.java index 285ddee..bc0adbf 100644 --- a/facebook/src/main/java/com/facebook/widget/GraphObjectAdapter.java +++ b/facebook/src/main/java/com/facebook/widget/GraphObjectAdapter.java @@ -23,7 +23,7 @@ import android.view.ViewGroup; import android.view.ViewStub; import android.widget.*; -import com.facebook.*; +import com.facebook.FacebookException; import com.facebook.android.R; import com.facebook.internal.ImageDownloader; import com.facebook.internal.ImageRequest; diff --git a/facebook/src/main/java/com/facebook/widget/GraphObjectPagingLoader.java b/facebook/src/main/java/com/facebook/widget/GraphObjectPagingLoader.java index 17d6cb6..0f100ed 100644 --- a/facebook/src/main/java/com/facebook/widget/GraphObjectPagingLoader.java +++ b/facebook/src/main/java/com/facebook/widget/GraphObjectPagingLoader.java @@ -20,9 +20,9 @@ import android.os.Handler; import android.support.v4.content.Loader; import com.facebook.*; +import com.facebook.internal.CacheableRequestBatch; import com.facebook.model.GraphObject; import com.facebook.model.GraphObjectList; -import com.facebook.internal.CacheableRequestBatch; class GraphObjectPagingLoader extends Loader> { private final Class graphObjectClass; diff --git a/facebook/src/main/java/com/facebook/widget/LoginButton.java b/facebook/src/main/java/com/facebook/widget/LoginButton.java index 828b21d..6f548f4 100644 --- a/facebook/src/main/java/com/facebook/widget/LoginButton.java +++ b/facebook/src/main/java/com/facebook/widget/LoginButton.java @@ -33,17 +33,18 @@ import android.view.Gravity; import android.view.View; import android.widget.Button; - import com.facebook.*; import com.facebook.android.R; import com.facebook.internal.AnalyticsEvents; -import com.facebook.model.GraphUser; import com.facebook.internal.SessionAuthorizationType; import com.facebook.internal.SessionTracker; import com.facebook.internal.Utility; import com.facebook.internal.Utility.FetchedAppSettings; +import com.facebook.model.GraphUser; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; /** * A Log In/Log Out button that maintains session state and logs diff --git a/facebook/src/main/java/com/facebook/widget/PickerFragment.java b/facebook/src/main/java/com/facebook/widget/PickerFragment.java index 780fdfa..4ef6cbd 100644 --- a/facebook/src/main/java/com/facebook/widget/PickerFragment.java +++ b/facebook/src/main/java/com/facebook/widget/PickerFragment.java @@ -32,10 +32,13 @@ import android.view.ViewStub; import android.view.animation.AlphaAnimation; import android.widget.*; -import com.facebook.*; +import com.facebook.FacebookException; +import com.facebook.Request; +import com.facebook.Session; +import com.facebook.SessionState; import com.facebook.android.R; -import com.facebook.model.GraphObject; import com.facebook.internal.SessionTracker; +import com.facebook.model.GraphObject; import java.util.*; diff --git a/facebook/src/main/java/com/facebook/widget/PlacePickerFragment.java b/facebook/src/main/java/com/facebook/widget/PlacePickerFragment.java index 58b2775..0437001 100644 --- a/facebook/src/main/java/com/facebook/widget/PlacePickerFragment.java +++ b/facebook/src/main/java/com/facebook/widget/PlacePickerFragment.java @@ -35,9 +35,9 @@ import com.facebook.*; import com.facebook.android.R; import com.facebook.internal.AnalyticsEvents; -import com.facebook.model.GraphPlace; import com.facebook.internal.Logger; import com.facebook.internal.Utility; +import com.facebook.model.GraphPlace; import java.util.*; diff --git a/facebook/src/main/java/com/facebook/widget/ToolTipPopup.java b/facebook/src/main/java/com/facebook/widget/ToolTipPopup.java index 58f64f3..0622416 100644 --- a/facebook/src/main/java/com/facebook/widget/ToolTipPopup.java +++ b/facebook/src/main/java/com/facebook/widget/ToolTipPopup.java @@ -16,11 +16,6 @@ package com.facebook.widget; -import java.lang.ref.WeakReference; - -import com.facebook.android.R; -import com.facebook.widget.LoginButton.ToolTipMode; - import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; @@ -31,6 +26,9 @@ import android.widget.ImageView; import android.widget.PopupWindow; import android.widget.TextView; +import com.facebook.android.R; + +import java.lang.ref.WeakReference; public class ToolTipPopup { diff --git a/facebook/src/main/java/com/facebook/widget/WebDialog.java b/facebook/src/main/java/com/facebook/widget/WebDialog.java index 84be24e..9150f50 100644 --- a/facebook/src/main/java/com/facebook/widget/WebDialog.java +++ b/facebook/src/main/java/com/facebook/widget/WebDialog.java @@ -37,7 +37,8 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.facebook.*; -import com.facebook.android.*; +import com.facebook.android.R; +import com.facebook.android.Util; import com.facebook.internal.Logger; import com.facebook.internal.ServerProtocol; import com.facebook.internal.Utility; @@ -51,7 +52,6 @@ public class WebDialog extends Dialog { private static final String LOG_TAG = Logger.LOG_TAG_BASE + "WebDialog"; private static final String DISPLAY_TOUCH = "touch"; - private static final String USER_AGENT = "user_agent"; static final String REDIRECT_URI = "fbconnect://success"; static final String CANCEL_URI = "fbconnect://cancel"; static final boolean DISABLE_SSL_CHECK_FOR_TESTING = false;